揭秘:自动化测试如何确保《博客系统》项目 UI 稳定运行(实操版)

本篇会加入个人的所谓鱼式疯言

❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言

而是理解过并总结出来通俗易懂的大白话,

小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.

🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人能接受我们这个概念 !!!

在这里插入图片描述

如果想亲测的小伙伴可以点击下面URL进行访问:
在这里插入图片描述
博客系统URL

引言

你是否曾为手动测试博客系统的登录、文章发布等UI功能感到枯燥又耗时?面对多浏览器兼容性、动态元素定位等问题,重复点击验证不仅低效,还容易遗漏潜在缺陷。本文将手把手带你用 Selenium + JDK + WebDriverManager 搭建一套「懒人友好」的自动化测试方案——无需手动配置浏览器驱动,无需复杂环境搭建,只需简单几行代码,即可让测试脚本自动验证博客系统的核心UI流程。从登录态保持到文章发布后的元素断言,揭秘如何用自动化守护你的博客体验,把时间留给更有价值的创作与优化 !

目录

  1. 设计自动化测试用例

  2. 环境搭建

  3. 公共模块功能

  4. 注册页面的自动化测试

  5. 登录页面的自动化测试

  6. 验证码页的自动化测试

  7. 列表页面的自动化测试

  8. 详情页面的自动化测试

  9. 编辑页面的自动化测试

一. 设计自动化测试用例

1. UI测试用例的思维导图

请添加图片描述

什么是UI测试:

UI测试全称: WebUI界面自动化测试

UI界面指的是用户界面, 也就是用户能够看到的界面,我们称之为 UI界面 ,也就是我们前端程序猿开发的前端界面!

那么 UI测试,就是基于一张张UI界面的维度进行测试

所以主要的界面有:

  1. 注册页面的自动化测试

  2. 登录页面的自动化测试

  3. 验证码页的自动化测试

  4. 列表页面的自动化测试

  5. 详情页面的自动化测试

  6. 编辑页面的自动化测试

为了让小伙伴们理解的更清楚一点,小编这里使用 幕布 这个app手戳了一张思维导图,方便小伙伴们查看:

二. 环境搭建

实现自动化测试脚本的环境主要有:

idea + JDK + selenium + webdrivermanager + 浏览器

ideaJDK 以及浏览器 就不用多说咯~ 想必小伙伴们都人手安排好了!

下面小编就手把手的带着小伙伴创建自动化项目开始,搭建环境,编写 自动化测试脚本

  1. 创建Maven项目

在这里插入图片描述

  1. 依赖导入

在这里插入图片描述

  1. 分类创建不同模块包名

在这里插入图片描述

自动化脚本分4个包管理:

  • common包 管理自动化的测试的 驱动程序屏幕截图 , 需要记录的url 等… 作用于所有的UI页面的测试中,并作为UI页面的父类。

  • main包 掌管自动化测试的主逻辑

  • pic包:存放屏幕截图的目录

  • tests : 每一个UI页面的具体的 自动化测试脚本的逻辑实现

一般而言, 对于测试的包和类,我们一般都是创建在 test 包下的Java包下, 小伙伴记住哦, 这是一种规范哦!

三. 通用模块功能

Utils实现公共模块URL

1. WebDriver的创建与配置

为了创建 WebDriver , 小编这里创建 getWebDriver方法Utils构造器 同时实现:

  • getWebDriver 实现:

    1. 首先 static 修饰 WebDriver 使用 单例模式 的方式,下载 Edge 浏览器的驱动
    1. 然后配置 --remote-allow-origins=* 这个命令,允许 所有来源访问浏览器的接口
    1. 之后获取 Edge驱动对象,并且加入上述配置
    1. 最后设置 隐式等待,设置为 3秒, 用于 处理大部分简单元素的加载
  • Utils 构造器 实现:

    1. 首先设置参数url , 从 getWebDriver 获取 WebDriver 对象, 设置好当前需要访问的页面url
    1. 最后添加 显示等待 对象 , 用于处理指定元素的条件加载

鱼式疯言

  • 相比小伙伴们肯定很疑惑吧,这里怎么对 WebDriver 使用 单例模式, 直接每次获取WebDriver 的时候,直接 new 不是也挺好的嘛? 小伙伴这样自然也不错。

    但是小编这里使用单例的设计模式,这里必然有小编的道理的,请容我细细道来

  1. 这样就可以避免频繁的对 WebDriver 进行 创建和销毁 , 这样的 减少开销的好处,何乐而不为呢?
  1. 对于 Utils 这个类而言,下面的代码实现我们是 作为UI自动化的父类,那么 WebDriver 这个对象,必然就可以成为大家共享的 全局的对象,可以直接就使用这个对象, 不用频繁的调用getWebDriver 这个方法, 就可以有效的 避免代码的冗余的方式 , 何乐而不为呢?
  1. 只使用唯一的WebDriver 对象, 那么就意味着: 配置只需要配一次,有益于方便管理即使日后需要修改配置的时候, 也能更好的维护。 何乐而不为呢?
  1. 一个 WebDriver 对象就会打开一个浏览器页面, 如果实例化话多个浏览器页面,那岂不是乱套了 , 所以为了操作更方便直观, 并且 一个 WebDriver 打开的浏览器页面能够更好的保证自动化测试的准确性。 何乐而不为呢?
  • 这里小编使用的 Edge 浏览器, 小伙伴们结合自己电脑的浏览器使用,大致和小编的实现流程差不多, 效果都是一样的哦~
    在这里插入图片描述

  • 上述 创建 WebDriver 中, 小编同时设置了 隐式等待和显式等待,为什么要设置这些等待方式?

主要的考虑的因数:

防止 程序运行太快,浏览器页面还未加载程序就已经运行到那个浏览器的那个页面

如果 浏览器还没加载出来 ,就会出现 即使有元素也会找不到的情况 !、

那么我们就需要让程序去等待合适的时间去让浏览器对应的页面加载出来,然后执行下面的代码。

这时就需要 设置不同的等待策略应对不同的使用场景

在自动化测试中,主要的等待方式有三种:

  1. 强制等待

Thread.sleep([时间]) , 这个等待是 设置多少时间,当前线程就需要阻塞多少毫秒

它是一种 简单粗暴的等待方式

对于一种固定的等待场景或者难以处理的页面,这种

等待是一种不错的选择哦~ 但是有可能导致时间过长,性能降低哦~

  1. 隐式等待

webDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(4));

隐式等待是为 所有元素统一设置等待的时间 ,会不断对 findElement() 进行轮询, 如果 寻找元素就进行下一步。这可是一次设置,就能作用于全局并且还能减少代码的冗余哦~

这可是使用可以 作用所有元素统一设置,或者整体的等待时间相差不大,用这个可是 “法宝”级的存在。

但是 它的不足就是不能作用于弹窗和导航栏相关的元素,并且不能灵活的单独设置特点的元素

  1. 显式等待
  • wait = new WebDriverWait(driver,Duration.ofSeconds(5)); -> 设置等待时间

  • wait.until(ExpectedConditions.alertIsPresent()); -> 设置等待条件

注意顺序,先设置等待时间,后设置等待条件

显式就更好和隐式等待相反,可以说是互补的存在, 显式等待可以作用于弹窗,可以灵活配置单独元素提供条件。常常配合 ExpectedConditions 的条件来使用

常用的条件了解即可,代码中都有体现):

在这里插入图片描述

也就是说强制等待会根据上面这些条件, 在规定的设置好的时间范围之内, 不满足就轮询等待满足就停止等待执行下面的代码

但如果 超过了设置好的时间范围, 还不满足上方条件,就会抛异常!


2. 屏幕截图的配置

为了方便在进行 UI自动化 的过程中,发现问题, selenium 提供一组屏幕截图的方法,可以在 异常情况下 记录 错误的页面方便观察

屏幕截图的实现

  1. 首先校验两种特殊情况 :
    - 如果 WebDriver 被释放了, 那么页面就不存在, 就 直接打印错误信息返回
    - 如果出现弹窗, 是 无法进行屏幕截图的, 得先处理弹窗,然后进行屏幕截图。
  1. 获取时间格式根据时间和传入参数标明不同的图片来设置图片文件名
  1. 最后创建文件目录, 如果文件目录不存在就创建目录, 然后从 getScreenshotAs (注意这个 TakesScreenshot 对象中的方法,需要强转后使用)这个方法中 获取文件, 并且拷贝到需要的目录文件中

鱼式疯言

之所以设置 时间 + 参数文件名的目的:

其实聪明的小伙伴们应该都想到了吧~

在这里插入图片描述
如上图:

小伙伴们可以思考一下, 在没有当 如果参数传入的相同都同一一个文件名就会导致文件之间就会相互覆盖, 只能查看当前覆盖后的最新文件, 而不能查看历史文件。

3. WebDriver的释放

WebDriver 对象 释放就直接调用 quit方法 即可释放

鱼式疯言

有小伙伴们可能还有听说过close 方法。 所以小伙伴可能会疑惑: close 方法也能释放WebDriver 对象资源, 可为啥偏偏要用 quit 方法呢?

其实啊, 小伙伴们有所不知, 虽然quit 和 close 都能关闭页面和释放系统资源。

但是 close 方法, 侧重关闭当前 URL 访问的页面, 如果出现 多个浏览器页面打开, 就会只能关闭当前访问的页面, 无法关闭全部的浏览器页面

quit 方法, 则是会关闭所有的 WebDriver浏览器页面, 也就是结束整个浏览器的会话, 并且把后台的进程也一并的杀死。 可以说是一网打尽!

所以总结一下使用场景:

  • 如果 需要关闭当前一个页面,当前进程(任务)中可能还需要再使用其他页面 , 小编推荐使用 close
  • 如果 不需要使用浏览器上的任何一个页面, 当前的进程(任务)已经全部完成了 , 那么quit 绝对是你的不二选择。

下面的时间, 就交给舞台交给每一个页面来展示吧~

tests 实现UI自动化URL

tests 包下, 围绕着:

注册页面,登录页面,验证码页面,列表页面, 详情页面,编辑页面

四. 注册页面的自动化测试

1. 测试用例的设计

在这里插入图片描述

用户注册模块,主要分为从 注册成功与否展开测试用例的设计~

2.实现流程

注册页面自动化测试URL

注册自动化的实现:

首先先继承 Util类,然后 设置需要访问的注册列表页的URL

然后主要分了三部分进行测试:

  • 页面是否加载成功 , 注册失败的情况注册成功的情况

<1>. 页面是否加载成功

  1. 分别获取 用户名密码重复密码 , 注册按钮css选择器 ,并 获取其文本
  1. 使用 断言分别判断文本内容是否达到预期内容

<2>. 注册失败

  • 这里小编就举例: 检查用户名存在,密码和重复密码都为空的情况用户名存在,密码和重复密码不为空 两种测试用例来实现~
  • 对于 检查用户名存在,密码和重复密码都为空的情况
  1. 首先, 分别获取 注册框的用户名,密码,重复密码元素,以及注册按钮。
  1. 其次在用户名中输入内容,就 直接点击注册按钮
  1. 最后由于是 前端拦截,这里看不到弹窗提示,但可以使用屏幕截图记录异常界面
  • 对于 用户名存在,密码和重复密码不为空
  1. 首先 清空前面输入的用户名,并重新输入用户名(已注册过的用户名),密码,重复密码(和密码相同)
  1. 之后点击注册按钮,如果等待弹窗出现,并断言验证弹窗提示信息。
  1. 点击弹窗确认之后,对该注册异常进行 ==屏幕截图记录 ==

<3>. 注册成功

  1. 首先还是获取注册框的元素(同上)
  1. 之后清空注册框的内容,输入合法的用户名(使用UUID配置多个用户名进行多组测试),密码和输入密码。点击注册。
  1. 接着 等待提示弹窗出现,断言验证弹窗提示信息
  1. 最后 点击确认弹窗之后,验证页面跳转页面是否为登录页面

鱼式疯言

补充说明

  1. xpathcssSelector

在上面的代码中, 其中最核心最基本的就是 定位页面页数

所以这块还不理解的小伙伴们要跟进小编的魔鬼步伐哦~

首先,小编这里说明一下哦, 我们一般遇到的是静态元素, 所以这里小编侧重讲解静态元素, 如果在下面的标签页面中遇到, 小编也会侧重提出讲解!

所以对于静态元素而已, 使用 xpathcssSelector 都可以定位到元素。

  • 定位元素
  1. 打开前端开发者页面
    快捷键:F12 或者 鼠标右击 + 检查
    在这里插入图片描述

在这里插入图片描述

  • 定位元素 复制 cssSelectorxpath

在这里插入图片描述

在这里插入图片描述

  • 根据 cssSelectorxpath查找元素
  1. 使用快捷键: ctrl + f

在这里插入图片描述

  1. 输入 cssSelectorxpath 查找元素

在这里插入图片描述

如上图, 在搜索框中输入需要查找的 xpathcssSelector定位到对应元素和元素个数

  • assert 断言

在自动化测试中, 我们常用 assert 这个关键字, 可用于根据某个条件是否成立,来判定预期是否达到想要的结果!

assert [条件语句] :如果 条件成立继续执行下面的程序,如果不处理,assert 就会 停止到这一步,并且打印错误信息。

例如: assert “注册成功!去登录->”.equals(alert.getText());

判定弹窗出现信息是否正确

如果弹窗信息为 注册成功!去登录-> 那么就 验证成功 ,测试通过!

如果弹窗信息不为 注册成功!去登录-> , 那么就 验证失败, 测试不通过,报错这一行!(如下图)

在这里插入图片描述

五. 登录页面的自动化测试

1. 测试用例的设计

在这里插入图片描述

用户登录页面: 主要分 登录成功,失败,以及跳转到注册模块 三个大方向展开测试用例的设计。

2.实现流程

登录自动化实现URL

首先先继承 Util类,然后 设置需要访问的注册列表页的URL

分三种情况: 页面是否加载登录失败登录成功

<1>. 页面是否加载

  1. 首先检查菜单元素:主页,写博客模块 是否存在。

  2. 最后检查登录框元素: 用户名,密码,登录按钮 是否存在。

<2>. 登录失败

  1. 首先,获取用户名,密码,登录按钮的元素对象
  1. 然后这里分别测试两种代表性的情况: 用户名不为空, 密码为空的情况用户名正确,密码错误的情况
  • 用户名不为空, 密码为空的情况
    - 首先 只对用户名输入框对象输入用户名 ,然后就点击 登录按钮
    - 之后等待弹窗出现,验证弹窗信息是否正确,下一步确认弹窗
    - 最后使用屏幕截图记录登录异常的页面
  • 用户名正确,密码错误的情况
    - 首先,先情况用户名输入框,密码框,保证输入前是没有数据干扰的
    - 然后,输入用户名和密码,进行点击
    - 等待弹窗出现,断言弹窗信息是否正确, 下一步确认弹窗
    - 最后屏幕进行屏幕截图
  1. 接着 验证未登录状态下菜单栏功能
  1. 测试 主页模块
  • 首先获取主页元素对象,点击 主页模块
  • 其次等待弹窗出现, 确认弹窗信息是否正确。
  • 最后确认弹窗之后,验证是否重新跳转回登录页面
  1. 测试写 博客模块
  • 首先获取 写博客 元素对象,并点击
  • 其次跳转到编辑页面, 获取 发布文章 按钮,并点击
  • 接着等待弹窗出现,并 验证弹窗信息是否正确
  • 点击 弹窗之后,等待校验是否回到登录页面

<3>. 登录成功

  1. 首先获取登录框的元素: 用户名密码登录按钮 元素对象、
  1. 其次对 用户名输入框,密码框进行清空
  1. 然后再 用户名输入框密码框 分别输入正确的用户名和密码,继而再 点击登录按钮
  1. 接着 等待弹窗出现assert 断言弹窗信息是否正确。
  1. 最后确认弹窗之后,等待验证码页的切换

六. 验证码页的自动化测试

1. 测试用例的设计

在这里插入图片描述

验证码的主逻辑: 还是 验证成功和失败 的两个方向来讨论设计。

2. 实现流程

验证码页面实现自动化测试URL

在分情况测试之前, 还是需要先继承 Utils 类 ,并且 重新设置当前验证码页面的Url

分两种情况: 验证成功,验证失败。

<1>. 验证失败

验证失败主要代表有: 输入为空验证码错误 的情况

  1. 首先获取 输入框和验证按钮的元素对象
  1. 之后点击验证按钮 (输入为空) ,等待弹窗出现,断言验证弹窗信息是否正确。
  1. 接着点击确认弹窗,并且 屏幕截图 记录该验证错误的截图
  1. 然后再 输入框输入错误验证码,并且点击验证按钮
  1. 之后等待弹窗出现,断言验证弹窗信息是否正确
  1. 点击确认弹窗, 屏幕截图 记录验证错误的截图。

上述虽然能测试验证失败的用例,对于验证码验证成功的自动化测试,由于是 随机字符串 ,所以测试起来比较困难,也不需要重点掌握,小编这里就不做示范了哦~

七. 列表页面的自动化测试

1. 测试用例的设计

在这里插入图片描述

分为登录状态和未登录状态下的不同的博客列表页面的响应

2.实现流程

实现列表页面自动化测试URL

一如既往的先继承 Utils 类, 并把列表的 URL 设置到 WebDriver

由于菜单栏是所有都 共有的模块 , 在登录页面已经演示过了, 这里小编就不赘述了, 下面的都是核心讲解咱们 登录状态下的列表页,详情页以及编辑页

<1>. 登录状态下的博客列表页

  1. 首先检查列表信息是否存在: 获取 文章标题,时间,正文,然后依次判空!
  1. 接着获取 查看全文 按钮,并且 判断按钮信息是否正确
  1. 之后获取左边页面的信息栏:获取 ==用户名,人员总数,以及文章总数,然后依次判空! == 并且 点击GitHub 链接,查看是否访问,查看之后返回列表页
  1. 然后验证菜单栏:主页按钮和写博客元素, 先点击 主页 元素,显式等待URL 是否包含列表页HTML验证是否跳转到列表页面
  1. 接着点击 写博客 元素, 显示等待URL 是否包含编辑页HTML , 验证是否跳转到编辑页面, 但是需要注意的是, 跳转成功之后需要重新设置回列表页面
  1. 跳转回列表页面,显示等待 查看全文 元素是否可点击
  1. 存在元素进行点击,显式等待详情页的出现, 并且需要保存当前的跳转到的详情页的URL, 目的记住当前详情页的ID ,为详情页的操作打好基础

鱼式疯言

  • 详情页URL 必须通过列表页来跳转得到, 否则很可能出现无法得到一个正确的博客详情页的情况, 就会进一步导致接下来的自动化测试异常

  • 并且博客详情页的URL ,统一交个 Utils 父类管理, 当详情页的测试类继承Utils 后, 就可以直接获取到对应的URL , 并 设置进WebDriver

八. 详情页面的自动化测试

1. 测试用例的设计

在这里插入图片描述

博客详情页还是以 登录状态 为维度展开进行测试用例的设计!

2. 实现流程

实现博客详情页自动化测试URL

在实现具体自动化脚本之前, 先继承 Utils 类,并且将 上方列表页保存在父类的 detailPageUrl 属性设置到 WebDriver 中进行页面访问~

在详情页中, 小编分了4种情况展开测试:

  • 详情页面是否加载成功

  • 更新模块是否有效

  • 取消删除是否有效

  • 确认删除是否有效

<1>. 详情页面是否加载成功

  1. 首先获取正文元素,并且 断言正文内容是否为空。

  2. 其次检查两个 编辑删除 元素是否存在,断言元素信息是否正确。

<2>. 更新模块是否有效

  1. 首先,回到详情页,先获取 编辑 元素,直接点击。
  1. 接着 强制等待更新页面的出现,获取 更新文章 元素,并且断言信息是否正确
  1. 然后点击更新文章,强制等待弹窗出现,并且断言弹窗信息是否正确。
  1. 最后确认弹窗之后, 强制等待列表页面的出现

<3>. 取消删除是否有效

  1. 首先,先 重新设置URL为详情页,确保是在详情页操作。
  1. 其次获取 删除 元素,并且点击
  1. 接着等待弹窗出现,并且断言弹窗信息是否正确。
  1. 最后 点击取消弹窗,断言是否还是详情页面

<4>. 确认删除是否有效

  1. 首先,先 重新设置URL为详情页,确保是在详情页操作。
  1. 其次 获取 删除 元素,并且点击
  1. 接着等待弹窗出现,并且断言弹窗信息是否正确~
  1. 然后 确认删除博客,等待删除成功的提示弹窗出现,并且断言提示信息是否正确~
  1. 最后 断言是否回到列表页

鱼式疯言

在详情页的自动化脚本中, 小伙伴们是否关注过获取文本的这两种不同方式:

  • updateClick.getAttribute("value")

在这里插入图片描述

如上图,上面这种方式是只能用于获取标签上的每种属性值中设置的文本

  • editEle.getText()

在这里插入图片描述

如上图,直接通过 getTest() 这种方式只能用于获取标签内夹着的文本信息

九. 编辑页面的自动化测试

1. 测试用例的设计

在这里插入图片描述

编辑页面 还是以 登录维度 展开测试用例的设计~

2. 实现流程

实现编辑页自动化测试URL

先继承 Utils 类, 并且将博客编辑页 URL 设置到 WebDriver

由于现在已经是在登录状态下了,所以小编的从三种情况考虑:

  1. 编辑页面是否加载成功

  2. 博客发布失败测试

  3. 博客发布成功测试

<1>. 编辑页面是否加载成功

  1. 首先,获取标题框,正文框,以及发布按钮,并且断言显示信息是否正文。

  2. 最后断言正文的初始文本是否加载正确

<2>. 博客发布失败测试

  • 先判断标题为空,正文不为空
  1. 首先获取 发布文章 按钮并点击

  2. 其次 强制等待弹窗出现 , 并且断言弹窗信息是否正确

  3. 最后 确认弹窗,屏幕截图记录发布异常图像

  • 最后判断标题和正文都为空
  1. 首先 获取正文元素
  1. 其次使用 Actions 类(下文细讲),进行鼠标和键盘同时操作正文,并删除
  1. 然后点击 发布文章强制等待弹窗出现, 并且断言弹窗信息是否正确~
  1. 最后 确认弹窗,屏幕截图记录发布异常图像 , 并且 断言当前URL还是编辑页面

<3>. 博客发布成功测试

  1. 首先重新设置WebDriver 的URL ,确保当前页面是编辑页面

  2. 其次获取 标题正文发布文章 等元素

  3. 对于 标题,可直接向标题输入内容 ,对于 正文,需要操作 Actions 类,对正文元素先进行删除,然后进一步的输入内容 。

  4. 点击 发布文章元素, 强制等待弹窗出现, 并且断言弹窗信息是否正确

  5. 确认弹窗,并且强制等待是否跳转到列表页面。

鱼式疯言

由于 Markdown 这个 轻量级标记语言 ,不是很支持 使用元素来操作正文的增删改查 , 所以这里需要考虑 用其他方式来对编辑页面正文 进行操作!

那么在 selenium 中提供了 一组 操作鼠标和键盘APIActions

 Actions action = new Actions(webDriver);
        Thread.sleep(100);
   action.click(contentEle)
           .keyDown(Keys.CONTROL)
           .sendKeys("a")
           .keyUp(Keys.CONTROL)
           .sendKeys(Keys.DELETE)
           .perform();

如上述代码:

  1. 首先实例化一个 Actions 对象, 并且 强制等待100 ms
  1. 然后可以模拟鼠标 click 正文元素, 并且按住键盘上的 ctrl , 接着 sendKeys 输入 a
  1. 接着 输入结束之后,松开 crtl , 这样就可以选中正文所有内容
  1. 最后 sendKeys 点击删除,并且perform执行即可。

本篇文章就到这里哦, 更多详情,小伙伴可以参照官网学习哦~

Actions 官网详解URL

如果觉得小编写的还不错的咱可支持 三连 下 (定有回访哦) , 不妥当的咱请评论区 指正

希望我的文章能给各位宝子们带来哪怕一点点的收获就是 小编创作 的最大 动力 💖 💖 💖

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

邂逅岁月

感谢干爹的超能力

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

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

打赏作者

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

抵扣说明:

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

余额充值