Selenium 2.0 WebDriver 使用指南

Selenium WebDriver


注意:我们正致力于完善帮助指南的每一个章节,虽然这个章节仍然存在需要完善的地方,不过我们坚信当前你看到的帮助信息是精确无误的,后续我们会提供更多的指导信息来完善帮助文档。


1.WebDriver介绍

Selenium 2.0最主要的新特性就是集成了WebDriver API。我们设计WebDriver的初衷是提供更加简单明了的接口来弥补Selenium-RC API的不足。在动态网页中,通常只会更新局部的html元素,WebDriver会很好的帮助用户快速定位这些元素。我们最终的目的是通过提供精心设计的面向对象API来解决现代高级网页中的测试难题。

2.WebDriver如何驱动浏览器?与Selenium-RC有什么区别?

不同类型的浏览器都会有原生的接口支持自动化操作,Selenium通过这些接口直接向浏览器发送指令。如何发送这些指令取决于你当前使用的浏览器类型,我们将在这一章节后面来详细介绍。

看上去WebDriver与之前Selenium-RC的实现方式类似,实际上两者之间存在着本质的区别。对于所有类型的浏览器Selenium-RC都是使用的同一种方法:当浏览器启动时,向其中注入javascript,从而使用这些js来驱动浏览器中的AUT(Application Under Test)。WebDriver并没有使用这种技术,它是通过调用浏览器原生的自动化API直接驱动浏览器。

3.WebDriver与Selenium Server

是否需要是用Selenium Server取决于你使用WebDriver的方式。以下两种情况不需要使用Selenium Server,WebDriver直接运行浏览器即可:1、testcases仅仅使用了Webdriver的API;2、浏览器和testcase在同一台PC上,而且testcases仅仅使用了Webdriver的API。

以下三种情况你需要结合Selenium Server来使用WebDriver:

1)使用Selenium-Grid管理集群环境(或者虚拟机)上的testcase;

2)需要调用非本机上的不同版本的浏览器;

3)未使用任何language binding(java/c#/python/ruby),且有意向使用HtmlUnitDriver。

4.配置Selenium-WebDriver工程

安装Selenium是指在开发环境上配置一个工程,然后可以在这个工程中用Selenium编写程序。如何配置取决于你使用的开发语言和编程环境。

使用Maven是配置一个Selenium 2.0 java工程最简单的方式。Maven会下载所有java bingdings以及所有相关的库( the Selenium 2.0 java client library)。通过使用pom.xml(maven配置文件)来新建工程,你可以根据自己的喜好将Maven工程导入IntelliJ IDEA或者Eclipse。

首先,创建一个文件夹存放Maven工程文件。然后,创建pom.xml,你可以使用text editor来编辑。鉴于已经有很多关于“如何在Maven工程中使用pom.xml”优秀的参考文献,这里将不再过多的讨论相关细节。下面给出一个示例,为你的工程也创建一个类似的文件。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>MySel20Proj</groupId>
        <artifactId>MySel20Proj</artifactId>
        <version>1.0</version>
        <dependencies>
            <dependency>
                <groupId>org.seleniumhq.selenium</groupId>
                <artifactId>selenium-java</artifactId>
                <version>2.38.0</version>
            </dependency>
            <dependency>
                <groupId>com.opera</groupId>
                <artifactId>operadriver</artifactId>
            </dependency>
        </dependencies>
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>com.opera</groupId>
                    <artifactId>operadriver</artifactId>
                    <version>1.5</version>
                    <exclusions>
                        <exclusion>
                            <groupId>org.seleniumhq.selenium</groupId>
                            <artifactId>selenium-remote-driver</artifactId>
                        </exclusion>
                    </exclusions>
                </dependency>
            </dependencies>
        </dependencyManagement>
</project>
请确认你使用的WebDriver是最新的当前版本。在这篇文档撰写时,上述示例给出的是最新的版本。在Selenium2.0发布不久WebDriver就有过频繁的更新。请在这个链接 Maven Download Page确认当前的版本,相应地修改你工程中的pon.xml。

现在,你可以通过dos界面使用CD命令进入工程所在文件夹,通过以下命令运行Maven。

mvn clean install

运行之后会自动下载Selenium及相关套件,并加载到你的工程中去。

最后,将你的工程导入到你偏好的IDE中。如果你对导入的过程不是很清楚,我们已经准备了操作指南。

Importing a maven project into IntelliJ IDEA.Importing a maven project into Eclipse

5.如何将自动化工程从Selenium1.0迁移到Selenium2.0

已经在Selenium1.0上构建测试工程的用户,我们为您提供了一份指导如何将已有的代码迁移到Selenium2.0。Selenium2.0的首席开发工程师Simon Stewart为此撰写了一片文章:Magrating From Selenium RC to Selenium WebDriver

6.Selenium-WebDriver API简介

WebDriver可以用来实现Web应用程序的自动化测试,特别适合于验证实际结果是否符合预期结果的场景。WebDriver旨在提供比Selenium1.0更加易用、友好的API,便于用户的探索和理解,从而使测试用例变得容易阅读和维护。WebDriver没有使用任何第三方测试框架,所以它可以很好与单元测试工具或者最古老的main函数结合使用。本章节将介绍如何使用WebDriver的API,帮助你慢慢开始了解WebDriver。如果你还没有新建一个Selenium工程,请先完成这个操作,在这个章节的上面有详细的描述。

当你创建完Selenium工程后,你会发现WebDriver和普通的第三方库一样是完全独立的,在你使用之前不需要启动任何额外的进程或者安装程序,相反如果你使用Selenium-RC需要先启动代理服务器。

注意:当你使用如下WebDriver时需要额外的步骤:Chrome Driver,Opera Driver,Android Driver,IPhone Driver。

现在你肯定跃跃欲试要写一些代码了。我们以一个简单的例子来开始第一段旅程:在Google上搜索“Cheese”,并打印出搜索结果网页的标题。

package org.openqa.selenium.example;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;

public class Selenium2Example  {
    public static void main(String[] args) {
        // 创建一个FirefoxDriver实例
        // 这个类依赖于接口而不是接口的实现
        WebDriver driver = new FirefoxDriver();

        // 使用get方法访问Google
        driver.get("http://www.google.com");
        // 使用下面这个方法也能够达到访问Google的目的
        // driver.navigate().to("http://www.google.com");

        // 找到html输入框的name
        WebElement element = driver.findElement(By.name("q"));

        // 输入要查找的内容
        element.sendKeys("Cheese!");

        // 提交表单,WebDriver会自动找到我们需要提交的元素所在的表单
        element.submit();

        // 打印网页的标题
        System.out.println("Page title is: " + driver.getTitle());
        
        // Google的搜索网页会通过JS动态渲染
        // 等待页面加载完毕,超时时间为10秒
        (new WebDriverWait(driver, 10)).until(new ExpectedCondition<Boolean>() {
            public Boolean apply(WebDriver d) {
                return d.getTitle().toLowerCase().startsWith("cheese!");
            }
        });

        // 控制台上将打印如下信息: "cheese! - Google Search"
        System.out.println("Page title is: " + driver.getTitle());
        
        // 关闭浏览器
        driver.quit();
    }
}
在本章节的接下来篇幅,我们将学习如何使用WebDriver操作你的浏览器,如何使用框架和窗口来测试Web网站。当然,我们将提供更加翔实的论述和举例。

7.Selenium-WebDriver API详解

7.1获取Web页面
我们第一件要做的事是通过WebDriver取得Web页面的控制权,一般情况下使用get方法
driver.get("http://www.google.com");
在某些情况下,比如操作系统和浏览器的穿插组合,WebDriver有可能不会等待Web页面加载完成,这种情况下WebDriver会返回错误或者直接运行下一步操作。为了保证程序的健壮性,你需要等待页面中某个元素加载完成后再进行下一步操作,请参考 Explicit and Implicit Waits

7.2定位UI元素
我们可以通过WebDriver实例或者WebElement类来定位UI元素。我们为每种编程语言都提供了两种方法:“Find Element”和“Find Elements”。第一种方法返回的一个WebElement,找不到则抛出异常。第二个方法返回一个WebElement链表(List),在找不到任何DOM元素的情况下会返回空的链表。
Find方法会使用类似探测器的类,类名叫做By。下面列举By的一些常用方法:

By ID
当我们定位一个UI 元素,这个是最有效也是最好的方法。不过这个方法不是万能的,有的前端开发在设计UI元素时会遗漏ID或者使用动态ID,这两种情况下都要避免使用这个方法。这时候使用获取class名称方法比By ID更合适。
示例:如何使用该方法定位元素
<div id="coolestWidgetEvah">...</div>
WebElement element = driver.findElement(By.id("coolestWidgetEvah"));


By Class Name

在这种场景下,我们引用DOM元素的属性。实际情况是很多元素都有一样的Class Name,因此找到多个有相同Class Name的元素,比找到第一个拥有这个Class Name的元素来的更重要。

示例:如何使用该方法定位元素

<div class="cheese"><span>Cheddar</span></div><div class="cheese"><span>Gouda</span></div>
List<WebElement> cheeses = driver.findElements(By.className("cheese"));

By Tag Name

DOM元素Tag的名称。

示例:如何使用该方法定位元素

<iframe src="..."></iframe>
WebElement frame = driver.findElement(By.tagName("iframe"));

By Name

找到与Name属性相同的Input元素。

示例:如何使用该方法定位元素

<input name="cheese" type="text"/>
WebElement cheese = driver.findElement(By.name("cheese"));

By Link Text

找到与Text属性精确匹配的超链接。

示例:如何使用该方法定位元素

<a href="http://www.google.com/search?q=cheese">cheese</a>
WebElement cheese = driver.findElement(By.linkText("cheese"));

By Partial Link Text

找到与Text属性模糊匹配的超链接。

示例:如何使用该方法定位元素
<a href="http://www.google.com/search?q=cheese">search for cheese</a>
WebElement cheese = driver.findElement(By.partialLinkText("cheese"));

By CSS
这个方法名称意味着它是一个CSS探测器。前提是浏览器默认支持这种方法,建议根据 W3C的标准文档构建CSS选择器。如果浏览器不支持CSS选择器,可以使用 Sizzle。IE6,7和FireFox3.0就是使用Sizzle作为CSS查询引擎。

注意不是所有浏览器都使用同样的CSS选择器表达式,有些CSS可能只在某一个版本中生效。

示例:如何使用该方法定位元素

<div id="food"><span class="dairy">milk</span><span class="dairy aged">cheese</span></div>
WebElement cheese = driver.findElement(By.cssSelector("#food span.dairy.aged"));

By XPath

当有需要时,WebDriver还可以使用浏览器自带的XPATH。对于那些不支持XPATH的浏览器,我们提供了WebDriver特有的实现方式。请确保熟悉XPATH在不同的引擎中的区别,否则会导致一些不可预料的问题。

Driver大小写敏感属性值是否可见是否支持XAPTH
HtmlUnit Driver仅识别小写可见
IE Driver仅识别小写可见
FireFox Diver大小写不敏感可见

上面的表格有一些抽象,让我们来看个例子

<input type="text" name="example" />
<INPUT type="text" name="other" />

List<WebElement> inputs = driver.findElements(By.xpath("//input"));

匹配结果如下

XPATH表达式HtmlUnit DriverFireFox DriverIE Driver
//input122
//INPUT020

有些标签的属性有默认值,这种情况下不指定属性值则匹配默认值。比如,"input"标签"type"属性默认为"text"。使用XPATH的首要原则就是不要忽略这些隐藏的实现。

使用JavaScript

只要返回的是一个Web Element,你还可以使用任意的JS代码查找Web元素,根据查询结果会自动修改为一个WebElement对象。

一个简单的使用jQuery的例子:

WebElement element = (WebElement) ((JavascriptExecutor)driver).executeScript("return $('.cheese')[0]");

查找页面中每个label的所有Input元素:

List<WebElement> labels = driver.findElements(By.tagName("label"));
List<WebElement> inputs = (List<WebElement>) ((JavascriptExecutor)driver).executeScript(
    "var labels = arguments[0], inputs = []; for (var i=0; i < labels.length; i++){" +
    "inputs.push(document.getElementById(labels[i].getAttribute('for'))); } return inputs;", labels);

7.3模拟用户输入行为
我们已经演示了在文本框输入文本内容,其他Web元素应该如何操作呢?你可以触发CheckBox的某个选项,也可以选择Select的某个选项。WebDriver处理Select元素也很简单。
WebElement select = driver.findElement(By.tagName("select"));
List<WebElement> allOptions = select.findElements(By.tagName("option"));
for (WebElement option : allOptions) {
    System.out.println(String.format("Value is: %s", option.getAttribute("value")));
    option.click();
}

上面的例子,将选择Web页面中的第一个Select元素,并将循环打印出选项的取值并单击选项。或许你已经注意到,使用这个方法并不是最有效的。WebDriver提供一个“Select”类,这个类的方法更适合于处理上述这种场景。
Select select = new Select(driver.findElement(By.tagName("select")));
select.deselectAll();
select.selectByVisibleText("Edam");

上面的例子,首先去除选定第一个选项的焦点,然后选中取值为"Edam"的选项。
一旦你完成了所有表单字段的输入,下一步就是提交表单。一种方法就是找到Web页面中的Submit按钮并单击:
driver.findElement(By.id("submit")).click();

作为另一种选择,WebDriver的Element类有一个更加便利的方法"sublmit"。如果你对表单中的某个Element使用该方法,WebDriver将会走读其所在的DOM对象,直到找到其所属的表单,并提交。如果该Element并不在某个表单中,将会抛出异常 NoSuchElementException。
element.submit();

7.4在windows和frames间切换

有些Web程序包含许多Frame和窗口,WebDriver提供"switch to"方法在这之间进行切换:
driver.switchTo().window("windowName");

所有传输给WebDriver的指定将被传输给切换后的窗口。如何直到窗口的名称呢?查看JS并打开该窗口就可以了:
<a href="somewhere.html" target="windowName">Click here to open a new window</a>

作为另一种选择,你可以使用一个“窗口句柄”传递给"switchTo().window()"方法。根据此方法,将会使用迭代器遍历所有打开的窗口:
for (String handle : driver.getWindowHandles()) {
    driver.switchTo().window(handle);
}

你也可以在Frame之间切换(或者进入Frame):
driver.switchTo().frame("frameName");

你还可以根据路径使用Frame的子Frame,而且可以通过索引定位Frame。
driver.switchTo().frame("frameName.0.child");
以上方法将切换到名称为“frameName”的Frame的第一个子Frame,所有Frame都是Web页面的最顶端开始计数。

7.5弹出框

Selenium2.0 beta1版本,我们提供方法获取弹出框。在你触发弹出框的操作后,你可以用一下方法进入弹出框:
Alert alert = driver.switchTo().alert();

以上方法将会返回当前当前打开的alert对象,你可以对这个对象进行任何可操作:点击取消,点击确定,关闭窗口,获取alert的文本内容等。这个接口在alerts、confirms、prompts对象上都有很好的应用,具体请参见API文档。

7.6Navigation:浏览器本地历史记录


前文中,我们使用get方法来获取网页(driver.get("http://www.example.com"))。正如你看到的,WebDriver有不少轻量级的功能聚焦的接口,Navigation就是这样一个。正因为加载网页是一个再普通不过的需求,这个方法存在于Driver类下面,但是用法很简单:

driver.navigate().to("http://www.example.com");

重申一下,"navigate().to()"和"get()"做的是同样的事情,只不过其中一个更适合打印。

Navigate接口还提供方法可以在浏览器历史记录中前后翻页。

driver.navigate().forward();
driver.navigate().back();

请注意,以上功能完全取决于底层的浏览器。如果你习惯跨浏览器操作,当你使用这些接口时可能会出现意想不到的的异常。


7.7Cookies

在我们开始下一步的讲解之前,你可能对WebDriver如何操作本地Cookies很感兴趣。首先,你必须处于当前Cookie的作用域。如果你在打开一个网页之前尝试预置Cookie,而且你的主页大到需要很长一段时间来加载,这时候你需要找一个小点的网页来替代,比如HTTP 404网页( http://example.com/some404page)。
// 打开Cookie作用的网站
driver.get("http://www.example.com");

// 设置全局Cookie
Cookie cookie = new Cookie("key", "value");
driver.manage().addCookie(cookie);

// 输出当前网页所有可用的Cookie
Set<Cookie> allCookies = driver.manage().getCookies();
for (Cookie loadedCookie : allCookies) {
    System.out.println(String.format("%s -> %s", loadedCookie.getName(), loadedCookie.getValue()));
}

// 你又三种方法删除Cookie
// By name
driver.manage().deleteCookieNamed("CookieName");
// By Cookie
driver.manage().deleteCookie(loadedCookie);
// Or all of them
driver.manage().deleteAllCookies();

7.8 修改用户代理服务器

对于FireFox来说很简单:
FirefoxProfile profile = new FirefoxProfile();
profile.addAdditionalPreference("general.useragent.override", "some UA string");
WebDriver driver = new FirefoxDriver(profile);

7.9 拖拽Web元素

下面是一个拖拽Web页面元素的例子,前提是本地事件必须可用。

WebElement element = driver.findElement(By.name("source"));
WebElement target = driver.findElement(By.name("target"));

(new Actions(driver)).dragAndDrop(element, target).perform();

8 各种Driver的特性以及如何选择合适Driver

WebDriver是接口的名称,在撰写测试脚本时会被使用到,该接口有如下几个实现类:HtmlUnit Driver、FireFox Driver、Internet Explorer Driver、Chrome Driver、Opera Driver、Android Diver、IOS Diver。

8.1HtmlUnit Driver


这是所有实现类中最快最轻量级的Driver。正如名字所阐释的,它是基于HtmlUnit。HtmlUnit是使用Java基于无GUI的WebBrower实现的。使用任何编程语言绑定Selenium Server都必须使用该Driver。
使用举例

WebDriver driver = new HtmlUnitDriver();

优点
1、最快的WebDriver实现类。
2、纯Java实现,具有平台独立性。
3、支持JavaScript。

缺点
1、超出其他浏览器的JavaScript行为(请栏下文)。

JavaScript在HtmlUnit中的应用
没有任何一个主流的浏览器使用基于HtmlUnit的JavaScript引擎。如果使用HtmlUnit测试JavaScript,在这些浏览器上的测试结果可能会相差很大。
当提到“JavaScript”时,我们其实是指“JavaScript和DOM”。DOM已经在W3C中,明确定义了每一种浏览器的特性以及JavaScript接口是如何工作的。
HtmlUnit拥有完全不同的一套DOM实现而且对JavaScript的支持也很好用,与W3C标准中DOM对主流浏览器的实现相比,HtmlUnit有一套自己的行为和特性(与其他浏览器类似)。

使用WebDriver,必须做出一个选择:1、激活HtmlUnit的JavaScript特性,执行测试任务并可能引入HtmlUnit自身存在的明显的问题;2、不激活JavaScript,但是现在越来越多的网页依赖于JavaScript(译者注:原文撰写的时间应该比较古老了)。
我们选择了较为保守的策略,使用HtmlUnit时默认不激活JavaScript。当每一次发布WebDriver和HtmlUnit时,我们对这个决定都很挣扎:我们希望在某些场景下默认激活HtmlUnit的JavaScript特性。

如果你迫不及待的想要使用此特性,激活JavaScript是很容易地操作:

HtmlUnitDriver driver = new HtmlUnitDriver(true);

这会导致HtmlUnit Driver与FireFox 3.6的JavaScript默认特性冲突。

8.2 FireFox Diver


我们通过FireFox插件控件FireFox浏览器。只有在装有Selenium WebDriver.xpi(FireFox插件)的机器上,FireFox配置文件才可以被使用。我们更改一些默认配置( see the source to see which ones)。
经测试,FireFox Driver可以在Windows、Linux、Mac正常运行。

使用方法

WebDriver driver = new FirefoxDriver();

优点
1、运行在真实的浏览器中且支持JavaScript原生特性。
2、比Internet Explorer Driver(译者注:IE躺着中枪)运行速度快。

缺点
1、比HtmlUnit Driver运行速度慢。

如何更改FireFox Driver配置文件
1、我们预料到客户会想要修改user agent的配置,有一个FireFox配置文件的使用窍门,包含了很多实用的特性。有两种方法获取配置文件。通常我们使用配置文件管理器(FireFox-ProFileManager)创建配置文件。

ProfilesIni allProfiles = new ProfilesIni();
FirefoxProfile profile = allProfiles.getProfile("WebDriver");
profile.setPreferences("foo.bar", 23);
WebDriver driver = new FirefoxDriver(profile);

2、作为替代,如果配置文件没有被注册到FireFox。

File profileDir = new File("path/to/top/level/of/profile");
FirefoxProfile profile = new FirefoxProfile(profileDir);
profile.addAdditionalPreferences(extraPrefs);
WebDriver driver = new FirefoxDriver(profile);

3、在开发FireFox Driver特性时,我们暴露它的能力并使用它。举个例子,我们发现Liunx系统下的FireFox的本地事件特性时稳定的,但是它默认是不激活的。下面是激活的方式:

FirefoxProfile profile = new FirefoxProfile();
profile.setEnableNativeEvents(true);
WebDriver driver = new FirefoxDriver(profile);

8.3Internet Explorer Driver


该Driver是通过Dll文件控制的,所以只能运行在Windows系统下。每一个Selenium发布版本都对内核进行了测试,包括IE6、7、8在XP上执行,IE9在WIN7上执行。
使用方法

WebDriver driver = new InternetExplorerDriver();

优点
1、运行在真实的浏览器中且支持JavaScript原生特性。

缺点
1、只能在Windows下运行。
2、相比而言运行速度较其他Driver慢。
3、大多数版本不支持XPATH。
4、IE6和7不支持本地的CSS选择器。
5、CSS选择器在IE8和9支持,但是不完全支持CSS3。

8.4 Chrome Driver


Chrome Driver是由 Chromium项目组自己维护支持的。WebDiver通过Chrome Binary与Chrome进行协作。你不仅需要安装Chrome,还需要安装ChromeDriver。ChromeDriver需要放在明确的系统文件夹中,从而WebDriver可以自动找到驱动文件。Chrome的安装路径会被ChromeDiver自动在默认安装路径下寻找。当然这两个路径都可以在环境变量中重写,请到这个 wiki下获取更多信息。

使用方法

WebDriver driver = new ChromeDriver();

优点
1、运行在真实的浏览器中且支持JavaScript原生特性。
2、因为Chrome是基于Webkit开发的浏览器,ChromeDriver支持在Safari下验证你的测试场景。因为使用的JavaScript引擎版本不一致,在两者下JavaScript执行结果会有些许不同。

缺点
1、比HtmlUnit Driver运行速度慢。

Info
详细请参照 See our wiki。更多的信息可以在这里查找到 downloads page

8.5 Opera Diver


详细请参照 See our wiki

8.6 IOS Diver


详细请参照 See our wiki

8.5 Android Diver


详细请参照 See our wiki



译者注:

1、原文链接:http://www.seleniumhq.org/docs/03_webdriver.jsp 原文成文时间较早,有部分描述落后当前的软件环境。

2、原文的最后两张未翻译,第9章(向前兼容:融合WebDriver和Selenium-RC),第10章(为远程WebDriver单独启动Selenium Server)。

3、文中只包含了java相关的操作,WebDriver还支持c#/Python/Ruby/Perl/PHP/Perl,如有需要,请阅读原文。

4、language binding,又叫glue code,意思是胶水代码,比如有个C++的lib库,java调用这个库的api就叫java binding。参考:http://en.wikipedia.org/wiki/Language_binding

5、本文为英语译文学习之作,措辞拙劣,有些单词句子没有深究就直译了,深感从阅读到翻译差的不仅仅是一本字典,还有文化的差异。笔者强烈推荐直接阅读官网上的原文,如果我的译文给你造成误解,深感不安。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值