python+selenium

一.selenium+python环境配置

1.1selenium 介绍

selenium是一个web的自动化测试工具
与QTP相比优点:
1.免费
2.小巧,对于不同语言它只是一个包而已。
3.支持多种语言例如:C、java、ruby、python、或都是C#
4.支持多平台:Windows、linux、MAX,支持多浏览器ie、ff、safari、opera、chrome
5.支持分布式用例的执行,可以把测试用例分布到不同测试机器执行,相当于分发机的功能
官方文档:
https://selenium-python.readthedocs.io/index.html
https://seleniumhq.github.io/selenium/docs/api/py/api.html

1.2selenium+Python环境配置

1.安装selenium
Win:pip install selenium
Mac:pip3 install selenium
2.安装webdriver
各大浏览器webdriver地址可以参见:selenium文档
firefox:firefox地址
chrome:chrome地址
ie:ie地址
**注:**webdriver需要和对应的浏览器版本以及selenium版本对应
3.webdriver安装路径
Win:复制webdriver到python安装目录下
Mac:复制webdriver到/user/local/bin目录下

二.元素定位及浏览器基本操作

2.1 启动浏览器

2.1.1 普通方式启动

启动Chrome浏览器:

from selenium impot webdriver
browser = webdriver.Chrome()
browser.get('http://www.baidu.com/')

启动Firefox浏览器:

from selenim import webdriver
browser = webdriver.Firefox()
browser.get('http://www.baidu.com/')

启动IE浏览器:

from selenium import webdriver
browser = webdriver.IE()
browser.get('http://www.baidu.com/')

2.1.2 Headless方式启动

Headless Chrome 是Chrome浏览器的无界面形态,可以再不打开浏览器的前提下,使用所有Chrome支持的特性运行你的程序。相比于现代浏览器,Headless Chrome更加方便测试web应用,获得网站的截图,做爬虫抓取信息等。相比于较早的PhanyomJS,SlimerJS等,Headless Chrome则更加贴近浏览器环境。

Headless Chrome对于Chrome版本要求
官方文档介绍,mac和linux环境要求chrome版本是59+,而windows版本的chrome要求是60+,同时chromedriver要求2.30+版本

from selenium import webdriver
from selenium.webdriver.commom.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_condition as EC
from selenium.webdriver.commom.action_chains import ActionChains
from selenium.webdriver.commom.keys import keys

#使用headless无界面浏览器模式
chrome_options = webdriver.ChromeOptions()
#增加无界面选项
chrome_options.add_argument('--headless--') 
#如果不加这个选项,有时定位会出现问题 
chrome_options.add-argument('--disable-gpu')

#启动浏览器,获取网页源代码
browser = webdriver.Chrome(chrome_options=chrome_options)
mainUrl = 'https://www.tabao.com/'
browser.get(mainUrl)
print(f"browser text = {browser.page_source}")
browser.quit()

2.1.3 加载配置启动浏览器

Selenium操作浏览器是不加载任何配置的,下面是关于加载Chrome配置的方法:
用Chrome地址栏输入chrome://version/,查看自己的“个人资料路径”,然后在浏览器启动时,调用这个配置文件,代码如下:

#coding=utf-8
from selenium import webdriver
option = webdriver.ChromeOptons()
option.add_argument('--user-data-dir=C:\Users\Administrator\AppData\Local\Google\Chrome\User Data')#设置成用户自己的数据目录
driver=webdriver.Chrome(chrome_options=option)

j加载Firfox配置的方法有些不同:
打开Firefox点右上角设置>?(帮助)>故障排除信息>显示文件夹,打开后把路径复制下来就可以了

#coding=utf-8
from selenium import webdriver
#配置文件地址
profile_directory = r'C:\Users\xxx\AppData\Roaming\Mozilla\Firefox\Profiles\1x41j9of.default'
#加载配置文件
profile = webdriver.FirefoxProfile(profile_directory)
#启动浏览器配置
driver = webdriver.Firefox(profile)

2.2 元素定位

对象的定位应该是自动化测试的核心,想要操作一个对象,首先应该识别这个对象。一个对象如一个人一样,他会有各种特征(属性),比如我们可以通过一个人的身份证号,姓名等找到这个人。那么对象也有类似的属性,我们可以通过这个属性找到这个对象。
webdriver 提供了一系列的对象定位方法,常用的有以下几种:

  • id定位:find_element_by_id()
  • name定位:find_element_by_name()
  • class定位:find_element_by_class_name()
  • link定位:find_element_by_link_text
  • partial link定位:find_element_by_partial_link_text()
  • tag定位:find_element_by_tag_name()
  • xpath定位:find_element_by_xpath()
  • css定位:find_element_by_css_selector()`
  • List item
#coding=utf-8
from selenim import webdriver
browser = webdriver.Chrome()
browser.get('http://www.baidu.com')
####百度输入框的定位方式####
#通过id定位方式
browser.find_element_by_id('kw').send_keys('selenium')
#通过name方式定位
browser.find_element_by_name('wd').send_keys('selenium')
#通过tag name方式定位
browser.find_element_by_tag_name('input').send_keys('selenium')
#通过class name方式定位
browser.find_element_by_class_name('s_ipt').send_keys('selenium')
#通过css方式定位
browser.find_element_by_css_selector('#kw').send_keys('selenium')
#通过xpath方式定位
browser.find_element_by_xpath('//input[@id='kw']').send_keys('selenium')
########
browser.find_element_by_id('su').click()
time.sleep(2)
browser.quit()

2.2.1 class含有空格时解决方法

在实际进行元素定位时,经常发现class name是有多个class组合的复合类,中间以空格隔开。如果直接进行定位会出现报错,可以通过一下方式处理:

  • class属性唯一但是有空格,选择空格两边唯一的那一个
  • 若空格隔开的class不唯一可以通过索引进行定位self.driver.find_elements_by_class_name('table-dragColumn')[0]click()
  • 通过css方法进行定位(空格以‘.’)代替
#前面加(.)空格地方用点(.)来代替
self.driver.find_element_by_css_selector('.dtb-style-1.table-dragColumns').click()
#包含整个类
self.driver.find_element_by_css_selector('class=''dtb-style-1 table-dragColumns ')

参考代码:

#coding:utf-8
from selenium import webdriver
driver = webdriver.Firefox()
driver.get('http://mail.126.com')
drier.implicitly_wait(20)

driver.switch_to.frame('x-URS-iframe')
#方法一:取单个class属性
driver.find_element_by_class_name('dlemail').send_keys('yoyo')
driver.find_element_by_class_name('dlpwd').send_keys('12333')

#方法二:定位一组取下标定位(下策)
driver.find_elements_by_css_selector('j-inputtext')[0].send_keys('yoyo')
driver.find_elements_by_css_selector('j-inputtext')[1].send_keys('123')

#方法三:css定位
driver.find_elements_by_css_selector('.j-inputtext.dlemail').send_keys('yoyo')
driver.find_elements_by_css_selector('.j-inputtext.dlpwd').send_keys('123')

#方法四:取单个class属性也是可以的
driver.find_elements_by_css_selector('.dlemail').send_keys('yoyo')
driver.find_elements_by_css_selector('.dlpwd').send_keys('123')

#方法五:直接包含空格的css属性定位大法
driver.find_elements_by_css_selector("[class='j-inputtext dlemail'").send_keys('yoyo')

2.3 selenium 三种等待方式

为保证脚本运行的稳定性,需要脚本中添加等待时间

2.3.1 强制等待

sleep(xx),需要引入‘time’模块,不管浏览器是否加载完成,程序都得等待3秒,3秒后继续执行下面的代码,作为调试很有用,但不建议总用这样的等待方式,影响程序执行速度。

#-*-coding:utf-8 -*-
from selenium import webdriver
import time
driver = webdriver.Firefox()
driver.get('http://baidu.com')
#强制等待3秒再执行下一步
time.sleep(3)
#获取当前网页url
print(driver.current_url)
driver.quit()

2.3.2 隐形等待

通过添加implicitly_wait()方法就可以方便的实现智能等待;implicitly_wait(30)的用法应该比time.sleep()更智能,后者只能选择一个固定的时间的等待,前者可以在一个时间范围内智能等待

#-*- coding = utf-8 -*-
from selenium import webdriver

driver = webdriver.Firefox()
#隐性等待,最长时间30秒
driver.implicitly_wait(30)
driver.get('http://baidu.com')

print(driver.current_url)
driver.quit()

隐性等待设置了一个最长等待时间,如果在规定时间内i网页加载完成,则执行下一步,否则一直等到时间截止,然后执行下一步。注意这里有一个弊端,那就是程序会一直等待整个页面加载完成,也就是一般情况下,你看到浏览器标签栏那个小圈不再转,才会执行下一步,但有时候页面想要的元素早就加载完成了,但是因为个别js之类的东西特别慢,我仍得等到页面全部完成才能执行下一步,我想等我要的元素出来之后就下一步怎么办?selenium提供了另一种方式显性等待。
注意:隐性等待对整个driver的周期都起作用,所以只要设置一次即可,我曾看到有人把隐性等待当成sleep在用,走到哪都来一下…

2.3.3 显性等待

WebDriverWait,配和该类的until()和until_not()方法,就能够根据判断条件而进行灵活地等待了。它主要的意思就是:程序每隔xx秒看一眼,如果条件成立了,则执行下一步,否则继续等待,直到超过设置的最长时间,然后抛出TimeoutException。
wait模块的WebDriverWait类是显性等待类,先看下它有哪些参数与方法:

selenium.webdriver.support.wait.WebDriverWait()
 
 **init**
 - driver:传入WebDriver实例,即我们上例中的driver
 - timeout:超时间,等待的最长时间(同时要考虑隐性等待时间)
 - poll_frequency:调用until或until_not中的方法的间隔时间,默认是0.5- ignored_exceptions:忽略的异常,如果在调用until或until_not的过程中抛出这个元组中的异常,则不中断代码,继续等待,如果抛出的是这个元祖外的异常,则中断代码,抛出异常,默认只有NoSuchElementException。

**until**

 1. method:在等待时间,每隔一段时间(_init_中的poll_frequency)调用这个传入的方法,直到返回值不是False
 2. message:如果超时,抛出TimeoutException,将message传入异常

**until_not**

 1. 与until相反,until是当某元素出现或什么条件成立则继续执行
 2. until_not是当某元素消失或什么条件不成立则继续执行,参数也相同,不再赘述。

调用以上方法如下:
WebDriverWait(driver,超时时长,调用频率,忽略异常).until(可执行方法,超时时返回的信息)
**注意**
until或until_not中的可执行方法method参数,很多人传入了WebElement对象,如下:
WebDriverWait(driver,10).until(driver.find_element_by_id('kw'))#错误

这是错误的用法,这里的参数一定要是可以调用的,即这个对象一定有call()方法,否则会抛出异常:
TypeError:'xxx' object is not callable

在这里,你可以用selenium提供的exception_conditions模块中的各种条件,也可以用WebElement的
is_displayed()#是否展示、
is_enabled()#是否启用
is_selected()#是否选择
或者用自己封装的方法都可以
#coding=utf-8
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
base_url = 'http://www.baidu.com'
driver = webdriver.Firefox()
driver.implicitly_wait(5)
'''隐式等待和显示等待都存在,超时事件取二者中较大的'''
locator = (By.ID,'kw')

driver.get(base_url)

'''判断title,返回布尔值'''
WebDriverWait(driver,10).until(EC.title_is(u"百度一下,你就知道"))

'''判断title,返回布尔值'''
WebDriverWait(driver,10).until(EC.title_contains(u"百度一下"))

'''判断某个元素是否被加到dom树里,并不代表该元素一定可见,如果定位到就返回WebElement'''
WebDriverWait(driver,10).until(EC.presence_of_element_located((By.ID,'kw')))

'''判断某个元素是否被添加到dom里并且可见,可见代表元素可显示并且宽和高都大于0'''
WebDriverWait(driver,10).until(EC.visibility_of_element_located((By.ID,'su')))

'''判断元素是否可见,如果可见就返回这个元素'''
WebDriverWait(driver,10).until(EC.visibility_of(driver.find_element(by=By.ID,value='kw')))

'''判断是否至少有1个元素存在于dom树中,如果定位到就返回列表'''
WebDriverWait(driver,10).until(EC.presence_of_all_elements_located((By.CSS_SELECTOR,'.mnav')))

'''判断是否至少有一个元素在页面中可见,如果定位到就返回列表'''
WebDriverWait(driver,10).until(EC.visibility_of_any_elements_located((By.CSS_SELECTOR,'.mnav')))

'''判断指定的元素中是否包含了预期的字符串,返回布尔值'''
WebDriverWait(driver,10).until(EC.text_to_be_present_in_element((By.XPATH,"//*[@id='u1']/a[8]"),u'设置'))

'''判断指定元素的属性值中是否包含了预期的字符串,返回布尔值'''
WebDriverWait(driver,10).until(EC.text_to_be_present_in_element_value((By.CSS_SELECTOR,'#su'),u'百度一下'))

'''判断该frame是否可以switch进去,如果可以的话,返回True并且switch进去,否则返回False'''
#注意这里并没有一个frame可以切换进去
#WebDriverWait(driver,10).until(EC.frame_to_be_available_and_switch_to_it(locator))

'''判断某个元素在是否存在于dom或不可见,如果可见返回False,不可见返回这个元素'''
#注意#swfEveryCookieWrap在此页面中是一个隐藏的元素
WebDriverWait(driver,10).until(EC.invisibility_of_element_located((By.CSS_SELECTOR,'#swfEveryCookieWrap')))

'''判断某个元素中是否可见并且是enable的,代表可点击'''
WebDriverWait(driver,10).until(EC.element_to_be_clickable((By.XPATH,"//*[@id='u1']/a[8]"))).click()
driver.find_element_by_xpath("//*[@id='wrapper']/div[6]/a[1]").click()
#WebDriverWait(driver,10).until(EC.element_to_be_clickable((By.XPATH,"//*[@id='wrapper']/div[6]/a[1]"))).click()

'''等待某个元素从dom树中移除'''
#这里没有找到合适的例子
#WebDriverWait(driver,10).until(EC.staleness_of(driver.find_element(By.ID,'su')))

'''判断某个元素是否被选中了,一般用在下拉列表'''
WebDriverWait(driver,10).until(EC.element_to_be_selected(driver.find_element(By.XPATH,"//*[@id='nr']/option[1]")))

'''判断某个元素的选中状态是否符合预期'''
WebDriverWait(driver,10).until(EC.element_located_selection_state_to_be((By.XPATH,"//*[@id='nr']/option[1]"),True))
driver.find_element_by_xpath(".//*[@id='gxszButton']/a[1]").click()

'''判断页面上是否存在alert,如果有就切换到alert并返回alert的内容'''
instance = WebDriverWait(driver,10).until(EC.alert_is_present())
print instance.text
instance.accept()
driver.close()

2.4 浏览器操作

2.4.1 浏览器最大化、最小化

浏览器最大化显示

browser.maximize_window()

将浏览器最小化显示

browser.minimize_window()

2.4.2 浏览器设置窗口大小

设置浏览器宽480、高800显示

browser.set_window_size(480,800)

2.4.3 浏览器前进后退

前进

browser.forword()

后退

browser.back()

2.5 操作测试对象

一般来说,webdriver中比较常用的操作对象的方法有下面几个:

  • click——点击对象
  • send_keys——在对象模拟键盘输入
  • clear——清除对象的内容,如果可以的话
  • submit——提交对象的内容,如果可以的话
  • text——用于获取元素的文本信息

2.6 键盘事件

要想调用键盘按键操作需要引入keys包:

from selenium.webdriver.common.keys import Keys通过 send_keys()调用按键:
send_keys(Keys.TAB) # TAB
send_keys(Keys.ENTER) # 回车

参考代码:

#coding=utf-8
from selenium import webdriver
#需要引入keys包
from selenium.webdriver.common.keys import Keys
import os,time

driver = webdriver.Firefox()
driver.get('http://passport.kuaibo.com/login/?referrer=http%3A%2F%2Fwebcloud .kuaibo.com%2F')

time.sleep(3)
#浏览器全屏显示
driver.maximize_windows

driver.find_element_by_id("user_name").clear() 
driver.find_element_by_id("user_name").send_keys("fnngj")

#tab 的定位相相于清除了密码框的默认提示信息,等同上面的 clear() 
driver.find_element_by_id("user_name").send_keys(Keys.TAB) 
time.sleep(3) 
driver.find_element_by_id("user_pwd").send_keys("123456")

#通过定位密码框,enter(回车)来代替登陆按钮
driver.find_element_by_id("user_pwd").send_keys(Keys.ENTER)

#也可定位登陆按钮,通过 enter(回车)代替 click() 
driver.find_element_by_id("login").send_keys(Keys.ENTER) 
time.sleep(3)

driver.quit()

键盘组合键的用法:


 1. #ctrl+a 全选输入框内容
 2. driver.find_element_by_id("kw").send_keys(Keys.CONTROL,'a')


 1. #ctrl+x 剪切输入框内容 
 2. driver.find_element_by_id("kw").send_keys(Keys.CONTROL,'x')

2.7 鼠标事件

鼠标事件一般包括鼠标右键,双击,拖动,移动鼠标到某个元素等等。
需要引入ActionChains类
引入方法:

from selenium.webdriver.common.action_chains import ActionChains

 1. ActionChains 常用方法:
 2. perform()执行所有ActionChains 中储存的行为;
 3. context_cllick() 右击;
 4. double_click() 双击;
 5. drag_and_drop() 拖动:
 6. move_to_element() 鼠标悬停。

鼠标双击示例:

 1. #定位元素的原位置
 2. element = driver.find_element_by_name('source')
 3. #定位元素要移动到目标的位置
 4. target = driver.find_element_by_name('target')
 5. #执行元素的移动操作
 6. ActionChains(driver).drag_and_drop(element,target).perform()

2.8 多层框架/层级定位

定位元素过程中经常会遇到找不到元素的问题,出现该问题一般都是以下因素导致:

  • 元素定位方法不对
  • 页面存在iframe或内嵌窗口
  • 页面超时
    webdriver 提供了一个switch_to_frame方法,可以很轻松来解决这个问题。

 1. #先找到ifrome1(id = f1)
 2. browser.switch_to_frame('f1')

同样的,如果是内嵌窗口:
browser.switch_to_window('f1')

2.9 Expected Conditions解析

Expected Conditions的使用场景有2种:

  • 直接在断言中使用
  • 与WebDriverWait配合使用,动态等待页面上元素出现或者消失
    相关方法:
 1. title_is:判断当前页面的title是否精确等于预期
 2. title_contains: 判断当前页面的title是否包含预期字符串
 3. presence_of_element_located:判断某个元素是否被加到了dom树里,并不代表该元素一定可见
 4. visibility_of_element_located:判断某个元素是否可见.可见代表元素非隐藏,并且元素的宽和高都不等于0
 5. visibility_of:跟上面的方法做一样的事情,只是上面的方法要传入locator,这个方法直接传定位到的element就好了
 6. presence_of_all_elements_located:判断是否至少有1个元素存在于dom树中。举个例子,如果页面上有n个元素的class都是'column-md-3',那么只要有1个元素存在,这个方法就返回True
 7. text_to_be_present_in_element:判断某个元素中的text是否包含了预期的字符串
 8. text_to_be_present_in_element_value:判断某个元素中的value属性是否包含了预期的字符串
 9. frame_to_be_available_and_switch_to_it:判断该frame是否可以switch进去,如果可以的话,返回True并且switch进去,否则返回False
 10. invisibility_of_element_located:判断某个元素中是否不存在于dom树或不可见
 11. element_to_be_clickable:判断某个元素中是否可见并且是enable的,这样的话才叫clickable
 12. staleness_of:等某个元素从dom树中移除,注意,这个方法也是返回TrueFalse
 13. element_to_be_selected:判断某个元素是否被选中了,一般用在下拉列表
 14. element_selection_state_to_be:判断某个元素的选中状态是否符合预期
 15. element_located_selection_state_to_be:跟上面的方法作用一样,只是上面的方法传入定位到的element,而这个方法传入locator
 16. alert_is_present:判断页面上是否存在alert,这是个老问题,很多同学会问到

示例:
判断title;title_is();title_contains()

  1. 首先导入expected_conditions模块
  2. 由于这个模块名称比较长,所以为了后续的调用方便,重新命名为EC了(有点像数据库里面多表查询时候重命名)
  3. 打开博客首页后判断title,返回结果是True或False
# coding:utf-8
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Firefox()
driver.get("http://baidu.com")
# 判断title完全等于
title = EC.title_is(u'百度')
print title(driver)

# 判断title包含
title1 = EC.title_contains(u'百度')
print title1(driver)

# 另外一种写法
r1 = EC.title_is(u'百度')(driver)
r2 = EC.title_contains(u'百度')(driver)
print r1
print r2

三.selenium速查表

3.1 Python Webdriver Exception速查表

异常描述
WebDriverException所有webdriver异常的基类,当有异常且不属于下列异常时抛出
InvalidSwitchToTargetException所有webdriver异常的基类,当有异常且不属于下列异常时抛出
NoSuchFrameException下面两个异常的父类,当要switch的目标不存在时抛出
NoSuchWindowException当你想要用switch_to.frame()切入某个不存在的frame时抛出
NoSuchElementException当你想要用switch_to.window()切入某个不存在的window时抛出
NoSuchAttributeException元素不存在,一般由find_element与find_elements抛出
StaleElementReferenceException指定的元素过时了,不在现在的DOM树里了,可能是被删除了或者是页面或iframe刷新了
UnexpectedAlertPresentException出现了意料之外的alert,阻碍了指令的执行时抛出
NoAlertPresentException你想要获取alert,但实际没有alert出现时抛出
InvalidElementStateException下面两个异常的父类,当元素状态不能进行想要的操作时抛出
ElementNotVisibleException元素存在,但是不可见,不可以与之交互
ElementNotSelectableException当你想要选择一个不可被选择的元素时抛出
InvalidSelectorException一般当你xpath语法错误的时候抛出这个错
InvalidCookieDomainException当你想要在非当前url的域里添加cookie时抛出
UnableToSetCookieException当driver无法添加一个cookie时抛出
TimeoutException当一个指令在足够的时间内没有完成时抛出
MoveTargetOutOfBoundsExceptionactions的move操作时抛出,将目标移动出了window之外
UnexpectedTagNameException获取到的元素标签不符合要求时抛出,比如实例化Select,你传入了非select标签的元素时
ImeNotAvailableException输入法不支持的时候抛出,这里两个异常不常见,ime引擎据说是仅用于linux下对中文/日文支持的时候
ImeActivationFailedException激活输入法失败时抛出
ErrorInResponseException不常见,server端出错时可能会抛
RemoteDriverServerException不常见,好像是在某些情况下驱动启动浏览器失败的时候会报这个错

3.2Xpath&Css定位方法速查表

描述XpathCss
直接子元素//div/adiv > a
子元素或后代元素//div//adiv a
以id定位//div[@id=‘idValue’]//adiv#idValue a
以class定位//div[@class=‘classValue’]//adiv.classValue a
同级弟弟元素//ul/li[@class=‘first’]/following-ul>li.first + li
属性//form/input[@name=‘username’]form input[name=‘username’]
多个属性//input[@name=‘continue’ andinput[name=‘continue’][type='button
第4个子元素//ul[@id=‘list’]/li[4]ul#list li:nth-child(4)
第1个子元素//ul[@id=‘list’]/li[1]ul#list li:first-child
最后1个子元素//ul[@id=‘list’]/li[last()]ul#list li:last-child
属性包含某字段//div[contains(@title,‘Title’)]div[title*=“Title”]
属性以某字段开头//input[starts-with(@name,‘user’)]input[name^=“user”]
属性以某字段结尾//input[ends-with(@name,‘name’)]input[name$=“name”]
text中包含某字段//div[contains(text(), ‘text’)]无法定位
元素有某属性//div[@title]div[title]
父节点//div/…无法定位
同级哥哥节点//li/preceding-sibling::div[1]无法定位

Tips
这里介绍一款在线代码美化工具,在线访问地址:Vscode

另外如果使用Vscode的话,可以安装对应的插件进行快速在线美化

  • 打开Vscode,在插件栏输入:carbon-now-sh
  • 点击安装
  • 点击重新加载即可安装
  • 按快捷键ALT+CMD+A(win系统下使用:ALT+WIN+A)即可
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值