用户端web自动化测试

一、SeleniumIDE用例录制

1、SeleniumIDE的下载以及安装

①官网:https://www.selenium.dev/
②Chrome插件:
https://chrome.google.com/webstore/detail/selenium-ide/mooikfkahbdckldjjndioackbalphokd
③Firefox插件:
https://addons.mozilla.org/en-US/firefox/addon/selenium-ide/
④github release:
https://github.com/SeleniumHQ/selenium-ide/releases
⑤其它版本:
https://addons.mozilla.org/en-GB/firefox/addon/selenium-ide/versions/ 
⑥注意:Chrome插件在国内无法下载,Firefox可以直接下载。

2、启动

1)安装完成后,通过在浏览器的菜单栏中点击它的图标来启动它:
2)如果没看到图标,首先确保是否安装了Selenium IDE扩展插件
3)通过以下链接访问所有插件
Chrome: chrome://extensions
Firefox: about:addons

3、SeleniumIDE常用功能

1)新建、保存、打开
2)开始和停止录制
3)运行8中的所有的实例
4)运行单个实例
5)调试模式
6)调整案例的运行速度
7)要录制的网址
8)实例列表
9)动作、目标、值
10)对单条命令的解释
11)运行日志

4、SeleniumIDE脚本导出:Java和Python 

 

二、自动化测试用例结构分析

1、标准的用例结构(功能)

①用例标题
②前提条件
③用例步骤
④预期结果
⑤实际结果

2、自动化用例结构及作用

【结构:自动化测试用例:作用】
①用例标题:测试包、文件、类、方法名称:【用例的唯一标识】
②前提条件:setup、setup_class(Pytest;BeforeEach、BeforeAll(JUnit):【测试用例前的准备动作,比如读取数据或者driver的初始化】
③用例步骤:测试方法内的代码逻辑:【测试用例具体的步骤行为】
④预期结果:assert 实际结果 = 预期结果:【断言,印证用例是否执行成功】
⑤实际结果:assert 实际结果 = 预期结果:【断言,印证用例是否执行成功】
⑥后置动作:teardown、teardown_class(Pytest);@AfterEach、@AfterAll(JUnit):【脏数据清理、关闭driver进程】

3、IDE录制脚本

1)脚本步骤:
①访问百度网站
②搜索框输入“霍格沃兹测试开发”
③点击搜索按钮

# Generated by Selenium IDE
import pytest
import time
import json
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

class Test():
  def setup_method(self, method):
    self.driver = webdriver.Chrome()
    self.vars = {}
  
  def teardown_method(self, method):
    self.driver.quit()
  
  def test_sougou(self):
    # 打开网页,设置窗口
    self.driver.get("https://www.sogou.com/")
    self.driver.set_window_size(1235, 693)
    # 输入搜索信息
    self.driver.find_element(By.ID, "query").click()
    self.driver.find_element(By.ID, "query").send_keys("霍格沃兹测试开发")
    # 点击搜索
    self.driver.find_element(By.ID, "stb").click()
    element = self.driver.find_element(By.ID, "stb")
    actions = ActionChains(self.driver)
    actions.move_to_element(element).perform()

#优化后的代码,添加断言判断用例是否成功
# Generated by Selenium IDE
import pytest
import time
import json
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities


class Test(object):
    # 前提条件,webdriver的初始化
    def setup_method(self, method):
        self.driver = webdriver.Chrome()
        self.vars = {}

    # 后置操作,关闭网站
    def teardown_method(self, method):
        self.driver.quit()

    # 测试用例步骤
    def test_sougou(self):
        # 打开网页,设置窗口
        self.driver.get("https://www.sogou.com/")
        self.driver.set_window_size(1235, 693)
        # 输入搜索信息
        self.driver.find_element(By.ID, "query").click()
        self.driver.find_element(By.ID, "query").send_keys("霍格沃兹测试开发")
        # 点击搜索
        self.driver.find_element(By.ID, "stb").click()
        element = self.driver.find_element(By.ID, "stb")
        actions = ActionChains(self.driver)
        actions.move_to_element(element).perform()
        # 问题:无法确定用例执行成功或失败
        # 解决方案:添加断言信息,判断搜索列表中,是否会有"霍格沃兹测试开发"
        res_element = self.driver.find_element(By.CSS_SELECTOR, "#sogou_vr_30000000_0 > em")
        # 获取到定位的文本信息
        # 判断实际获取到的搜索展示的列表和预期是否一致
        assert res_element.text == "霍格沃兹测试开发"

三、web 浏览器控制

  • 模拟功能测试中对浏览器的操作

import time
from selenium import webdriver
# 打开网页
def open_browser():
    # 实例化chromedriver
    driver = webdriver.Chrome()
    # 调用get方法时需要传递浏览器的URL
    driver.get("https://ceshiren.com/")
    # 添加等待1秒
    time.sleep(2)
    # # 刷新浏览器
    # driver.refresh()
    # # 通过get跳转到百度
    # driver.get("https://www.baidu.com/")
    # # 回退操作,退回到测试人网页
    # driver.back()
    # 最大化浏览器
    driver.maximize_window()
    time.sleep(2)
    # 最小化浏览器
    driver.minimize_window()
    time.sleep(2)

if __name__ == '__main__':
    open_browser()

四、常见控件定位方法

1、HTML铺垫

①标签:尖括号,如括起来的<a>、<html>、<title>等
②属性:a=b,如href
③类属性: class

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>测试人论坛</title>
</head>
<body>
<a href="https://ceshiren.com/" class="link">链接</a>
</body>
</html>

2、Selenium八大定位方式

1)selenium 常用定位方式
    #格式: 
    driver.find_element_by_定位方式(定位元素)
    driver.find_element(By.定位方式, 定位元素) 

    # 示例,两种方式作用一模一样
    # 官方建议使用下面的方式
    driver.find_element_by_id("su")
    driver.find_element(By.ID, "su")

 

2)【方式:描述】
①class name:class 属性对应的值
②css selector(重点):css 表达式
a.格式: driver.find_element(By.CSS_SELECTOR, "css表达式")
b.复制绝对定位
c.编写 css selector 表达式
③id(重点):id 属性对应的值
格式: driver.find_element(By.ID, "ID属性对应的值")
④name(重点):name 属性对应的值
格式: driver.find_element(By.NAME, "Name属性对应的值")
⑤link text:查找其可见文本与搜索值匹配的锚元素
格式:driver.find_element(By.LINK_TEXT,"文本信息")
⑥partial link text:查找其可见文本包含搜索值的锚元素。如果多个元素匹配,则只会选择第一个元素。
⑦tag name:标签名称
⑧xpath(重点):xpath表达式
a.格式: driver.find_element(By.XPATH, "xpath表达式")
b.复制绝对定位
c.编写 xpath 表达式

 

from selenium import webdriver
from selenium.webdriver.common.by import By
def web_locate():
    # 首先需要实例化driver对象,Chrome一定要加括号
    driver = webdriver.Chrome()
    # 打开一个网页
    driver.get("https://vip.ceshiren.com/#/ui_study/frame")
    # 1.ID定位,第一个参数传递定位方式,第二个参数传递定位元素,调用这个方法的返回值为WebElement
    web_element = driver.find_element(By.ID, "locate_id")
    print(web_element)
    # 2.NAME定位,如果没有报错,证明元素找到了
    # 如果报错no such element,代表元素定位可能出现错误
    # driver.find_element(By.NAME, "locate123")   # 错误示例
    driver.find_element(By.NAME, "locate")
    # 3.CSS选择器定位
    driver.find_element(By.CSS_SELECTOR, "#locate_id > a > span")
    # 4.xpath表达式定位
    driver.find_element(By.XPATH, '//*[@id="locate_id"]/a/span')  # 表达式里有双引号,建议用单引号
    # 5.Link text,通过链接文本的方式,(1)元素一定是a标签;(2)输入的元素为标签内的文本
    driver.find_element(By.LINK_TEXT, "元素定位")   # 通常会带一个点击的操作,在最后面添加.click(),可加可不加


if __name__ == '__main__':
    web_locate()

五、强制等待与隐式等待

1、为什么要添加等待

避免页面未渲染完成后操作,导致的报错

from selenium import webdriver
from selenium.webdriver.common.by import By

def wait_sleep():
    """
    如果直接执行,不添加任何等待,可能会报错
    """
    driver = webdriver.Chrome()
    driver.get("https://vip.ceshiren.com/")
    # 不加等待,可能会因为网速等原因产生报错
    # 报错:no such element: Unable to locate element
    # 原因:页面未加载完成,就去查找元素,此时这个元素还未加载出来
    driver.find_element(By.XPATH, "//*[text()='个人中心']")

if __name__ == '__main__':
    wait_sleep()

2、强制(直接)等待

(1)解决方案:在报错的元素操作之前添加等待
(2)原理:强制等待,线程休眠一定时间。time.sleep(3)

import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

def wait_sleep():
    """
    如果直接执行,不添加任何等待,可能会报错
    """
    driver = webdriver.Chrome()
    driver.get("https://vip.ceshiren.com/")
    # 不加等待,可能会因为网速等原因产生报错
    # 报错:no such element: Unable to locate element
    # 原因:页面未加载完成,就去查找元素,此时这个元素还未加载出来
    # 1.强制等待,让页面渲染完成,在报错的元素操作之前添加等待,没有报错,就证明是页面渲染速度导致,有报错则是其他问题,如定位错误等
    time.sleep(3)
    driver.find_element(By.XPATH, "//*[text()='个人中心']")

if __name__ == '__main__':
    wait_sleep()

3、隐式等待

(1)问题:难以确定元素加载的具体等待时间。
(2)解决方案:针对于寻找元素的这个动作,使用隐式等待添加配置。
(3)原理:设置一个等待时间,轮询查找(默认0.5秒)元素是否出现,如果没出现就抛出异常

import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

def wait_sleep():
    """
    如果直接执行,不添加任何等待,可能会报错
    """
    driver = webdriver.Chrome()
    driver.get("https://vip.ceshiren.com/")
    # 不加等待,可能会因为网速等原因产生报错
    # 报错:no such element: Unable to locate element
    # 原因:页面未加载完成,就去查找元素,此时这个元素还未加载出来
    # 1.强制等待,让页面渲染完成,在报错的元素操作之前添加等待,没有报错,就证明是页面渲染速度导致,有报错则是其他问题,如定位错误等
    # time.sleep(3)
    # 强制等待的问题:(1)不确定页面加载时间,可能会因为等待时间过长,而影响用例的执行效率;(2)不确定页面加载时间,可能会因为等待时间过短,而导致代码依然报错
    # 2.隐式等待
    # 设置一个最长的等待时间,轮询查找(默认0.5秒)元素是否出现,如果没出现就抛出异常
    # 注意:(1)在代码一开始运行时就添加隐式等待的配置,隐式等待是全局生效,即在所有find_element动作之前添加该配置即可;(2)隐式等待只能解决元素查找问题,不能解决元素交互问题
    driver.implicitly_wait(3)
    driver.find_element(By.XPATH, "//*[text()='个人中心']")
    # driver.implicitly_wait(5)    # 修改隐式等待的配置
    driver.find_element(By.XPATH, "//*[text()='题库']")

4、隐式等待无法解决的问题

(1)问题:元素可以找到,使用点击等操作,出现报错
(2)原因:
①页面元素加载是异步加载过程,通常html会先加载完成,js、css其后
②元素存在与否是由HTML决定,元素的交互是由css或者js决定
③隐式等待只关注元素能不能找到,不关注元素能否点击或者进行其他的交互
(3)解决方案:使用显式等待

5、显式等待基本使用(初级)

(1)示例: WebDriverWait(driver实例, 最长等待时间, 轮询时间).until(结束条件)
(2)原理:在最长等待时间内,轮询,是否满足结束条件
注意:在初级时期,先关注使用

import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait


"""显示等待"""


def wait_show():
    driver = webdriver.Chrome()
    driver.get("https://vip.ceshiren.com/#/ui_study/frame")
    # driver.implicitly_wait(3)  # 问题:元素可以找到,但是点击效果没有触发
    # 显示等待,第一个参数是driver,第二个参数是最长等待时间,轮询时间可加可不加,util方法内需要结合expected_conditions或者自己封装的方法进行使用
    # expected_conditions的参数传入都是一个元组,即多一层小括号
    WebDriverWait(driver, 10).until(expected_conditions.element_to_be_clickable((By.ID, "success_btn")))
    driver.find_element(By.ID, "success_btn").click()   # 添加点击操作.click(),点击"消息提示"
    time.sleep(5)


if __name__ == '__main__':
    wait_show()

6、总结

(1)直接等待:
1)使用方式:time.sleep(等待时间))
2)原理:强制线程等待
3)适用场景:调试代码,临时性添加
(2)隐式等待:
1)使用方式:driver.implicitly_wait(等待时间)
2)原理:在时间范围内,轮询查找元素
3)适用场景:解决找不到元素问题,无法解决交互问题
(3)显式等待:
1)使用方式:WebDriverWait(driver实例, 最长等待时间, 轮询时间).until(结束条件)
2)原理:设定特定的等待条件,轮询操作
3)适用场景:解决特定条件下的等待问题,比如点击等交互性行为

六、常见控件交互方法

1、元素操作

点击,输入,清空

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

def element_interaction():
    """
    元素操作:点击、输入、清空
    :return:
    """
    # 1.实例化driver对象
    driver = webdriver.Chrome()
    # 2.打开一个网页
    driver.get("https://www.sogou.com/")
    # 3.定位到输入框进行输入操作,.send_keys()
    driver.find_element(By.ID, "query").send_keys("霍格沃滋测试开发")
    # 强制等待2秒
    time.sleep(2)
    # 4.对输入框进行清空操作.clear()
    driver.find_element(By.ID, "query").clear()
    time.sleep(2)
    # 5.再次输入
    driver.find_element(By.ID, "query").send_keys("霍格沃滋测试开发学社")
    time.sleep(2)
    # 6.点击搜索.click()
    driver.find_element(By.ID, "stb").click()
    time.sleep(2)

if __name__ == '__main__':
    element_interaction()

2、获取元素属性信息

①原因:定位到元素后,获取元素的文本信息,属性信息等
②目的:根据这些信息进行断言或者调试

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

# 获取元素属性
def element_get_attr():
    # 1.实例化driver对象
    driver = webdriver.Chrome()
    # 2.打开网页
    driver.get("https://vip.ceshiren.com/#/ui_study/iframe")
    # 3.定位一个元素
    web_element = driver.find_element(By.ID, "locate_id")
    # 4.打印这个元素信息
    # 使用debug方式查看,断点打在想看的对象的下一行
    # print(web_element)
    # 5.获取元素的文本信息,不是每个元素都有文本信息的
    print(web_element.text)
    # 6.获取元素的属性信息,如 id="locate_id", title="xxx"
    res = web_element.get_attribute("class")
    print(res)

if __name__ == '__main__':
    # 获取元素属性
    element_get_attr()

 

3、获取元素属性信息的方法

①获取元素文本
②获取元素的属性(html的属性值)

# 获取元素文本
driver.find_element(By.ID, "id").text
# 获取这个元素的name属性的值
driver.find_element(By.ID, "id").get_attribute("name")

七、自动化测试定位策略

1、定位方式

 2、通用 Web 定位方式

【定位策略:描述】
①class name:通过 class 属性定位元素
②css selector:通过匹配 css selector 定位元素
③id:通过 id 属性匹配元素
④name:通过 name 属性定位元素
⑤link text:通过 <a>text</a> 标签中间的 text 文本定位元素
⑥partial link text:通过 <a>text</a> 标签中间的 text 文本的部分内容定位元素
⑦tag name:通过 tag 名称定位元素
⑧xpath:通过 xpath 表达式匹配元素

3、选择定位器通用原则

1)与研发约定的属性优先(class属性: [name='locate'])
2)身份属性 id,name(web 定位)
3)复杂场景使用组合定位:
①xpath,css
②属性动态变化(id,text)
③重复元素属性(id,text,class)
④父子定位(子定位父)
4)js定位

4、Web 弹框定位

1)场景:web 页面 alert 弹框
2)解决:web 需要使用 driver.switchTo().alert() 处理

5、下拉框/日期控件定位

1)场景:
<input>标签组合的下拉框无法定位
<input>标签组合的日期控件无法定位
2)解决:面对这些元素,我们可以引入JS注入技术来解决问题。

6、文件上传定位

1)场景:input 标签文件上传
2)解决:input 标签直接使用 send_keys()方法

 

L1实战–测试人论坛搜索功能自动化测试

from selenium import webdriver
from selenium.webdriver.common.by import By

class TestCeshiren01:

    def setup(self):
        """
        前提条件:进入测试人论坛的搜索页面
        :return:
        """
        # 实例化driver对象
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(3)  # 隐式等待
        # 打开被测地址
        self.driver.get("https://ceshiren.com/search?expanded=true")

    def teardown(self):
        # 每一次用例结束之后都会关闭chromedriver进程,也会关闭浏览器
        self.driver.quit()

    def test_search01(self):
        """
        测试步骤:1.输入搜索关键词;2.点击搜索按钮
        :return:
        """
        # 定位搜索框,并输入搜索内容,如果是动态id,使用css
        self.driver.find_element(By.CSS_SELECTOR, "[placeholder=搜索]").send_keys("appium")
        # 定位到搜索按钮,并点击搜索
        self.driver.find_element(By.CSS_SELECTOR, ".search-cta").click()
        # 断言=预期结果与实际结果对比的结果
        # 定位实际结果,即为获取搜索结果列表的标题内容
        web_element = self.driver.find_element(By.CSS_SELECTOR, ".topic-title")
        # 获取文本类的实际结果断言,appium关键字是否在获取的实际结果文本之中
        assert "appium" in web_element.text

八、高级定位-css

1、css 选择器概念

①css 选择器有自己的语法规则和表达式
②css 定位通常分为绝对定位和相对定位
③和Xpath一起常用于UI自动化测试中的元素定位

2、css 相对定位使用场景

①支持web产品
②支持app端的webview

3、css 相对定位的优点

①可维护性更强
②语法更加简洁
③解决各种复杂的定位场景

4、css 定位的调试方法

1)进入浏览器的console
2)输入:$("css表达式")或者$$("css表达式"),如果表达式里有双引号,外面要单引号,如果表达式用的是单引号,外面就要用双引号,外双内单,外单内双

5、css基础语法

【类型:表达式】
①标签:标签名
$("div") # 获取所有的div标签
②类:.表示为class属性值,如果类里面有多个值(单词),就不能直接复制,将空格改为.即可
$(".logo-big")等同于$("[class='logo-big']")
③ID:#表示为id属性值
$("#site-logo")等同于$("[id='site-logo']")
④属性:[属性名='属性值']
$("[alt='测试人社区']")

 

6、css关系定位

【类型:格式】
①并集:元素,元素
$("#main,#ember4")

 

②邻近兄弟(了解即可):元素+元素
$("#ember39+#ember40")
③兄弟(了解即可):元素1~元素2
$("#ember39~#ember41")
④父子:元素>元素
$("#main>#ember4")
⑤后代:元素 元素
$("#main #skip-link") 

7、css 顺序关系

 【类型:格式】
①父子关系+顺序:元素 元素(父亲有多个孩子,找到第一个孩子)
表达式://:nth-child(n)
$("#ember15>:nth-child(3)") ( #ember15为父亲,:nth-child(3)为第三个孩子)
②父子关系+标签类型+顺序:元素 元素(父亲的孩子有多个类型,想找其中一个类型里面的第一个孩子)
表达式://:nth-of-type(n)
$("#ember15>div:nth-of-type(1)")( #ember15为父亲,:nth-of-type(1)为第三个类型div的第一个孩子)

css表达式定位

 

九、高级定位-xpath

1、xpath 基本概念

①XPath 是一门在 XML 文档中查找信息的语言
②XPath 使用路径表达式在 XML 文档中进行导航
③XPath 的应用非常广泛
④XPath 可以应用在UI自动化测试

2、xpath 使用场景

①web自动化测试
②app自动化测试

3、xpath 相对定位的优点

①可维护性更强
②语法更加简洁
③相比于css可以支持更多的方式

4、xpath 定位的调试方法

1)浏览器-console
$x("xpath表达式")
2)浏览器-elements
ctrl+f 输入xpath或者css

5、xpath 基础语法(包含关系)

【表达式:结果】
①/ :从该节点的子元素选取
②// :从该节点的子孙元素选取
③* :通配符
$x("//*[@id='ember61']"),匹配所有的[@id='ember61']
$x("//tr[@id='ember61']"),匹配tr标签下面的[@id='ember61']
④nodename :选取此节点的所有子节点,相当于标签名
⑤.. :选取当前节点的父节点,
$x("//*[@id='ember61']/.."),寻找ember61的父节点
写法二:$x("//tr[@id='ember61']"),在某个标签下寻找
⑥@ :选取属性

# 整个页面
$x("/")

 

页面中的所有的子元素,匹配/下面的所有节点,相当于是html
$x("/*")

 整个页面中的所有元素
$x("//*")

查找页面上面所有的div标签节点,标签不等于属性
$x("//div")

 

查找id属性为ember61的节点,*先匹配所有节点,再匹配某个属性
$x('//*[@id="ember61"]')

查找ember61节点的父节点
$x('//*[@id="ember61"]/..')
$x("//*[@id='ember61']/../..") # 再往上寻找父节点

6、xpath 顺序关系(索引)

xpath通过索引直接获取对应元素

# 查找tbody下的所有tr
$x("//tbody//tr")
# 查找tbody下的第一个tr,下标从1开始
$x("//tbody//tr[1]")
# 获取所有的tr,注意这里的tr并不是都是同一个父节点
$x("//tr")
# 获取每个不同父节点下面的第一个tr
$x("//tr[1]")

 

7、xpath 高级用法

①[last()]: 选取最后一个
$x("//tr[last()]") # 获取每一个父节点下的最后一个tr
$x("//tbody//tr[last()]") # 获取tbody下面的最后一个tr

②[@属性名='属性值' and @属性名='属性值']: 与关系
$x("//*[@class='topic-list-item category-bu-161-category unseen-topic ember-view' and @id='ember44']")

③[@属性名='属性值' or @属性名='属性值']: 或关系
$x("//*[@class='topic-list-item category-bu-161-category unseen-topic ember-view' or @id='ember44']")

④[text()='文本信息']: 根据文本信息定位
$x("//*[text()='赏金任务']") # text不是属性,不需要加@,是一个方法,加括号

⑤[contains(text(),'文本信息')]: 根据文本信息包含定位;也可以contains(@id或@name等)
$x("//*[contains(text(),'赏金')]")
$x("//*[contains(@id,'site')]")

⑥注意:所有的表达式需要和[]结合

十、显式等待高级使用

1、显式等待原理

①在代码中定义等待一定条件发生后再进一步执行代码
②在最长等待时间内循环执行结束条件的函数,结合③一起查看
③源码:WebDriverWait(driver 实例, 最长等待时间, 轮询时间).until(结束条件函数)

2、常见 expected_conditions

1)类型:element:
示例方法:
element_to_be_clickable()visibility_of_element_located()
说明:针对于元素,比如判断元素是否可以点击,或者元素是否可见
2)类型:url
示例方法:url_contains()
说明:针对于 url
3)类型:title
示例方法:title_is()
说明:针对于标题
4)类型:frame
示例方法:frame_to_be_available_and_switch_to_it(locator)
说明:针对于 frame
5)类型:alert
示例方法:alert_is_present()
说明:针对于弹窗

3、封装等待条件

1)官方的 excepted_conditions 不可能覆盖所有场景,如有些按钮需要点击两次或多次才会有反应
2)定制封装条件会更加灵活、可控

import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support.wait import WebDriverWait


class TestWebdriverWait:

    driver = webdriver.Chrome()

    driver.maximize_window()
    driver.implicitly_wait(5)
    driver.get("https://vip.ceshiren.com/#/ui_study/iframe")
    def teardown(self):
        self.driver.quit()
    def test_webdriver_wait(self):
        # 解决的问题:有的按钮点击一次没有反应,可能要点击多次,比如企业微信的添加成员
        # 解决的方案:一直点击按钮,直到下个页面出现,封装成显式等待的一个条件
        def muliti_click(button_element,until_ele):
            # 函数封装
            def inner(driver):
                # 封装点击方法
                driver.find_element(By.XPATH,button_element).click()
                return driver.find_element(By.XPATH,until_ele)
            return inner
        time.sleep(5)
        # 在限制时间内会一直点击按钮,直到展示弹框
        WebDriverWait(self.driver,10).until(muliti_click("//*[text()='点击两次响应']","//*[text()='该弹框点击两次后才会弹出']"))
        time.sleep(5)

十一、高级控件交互方法

1、使用场景

 

1)复制粘贴
对应事件:键盘事件
2)拖动元素到某个位置
对应事件:鼠标事件
3)鼠标悬停
对应事件:鼠标事件
4)滚动到某个元素
对应事件:滚动事件
5)使用触控笔点击
对应事件:触控笔事件(了解即可)

2、ActionChains解析

1)实例化类ActionChains,参数为driver实例。
2)中间可以有多个操作。
3).perform()代表确定执行。

3、键盘事件

1)按下、释放键盘键位
2)结合send_keys回车
3)键盘事件-使用shift实现大写
ActionChains(self.driver): 实例化ActionChains类
key_down(Keys.SHIFT, ele): 按下shift键实现大写
send_keys("selenium"): 输入大写的selenium
perform(): 确认执行

from selenium import webdriver
from selenium.webdriver import ActionChains, Keys
from selenium.webdriver.common.by import By

class TestKeyBoard:

    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(3)  # 隐式等待3秒

    def teardown_class(self):
        self.driver.quit()

    def test_shift(self):
        """
	     键盘事件-使用shift实现大写
        1.访问https://ceshiren.com/官方网站
        2.点击搜索按钮
        3.输入搜索的内容,输入的同时按着shift键
        :return:
        """
        self.driver.get("https://ceshiren.com/")
        # 点击搜索按钮
        self.driver.find_element(By.ID, "search-button").click()
        # 目标元素,即为输入框
        ele = self.driver.find_element(By.ID, "search-term")
        # key_down代表按下某个键位,.send_keys输入内容,.perform()确认执行以上操作
        ActionChains(self.driver).key_down(Keys.SHIFT, ele).send_keys("selenium").perform()

4)键盘事件-输入后回车
①直接输入回车: 元素.send_keys(Keys.ENTER)
②使用ActionChains: key_down(Keys.ENTER)

from selenium import webdriver
from selenium.webdriver import ActionChains, Keys
from selenium.webdriver.common.by import By

class TestKeyBoard:

    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(3)  # 隐式等待3秒

    def teardown_class(self):
        self.driver.quit()

    def test_enter(self):
	 """键盘事件-输入后回车"""
        self.driver.get("https://www.sogou.com/")
        self.driver.find_element(By.ID, "query").send_keys("python语言")
        # 第一种回车方式,直接输入回车
        self.driver.find_element(By.ID, "query").send_keys(Keys.ENTER)
        # 第二种使用ActionChains,要记得加.perform()
        ActionChains(self.driver).key_down(Keys.ENTER).perform()

5)键盘事件-复制粘贴
①多系统兼容
mac 的复制按钮为 COMMAND
windows 的复制按钮为 CONTROL
②左箭头:Keys.ARROW_LEFT
③按下COMMAND或者CONTROL: key_down(cmd_ctrl)
④按下剪切与粘贴按钮: send_keys("xvvvvv")

import sys
from selenium import webdriver
from selenium.webdriver import ActionChains, Keys
from selenium.webdriver.common.by import By

class TestKeyBoard:
    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(3)  # 隐式等待3秒

    def teardown_class(self):
        self.driver.quit()

    def test_copy_and_paste(self):
        """键盘事件-复制粘贴"""
        # 打开网页
        self.driver.get("https://ceshiren.com/")
        # 点击搜索按钮
        self.driver.find_element(By.ID, "search-button").click()
        # 目标元素,即为输入框
        ele = self.driver.find_element(By.ID, "search-term")
        # 判断操作系统是否为Mac(darwin),是mac返回command键位,windows返回control键位
        command_control = Keys.COMMAND if sys.platform == "darwin" else Keys.CONTROL
        # .key_down(Keys.SHIFT, ele)按下shift键,.send_keys("selenium")输入大写selenium,
        # .key_down(Keys.ARROW_LEFT)按下左箭头,.key_down(command_control)按下command或control键位,
        # .send_keys("xvv"),x表示为剪切,多少个v表示复制多少次,.key_up(command_control)表示松开command或control键位,.perform()执行
        ActionChains(self.driver)\
            .key_down(Keys.SHIFT, ele)\
            .send_keys("selenium@")\
            .key_down(Keys.ARROW_LEFT)\
            .key_down(command_control).send_keys("xvvvvvv").key_up(command_control)\
            .perform()

4、鼠标事件

1)双击
double_click(元素对象): 双击元素
2)拖动元素
drag_and_drop(起始元素对象, 结束元素对象): 拖动并放开元素
3)指定位置(悬浮)
move_to_element(元素对象): 移动到某个元素

import time
from selenium import webdriver
from selenium.webdriver import ActionChains, Keys
from selenium.webdriver.common.by import By

class TestKeyBoard:

    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(3)  # 隐式等待3秒

    def teardown_class(self):
        self.driver.quit()

    def test_double_click(self):
        """鼠标事件-双击"""
        # 打开网页
        self.driver.get("https://vip.ceshiren.com/#/ui_study/iframe")
        # 获取点击按钮
        ele = self.driver.find_element(By.ID, "primary_btn")
        # .double_click(ele)调用双击方法,传入双击元素,.perform()执行
        ActionChains(self.driver).double_click(ele).perform()

    def test_drag_and_drop(self):
        """鼠标事件-拖动元素"""
        # 打开网页
        self.driver.get("https://vip.ceshiren.com/#/ui_study/action_chains")
        # 获取需要拖动的元素,即起始元素的位置
        item_left = self.driver.find_element(By.CSS_SELECTOR, '#item1')   # #表示id标签
        # 获取目标元素的位置
        item_right = self.driver.find_element(By.CSS_SELECTOR, '#item3')  # #表示id标签
        # 实现拖拽操作,.drag_and_drop(item_left, item_right)
        ActionChains(self.driver).drag_and_drop(item_left, item_right).perform()

    def test_hover(self):
        """鼠标事件-悬浮"""
        self.driver.get("https://vip.ceshiren.com/#/ui_study/action_chains2")
        # 获取下拉框位置
        ele = self.driver.find_element(By.CSS_SELECTOR, ".menu")
        # 鼠标悬浮在下拉框
        ActionChains(self.driver).move_to_element(ele).perform()
        # 选择下拉选项
        self.driver.find_element(By.XPATH, "//*[contains(text(),'测开班')]").click()
        time.sleep(3)

5、滚轮/滚动操作

1)滚动到元素
scroll_to_element(WebElement对象):滚动到某个元素
2)根据坐标滚动
scroll_by_amount(横坐标, 纵坐标)
3)注意: selenium 版本需要在 4.2 之后

import time
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By

class TestScroll:

    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(10)  # 隐式等待

    def teardown_class(self):
        self.driver.quit()

    def test_scoll_to_element(self):
        """滚轮/滚动操作-滚动到元素"""
        # 打开网页
        self.driver.get("https://ceshiren.com/")
        # 获取页面底部某个标题
        ele = self.driver.find_element(By.XPATH, "//*[text()='接口测试上线标准']")
        # 页面滚动操作,找到"接口测试上线标准",selenium版本需要在 4.2 之后
        ActionChains(self.driver).scroll_to_element(ele).perform()
        time.sleep(5)

    def test_scroll_to_xy(self):
        """滚轮/滚动操作-根据坐标滚动"""
        self.driver.get("https://ceshiren.com/")
        # 坐标滚动方式,.scroll_by_amount(0, 10000)纵向滚动
        ActionChains(self.driver).scroll_by_amount(0, 3000).perform()
        time.sleep(3)

十二、网页 frame 与多窗口处理

1、selenium⾥⾯如何处理多窗⼜场景

1) 多个窗口识别
2)多个窗口之间切换

2、selenium⾥⾯如何处理frame

1)多个frame识别
2)多个frame之间切换

3、多窗口处理

1)点击某些链接,会重新打开⼀个窗口,对于这种情况,想在新页⾯上操作,就得先切换窗口了。
2)获取窗口的唯⼀标识⽤句柄表⽰,所以只需要切换句柄,就可以在多个页⾯灵活操作了。

4、多窗口处理流程

1)先获取到当前的窗口句柄(driver.current_window_handle)
2)再获取到所有的窗口句柄(driver.window_handles)
3.)判断是否是想要操作的窗口,如果是,就可以对窗口进⾏操作,如果不是,
跳转到另外⼀个窗口,对另⼀个窗口进⾏操作 (driver.switch_to_window)

"""base.py文件,Base类有前置和后置操作"""
from selenium import webdriver

# 前置和后置操作,提供测试用例调用
class Base(object):
    # 前置操作
    def setup(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(5)  # 隐式等待

    # 后置操作
    def teardown(self):
        self.driver.quit()


"""测试用例文件test_window.py"""
import time
from selenium.webdriver.common.by import By
from webui.window_frame.base import Base


# 继承Base类,Base类里有前置和后置操作
class TestWindows(Base):
    def test_window(self):
        self.driver.get("https://www.baidu.com/")
        # 找到登录按钮,并点击
        self.driver.find_element(By.XPATH, "//*[@id='s-top-loginbtn']").click()
        print(self.driver.current_window_handle)   # 点击登录之后,打印当前窗口
        # 点击弹窗里的立即注册按钮
        self.driver.find_element(By.XPATH, "//*[text()='立即注册']").click()
        # # 点击立即注册之后,打印当前窗口
        # print(self.driver.current_window_handle)
        # # 打印所有窗口的名字,列表形式,会有注册页面的窗口,
        # print(self.driver.window_handles)
        # 获取所有窗口列表,赋给window
        window = self.driver.window_handles
        # window[-1]获取注册页面的窗口,并切换到注册页面的窗口
        self.driver.switch_to.window(window[-1])
        # 注册页面,输入用户名username
        self.driver.find_element(By.ID, "TANGRAM__PSP_4__userName").send_keys("username")
        time.sleep(3)
        # 切换回最初登录页面的窗口
        self.driver.switch_to.window(window[0])
        # # 登录页面,点击短信登录
        # self.driver.find_element(By.XPATH, "//*[text()='短信登录']").click()
        # 获取用户名输入框,并输入用户名
        self.driver.find_element(By.ID, "TANGRAM__PSP_11__userName").send_keys("login_username")
        # 获取密码输入框,并输入密码
        self.driver.find_element(By.ID, "TANGRAM__PSP_11__password").send_keys("login_password")
        # 点击登录
        self.driver.find_element(By.ID, "TANGRAM__PSP_11__submit").click()
        time.sleep(6)

5、frame处理

1)frame介绍
①在web⾃动化中,如果⼀个元素定位不到,那么很⼤可能是在iframe中。
②什么是frame?
a. frame是html中的框架,在html中,所谓的框架就是可以在同⼀个浏览器中显⽰不⽌⼀个页⾯。
b. 基于html的框架,又分为垂直框架和⽔平框架(cols,rows)
③Frame 分类
a. frame标签包含frameset、frame、iframe三种,
b. frameset和普通的标签⼀样,不会影响正常的定位,可以使⽤index、id、name 、webelement任意种⽅式定位frame。
c. ⽽frame与iframe对selenium定位⽽⾔是⼀样的。selenium有⼀组⽅法对frame进⾏操作

2)多frame切换
①frame存在两种:⼀种是嵌套的,⼀种是未嵌套的
②切换frame
❖ driver.switch_to.frame() # 根据元素id或者index切换切换frame
❖ driver.switch_to.default_content() # 切换到默认frame
❖ driver.switch_to.parent_frame() # 切换到⽗级frame

3)frame未嵌套
①处理未嵌套的iframe
❖ driver.switch_to_frame(“frame 的 id”)
❖ driver.switch_to_frame(“frame - index”) frame⽆ID的时候依据索引来处理,索引从0开始 driver.switch_to_frame(0)

4)frame嵌套
②处理嵌套的iframe
❖ 对于嵌套的先进⼊到iframe的⽗节点,再进到⼦节点,然后可以对⼦节点⾥⾯的对象进⾏处理和操作
❖ driver.switch_to.frame(“⽗节点”)
❖ driver.switch_to.frame(“⼦节点”)

import time
from selenium.webdriver.common.by import By
from webui.window_frame.base import Base

# 继承Base类,Base类里有前置和后置操作
class TestFrame(Base):
    def test_frame(self):
        self.driver.get("https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable")
        # 切换到frame中,根据frame的元素id
        self.driver.switch_to.frame("iframeResult")  # 写法一,常用
        # self.driver.switch_to_frame("iframeResult")  # 写法二
        # 获取"请拖拽我!"的属性,获取文本内容
        print(self.driver.find_element(By.ID, "draggable").text)

        # 写法一:切回网页,即不在frame页面,即切回frame父节点
        # self.driver.switch_to.parent_frame()
        # 写法二:切换到默认frame节点,即刚打开的地址节点
        self.driver.switch_to.default_content()
        # 获取"点击运行"按钮,并点击
        self.driver.find_element(By.ID, "submitBTN").click()
        time.sleep(5)

 

十三、文件上传弹框处理

1、⽂件上传

1)input标签可以直接使⽤send_keys(⽂件地址)上传⽂件
2) ⽤法:
el = driver.find_element_by_id('上传按钮id')
el.send_keys(”⽂件路径+⽂件名")

import time
from selenium.webdriver.common.by import By
from webui.window_frame.base import Base

# 继承Base类,Base类里有前置和后置操作
class TestFile(Base):
    def test_file_upload(self):
        self.driver.get("https://www.baidu.com/")
        # 找到百度的相机图标并点击
        self.driver.find_element(By.XPATH, "//*[@class='soutu-btn']").click()
        # 定位到选择文件,并选择文件
        self.driver.find_element(By.XPATH, "//input[@class='upload-pic']").send_keys("/Users/jiyu/PycharmProjects/pythonProject/webui/img/12.png")
        time.sleep(5)

2、chrome 开启 debug 模式

1)有时候登录⽅式⽐较繁琐,需要动态⼿机密码,⼆维码登录之类的。⾃动话实现⽐较⿇烦。⼿⼯登录后,
不想让selenium启动⼀个新浏览器。可以使⽤chrome的debug⽅式来执⾏测试。
2)启动chrome的时候需要先退出所有chrome进程。使⽤ps aux|grep chrome|grep -v 'grep'查看是否有
chrome进程存在。确保没有chrome进程被启动过。
3)正常启动chrome的debug模式
①# 默认macOS系统
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debuggingport=9222
②# Windows下找到chrome.exe位置执⾏下⾯的命令
chrome.exe --remote-debugging-port=9222
4)启动后的提⽰信息,代表chrome运⾏正常,不要关闭⾃动打开的chrome窗口。

3、弹框处理机制

1)在页⾯操作中有时会遇到JavaScript所⽣成的alert、confirm以及prompt弹框,可以使⽤switch_to.alert()⽅法定位到。然后使⽤text/accept/dismiss/send_keys等⽅法进⾏操作。
2)操作alert常⽤的⽅法:
①switch_to.alert():获取当前页⾯上的警告框。
②text:返回alert/confirm/prompt 中的⽂字信息。
③accept():接受现有警告框。
④dismiss():解散现有警告框。
⑤send_keys(keysToSend):发送⽂本⾄警告框。keysToSend:将⽂本发送⾄警告框。

 

import time
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from webui.window_frame.base import Base

class TestAlert(Base):
    def test_alert(self):
        """
        ❖ 打开⽹页 
https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable
        ❖ 操作窗⼜右侧页⾯, 将元素1拖拽到元素2
        ❖ 这时候会有⼀个alert弹框,点击弹框中的’确定’
        ❖ 然后再按’点击运⾏’
        ❖ 关闭⽹页
        :return:
        """
        self.driver.get("https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable")
        # 切换到frame中,根据frame的元素id
        self.driver.switch_to.frame("iframeResult")
        # 获取需要拖动的元素,即起始元素的位置
        a1 = self.driver.find_element(By.CSS_SELECTOR, '#draggable')
        # 获取目标元素的位置
        a2 = self.driver.find_element(By.CSS_SELECTOR, '#droppable')
        # 实现拖拽操作,.drag_and_drop(a1, a2):将a1拖拽到a2上,.perform()执行
        action = ActionChains(self.driver)
        action.drag_and_drop(a1, a2).perform()
        time.sleep(2)  # 强制等待2秒,查看效果
        # 切换到alert页面,点击alert的确认按钮.accept()
        self.driver.switch_to.alert.accept()
        # 退出alert页面,返回到默认frame节点
        self.driver.switch_to.default_content()
        # 获取"点击运行"按钮,并点击
        self.driver.find_element(By.ID, 'submitBTN').click()
        time.sleep(3)

十四、自动化关键数据记录

1、什么是关键数据

①代码的执行日志
②代码执行的截图
③page source(页面源代码)

2、记录关键数据的作用

1)日志:
①记录代码的执行记录,方便复现场景
②可以作为bug依据
2)截图
①断言失败或成功截图
②异常截图达到丰富报告的作用
③可以作为bug依据
3)page source
①协助排查报错时元素当时是否存在页面上

3、行为日志记录

1)日志配置
2)脚本日志级别
①debug记录步骤信息
②info记录关键信息,比如断言等

#日志配置文件log_utils.py
# 日志配置
import logging
# 创建logger实例
logger = logging.getLogger('simple_example')
# 设置日志级别
logger.setLevel(logging.DEBUG)
# 流处理器
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# 日志打印格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 添加格式配置
ch.setFormatter(formatter)
# 添加日志配置
logger.addHandler(ch)
# 测试用例脚本文件test_data_record.py
# 日志与脚本结合
from selenium import webdriver
from selenium.webdriver.common.by import By
from webui.data_record.log_utils import logger
from webui.window_frame.base import Base

# 继承Base类,Base类里有前置和后置操作
class TestDataRecord(Base):
    """"""
    def test_data_record(self):
        search_content = "python语言"
        # 打开网页
        self.driver.get("https://www.sogou.com/")
        # 定位输入框,输入内容
        self.driver.find_element(By.ID, "query").send_keys(search_content)
        # 加入日志,步骤信息较多且琐碎,使用debug
        logger.debug(f"搜索的信息为:{search_content}")
        # 点击搜索
        self.driver.find_element(By.ID, "stb").click()
        # 获取搜索结果列表的标题,对应测试结果的实际结果
        search_result = self.driver.find_element(By.CSS_SELECTOR, "em")  # 多个em的情况下,默认拿第一个
        # 添加日志,断言使用info,级别高于debug,断言实际结果与预期结果是否一致
        logger.info(f"实际结果为:{search_result.text},预期结果为:{search_content}")
        # 断言,search_result.text 获取搜索结果的文本信息,预期结果search_content
        assert search_result.text == search_content

4、步骤截图记录

1)save_screenshot(截图路径+名称)
2)记录关键页面
①断言页面
②重要的业务场景页面
③容易出错的页面

from selenium.webdriver.common.by import By
from webui.data_record.log_utils import logger
from webui.window_frame.base import Base

# 继承Base类,Base类里有前置和后置操作
class TestDataRecord(Base):
    def test_screen_shot_data_record(self):
        search_content01 = "python语言"
        # 打开网页
        self.driver.get("https://www.sogou.com/")
        # 定位输入框,输入内容
        self.driver.find_element(By.ID, "query").send_keys(search_content01)
        # 加入日志,步骤信息较多且琐碎,使用debug
        logger.debug(f"搜索的信息为:{search_content01}")
        # 点击搜索
        self.driver.find_element(By.ID, "stb").click()
        # 获取搜索结果列表的标题,对应测试结果的实际结果
        search_result01 = self.driver.find_element(By.CSS_SELECTOR, "em")  # 多个em的情况下,默认拿第一个
        # 添加日志,断言使用info,级别高于debug,断言实际结果与预期结果是否一致
        logger.info(f"实际结果为:{search_result01.text},预期结果为:{search_content01}")
        # 截图记录,双重保障
        self.driver.save_screenshot("search_result01.png")
        # 断言,search_result01.text 获取搜索结果的文本信息,预期结果search_content01
        assert search_result01.text == search_content01

5、page source记录

1)使用page_source属性获取页面源码
2)在调试过程中,如果有找不到元素的错误可以保存当时的page_source调试代码

from selenium.webdriver.common.by import By
from webui.window_frame.base import Base

# 继承Base类,Base类里有前置和后置操作
class TestDataRecord(Base):
    def test_page_source_data_record(self):
        """获取page_source主要用于调试,可以不需要断言等其余信息"""
        # 现象:产生了no such element的错误
        # 方案:在报错的代码行之前打印page_source,确认定位的元素没有问题
        content = "python语言"
        # 打开网页
        self.driver.get("https://www.sogou.com/")
        # # 定位输入框,输入内容
        # self.driver.find_element(By.ID, "query").send_keys(content)
        # 获取page_source,即获取的是这个网页的源代码
        # logger.debug(self.driver.page_source)  # 可以使用debug查看输出的内容
        # 将获取的page_source写入到record.html
        with open("record.html", "w", encoding="utf-8") as f:
            f.write(self.driver.page_source)
        # 错误定位输入框
        self.driver.find_element(By.ID, "query1").send_keys(content)

 

 

最后:下面是配套学习资料,对于做【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!【100%无套路免费领取】

软件测试面试小程序

被百万人刷爆的软件测试题库!!!谁用谁知道!!!全网最全面试刷题小程序,手机就可以刷题,地铁上公交上,卷起来!

涵盖以下这些面试题板块:

1、软件测试基础理论 ,2、web,app,接口功能测试 ,3、网络 ,4、数据库 ,5、linux

6、web,app,接口自动化 ,7、性能测试 ,8、编程基础,9、hr面试题 ,10、开放性测试题,11、安全测试,12、计算机基础

  全套资料获取方式:点击下方小卡片自行领取即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码小怡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值