Selenium 测试框架:第三方服务与示例代码解析
1. TestObject 真实设备云
2017 年推出的 TestObject 真实设备云,是一个包含 Android 和 iOS 物理移动设备的资源池。用户可以购买许可证来访问这些设备,用于手动测试移动应用程序或运行自动化测试。与在模拟器和仿真器平台上运行不同,用户可以在他们所需测试的真实设备及其平台和版本上运行测试。
若要针对真实设备进行测试,需设置额外的驱动类功能以指向不同的远程中心,示例代码如下:
// section in CreateDriver.java class for TestObject features
...
boolean realDevice = true;
if ( realDevice == true ) {
caps.setCapability("testobject_device",
"iPhone 6");
caps.setCapability("testobject_cache_device",
false);
caps.setCapability("testobject_session_creation_timeout",
"900000");
caps.setCapability("testobject_appium_version",
"1.7.1");
caps.setCapability("testobject_suite_name",
"mySuiteName");
caps.setCapability("testobject_app_id",
1);
caps.setCapability("testobject_test_name",
"myTestName");
// private pool caps
caps.setCapability("phoneOnly",
"iphone.phoneOnly.rdc");
caps.setCapability("tabletOnly",
"iphone.tabletOnly.rdc");
caps.setCapability("privateDevicesOnly",
"iphone.privateDevicesOnly.rdc");
if ( browser.contains("iphone") || browser.contains("ipad") ) {
caps.setCapability("testobject_api_key",
"iOSAppKey");
}
else {
caps.setCapability("testobject_api_key",
"androidAppKey");
}
remoteHubURL = "https://us1.appium.testobject.com/wd/hub";
}
2. Jenkins 插件
还有一个适用于 Jenkins 的 Sauce Labs 插件。该插件允许用户在 Jenkins 项目中选择所需的平台、浏览器、移动设备及其版本。用户还可以设置 SauceConnect 隧道参数,以及其他所需的命令行选项,如日志文件、日志文件路径、代理服务器、端口等。这些选择会被设置为系统环境变量,在运行时可被驱动程序调用。
3. 自建网格与第三方网格的优缺点对比
| 对比项 | 自建网格 | 第三方网格(如 Sauce Labs) |
|---|---|---|
| 平台数量 | 难以构建包含 900 种浏览器/操作系统组合或数百个移动 iOS 和 Android 设备的网格 | 几乎拥有无限的浏览器和移动平台资源池,可测试不同的浏览器版本、操作系统版本、移动设备版本等 |
| 维护 | 需要持续安装操作系统补丁、安全补丁、浏览器升级、Selenium 升级、驱动程序升级等。在移动方面,还需不断升级 Xcode 或 Android SDK 以及模拟器/仿真器,以及 Appium、NPM、Node.js 等 | 无需维护,只需承担财务成本 |
| 性能 | 速度比 Sauce Labs 测试云快 25 - 30% | 测试运行时会引入延迟,但如果将测试设计得小而模块化,并并行运行,可利用多个虚拟机同时运行,从而加快测试速度 |
| 高可用性 | 由于定期向网格上的虚拟机推送软件更新,节点可能会重启,导致节点可用性不稳定 | 服务大部分时间可用,至少 99% 的时间处于运行状态 |
| 升级 | 用户需要在版本发布之间安排停机时间来关闭中心和节点,升级频率较低 | 持续提供新平台、浏览器和移动设备的升级,支持最新的 Selenium、Appium 和浏览器驱动程序修订版 |
| 增强功能 | 支持基于 Selenium 的插件,如 BrowserMob Proxy,并提供最新技术的测试和使用,包括新推出的支持物理设备移动测试的 TestObject 真实设备云服务 |
4. 第三方插件集成
在 Selenium 框架中,由于使用 Java 和 TestNG 技术,与之相关的各种插件和 API 易于集成:
-
IntelliJ 插件
:有适用于 IntelliJ 的 Selenium 插件,以及内置的 TestNG 插件,可在控制台和报告格式中提供测试结果。
-
Jenkins 插件
:Jenkins 有 TestNG 插件提供结果和历史数据,还有 HTML 发布器插件,允许用户包含框架自动生成的 HTML 报告。
-
ExtentReports API
:可利用 DataProvider 数据和 TestNG 结果集成到框架中。
5. 工作示例框架
示例框架包含驱动类、所需的实用类、浏览器页面对象类、浏览器测试类和 JSON 数据文件,可在 IntelliJ 或 Eclipse IDE 中运行,具体组件如下:
- Selenium 驱动和 DataProvider 类
- Selenium 实用类
- ExtentReports 类
- 浏览器页面对象基类和子类
- 浏览器测试类和数据文件
- 浏览器套件 XML 和 Maven POM XML 文件
5.1 运行示例测试的准备工作
-
下载文件
:下载最新的 Selenium 3.x JAR 文件、TestNG JAR 文件以及所需的浏览器驱动程序版本。具体包括:
- Java 1.8 SDK 和 JRE
- IntelliJ IDEA 2017.3
- Selenium 3.7.1 WebDriver JARs
- TestNG 6.11 JARs
- ExtentReports 3.1.0 JARs
- ChromeDriver.exe 2.33(Windows 32 位;目前无 64 位驱动)
- Firefox GeckoDriver.exe 0.19.1(Windows 64 位)
- IEDriverServer.exe 3.7.1(Windows 32 位;比 64 位驱动运行速度快)
- Chrome 浏览器 62.0
- Firefox 浏览器 57.0
- Internet Explorer 浏览器 11.0
- 文件放置与路径修改 :将文件放置在 IDE 的项目文件夹中,并修改 selenium.properties 和 Global_VARS.java 文件中的路径,以指向正确的包和驱动程序位置。
- 包结构创建 :在 IntelliJ 中创建名为 SeleniumFrameworkDesign 的项目,并创建以下包结构:src/main/java/com/framework/ux/utils/chapter10。同时,创建以下文件夹用于存放驱动程序和测试输出:SeleniumFrameworkDesign/drivers 和 SeleniumFrameworkDesign/test - output。
5.2 示例代码
5.2.1 CreateDriver.java
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.ie.InternetExplorerOptions;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.io.FileInputStream;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @author Carl Cocchiaro
*
* Selenium Driver Class
*
*/
public class CreateDriver {
// local variables
private static CreateDriver instance = null;
private static final int IMPLICIT_TIMEOUT = 0;
private ThreadLocal<WebDriver> webDriver =
new ThreadLocal<WebDriver>();
private ThreadLocal<String> sessionId =
new ThreadLocal<String>();
private ThreadLocal<String> sessionBrowser =
new ThreadLocal<String>();
private ThreadLocal<String> sessionPlatform =
new ThreadLocal<String>();
private ThreadLocal<String> sessionVersion =
new ThreadLocal<String>();
private String getEnv = null;
private Properties props = new Properties();
// constructor
private CreateDriver() {
}
/**
* getInstance method to retrieve active driver instance
*
* @return CreateDriver
*/
public static CreateDriver getInstance() {
if ( instance == null ) {
instance = new CreateDriver();
}
return instance;
}
/**
* setDriver method to create driver instance
*
* @param browser
* @param environment
* @param platform
* @param optPreferences
* @throws Exception
*/
@SafeVarargs
public final void setDriver(String browser,
String platform,
String environment,
Map<String, Object>... optPreferences)
throws Exception {
DesiredCapabilities caps = null;
String getPlatform = null;
props.load(new FileInputStream(Global_VARS.SE_PROPS));
switch (browser) {
case "firefox":
caps = DesiredCapabilities.firefox();
FirefoxOptions ffOpts = new FirefoxOptions();
FirefoxProfile ffProfile = new FirefoxProfile();
ffProfile.setPreference("browser.autofocus",
true);
ffProfile.setPreference("browser.tabs.remote.autostart.2", false);
caps.setCapability(FirefoxDriver.PROFILE,
ffProfile);
caps.setCapability("marionette",
true);
// then pass them to the local WebDriver
if ( environment.equalsIgnoreCase("local") ) {
System.setProperty("webdriver.gecko.driver",
props.getProperty("gecko.driver.windows.path"));
webDriver.set(new
FirefoxDriver(ffOpts.merge(caps)));
}
break;
case "chrome":
caps = DesiredCapabilities.chrome();
ChromeOptions chOptions = new ChromeOptions();
Map<String, Object> chromePrefs =
new HashMap<String, Object>();
chromePrefs.put("credentials_enable_service",
false);
chOptions.setExperimentalOption("prefs",
chromePrefs);
chOptions.addArguments("--disable-plugins",
"--disable-extensions",
"--disable-popup-blocking");
caps.setCapability(ChromeOptions.CAPABILITY,
chOptions);
caps.setCapability("applicationCacheEnabled",
false);
if ( environment.equalsIgnoreCase("local") ) {
System.setProperty("webdriver.chrome.driver",
props.getProperty("chrome.driver.windows.path"));
webDriver.set(new
ChromeDriver(chOptions.merge(caps)));
}
break;
case "internet explorer":
caps = DesiredCapabilities.internetExplorer();
InternetExplorerOptions ieOpts =
new InternetExplorerOptions();
ieOpts.requireWindowFocus();
ieOpts.merge(caps);
caps.setCapability("requireWindowFocus",
true);
if ( environment.equalsIgnoreCase("local") ) {
System.setProperty("webdriver.ie.driver",
props.getProperty("ie.driver.windows.path"));
webDriver.set(new InternetExplorerDriver(
ieOpts.merge(caps)));
}
break;
}
getEnv = environment;
getPlatform = platform;
sessionId.set(((RemoteWebDriver) webDriver.get())
.getSessionId().toString());
sessionBrowser.set(caps.getBrowserName());
sessionVersion.set(caps.getVersion());
sessionPlatform.set(getPlatform);
System.out.println("\n*** TEST ENVIRONMENT = "
+ getSessionBrowser().toUpperCase()
+ "/" + getSessionPlatform().toUpperCase()
+ "/" + getEnv.toUpperCase()
+ "/Selenium Version="
+ props.getProperty("selenium.revision")
+ "/Session ID="
+ getSessionId()
+ "\n");
getDriver().manage().timeouts().implicitlyWait(
IMPLICIT_TIMEOUT, TimeUnit.SECONDS);
getDriver().manage().window().maximize();
}
/**
* getDriver method to retrieve active driver
*
* @return WebDriver
*/
public WebDriver getDriver() {
return webDriver.get();
}
/**
* closeDriver method to close active driver
*
*/
public void closeDriver() {
try {
getDriver().quit();
}
catch ( Exception e ) {
// do something
}
}
/**
* getSessionId method to retrieve active id
*
* @return String
* @throws Exception
*/
public String getSessionId() throws Exception {
return sessionId.get();
}
/**
* getSessionBrowser method to retrieve active browser
* @return String
* @throws Exception
*/
public String getSessionBrowser() throws Exception{
return sessionBrowser.get();
}
/**
* getSessionVersion method to retrieve active version
*
* @return String
* @throws Exception
*/
public String getSessionVersion() throws Exception {
return sessionVersion.get();
}
/**
* getSessionPlatform method to retrieve active platform
* @return String
* @throws Exception
*/
public String getSessionPlatform() throws Exception {
return sessionPlatform.get();
}
}
5.2.2 JSONDataProvider.java
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.testng.annotations.DataProvider;
import java.io.FileReader;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author Carl Cocchiaro
*
* TestNG JSON DataProvider Utility Class
*
*/
public class JSONDataProvider {
public static String dataFile = "";
public static String testCaseName = "NA";
public JSONDataProvider() throws Exception {
}
/**
* fetchData method to retrieve test data for specified method
*
* @param method
* @return Object[][]
* @throws Exception
*/
@DataProvider(name = "fetchData_JSON")
public static Object[][] fetchData(Method method) throws Exception
{
Object rowID, description;
Object[][] result;
testCaseName = method.getName();
List<JSONObject> testDataList = new ArrayList<JSONObject>();
JSONArray testData =
(JSONArray)
extractData_JSON(dataFile).get(method.getName());
for ( int i = 0; i < testData.size(); i++ ) {
testDataList.add((JSONObject) testData.get(i));
}
// include Filter
if ( System.getProperty("includePattern") != null ) {
String include = System.getProperty("includePattern");
List<JSONObject> newList = new ArrayList<JSONObject>();
List<String> tests = Arrays.asList(include.split(",", -1));
for ( String getTest : tests ) {
for ( int i = 0; i < testDataList.size(); i++ ) {
if (
testDataList.get(i).toString().
contains(getTest) ) {
newList.add(testDataList.get(i));
}
}
}
// reassign testRows after filtering tests
testDataList = newList;
}
// exclude Filter
if ( System.getProperty("excludePattern") != null ) {
String exclude =System.getProperty("excludePattern");
List<String> tests = Arrays.asList(exclude.split(",", -1));
for ( String getTest : tests ) {
for ( int i = testDataList.size() - 1 ; i >= 0; i-- ) {
if ( testDataList.get(i).toString().
contains(getTest) ) {
testDataList.remove(testDataList.get(i));
}
}
}
}
// create object for dataprovider to return
try {
result =
new Object[testDataList.size()]
[testDataList.get(0).size()];
for ( int i = 0; i < testDataList.size(); i++ ) {
rowID = testDataList.get(i).get("rowID");
description = testDataList.get(i).get("description");
result[i] =
new Object[] { rowID, description, testDataList.get(i)
};
}
}
catch(IndexOutOfBoundsException ie) {
result = new Object[0][0];
}
return result;
}
/**
* extractData_JSON method to get JSON data from file
*
* @param file
* @return JSONObject
* @throws Exception
*/
public static JSONObject extractData_JSON(String file) throws
Exception {
FileReader reader = new FileReader(file);
JSONParser jsonParser = new JSONParser();
return (JSONObject) jsonParser.parse(reader);
}
}
5.2.3 工作流 mermaid 图
graph LR
A[开始] --> B[选择测试平台]
B --> C{是否使用第三方服务}
C -- 是 --> D[使用 Sauce Labs 等服务]
C -- 否 --> E[自建 Selenium 网格]
D --> F[配置 TestObject 真实设备云或其他服务]
E --> G[维护网格环境]
F --> H[运行测试]
G --> H
H --> I[生成测试报告]
I --> J[结束]
通过以上内容,我们对 Selenium 框架中第三方插件的使用、自建网格与第三方网格的优缺点有了清晰的了解,并掌握了示例框架的运行方法和相关代码。
6. Selenium 实用类代码解析
6.1 BrowserUtils.java
import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
/**
* @author Carl Cocchiaro
*
* Browser Utility Class
*
*/
public class BrowserUtils {
/**
* waitFor method to poll page title
*
* @param title
* @param timer
* @throws Exception
*/
public static void waitFor(String title,
int timer)
throws Exception {
WebDriver driver = CreateDriver.getInstance().getDriver();
WebDriverWait exists = new WebDriverWait(driver, timer);
exists.until(ExpectedConditions.refreshed(
ExpectedConditions.titleContains(title)));
}
/**
* waitForURL method to poll page URL
*
* @param url
* @param timer
* @throws Exception
*/
public static void waitForURL(String url,
int timer)
throws Exception {
WebDriver driver = CreateDriver.getInstance().getDriver();
WebDriverWait exists = new WebDriverWait(driver, timer);
exists.until(ExpectedConditions.refreshed(
ExpectedConditions.urlContains(url)));
}
/**
* waitForClickable method to poll for clickable
*
* @param by
* @param timer
* @throws Exception
*/
public static void waitForClickable(By by,
int timer)
throws Exception {
WebDriver driver = CreateDriver.getInstance().getDriver();
WebDriverWait exists = new WebDriverWait(driver, timer);
exists.until(ExpectedConditions.refreshed(
ExpectedConditions.elementToBeClickable(by)));
}
/**
* click method using JavaScript API click
*
* @param by
* @throws Exception
*/
public static void click(By by) throws Exception {
WebDriver driver = CreateDriver.getInstance().getDriver();
WebElement element = driver.findElement(by);
JavascriptExecutor js = (JavascriptExecutor)driver;
js.executeScript("arguments[0].click();", element);
}
}
BrowserUtils
类提供了一些实用的方法,用于处理浏览器操作:
-
waitFor
方法:用于轮询页面标题,直到标题包含指定的字符串。
-
waitForURL
方法:用于轮询页面 URL,直到 URL 包含指定的字符串。
-
waitForClickable
方法:用于轮询元素是否可点击。
-
click
方法:使用 JavaScript API 点击元素。
6.2 Global_VARS.java
import java.io.File;
/**
* @author Carl Cocchiaro
*
* Global Variable Utility Class
*
*/
public class Global_VARS {
// browser defaults
public static final String BROWSER = "chrome";
public static final String PLATFORM = "Windows 7";
public static final String ENVIRONMENT = "local";
public static String DEF_BROWSER = null;
public static String DEF_PLATFORM = null;
public static String DEF_ENVIRONMENT = null;
// suite folder defaults
public static String SUITE_NAME = null;
public static final String TARGET_URL =
"http://www.practiceselenium.com/";
public static String propFile =
"src/main/java/com/framework/ux/utils/chapter10/selenium.properties";
public static final String SE_PROPS =
new File(propFile).getAbsolutePath();
public static final String TEST_OUTPUT_PATH = "test-output/";
public static final String LOGFILE_PATH = TEST_OUTPUT_PATH +
"Logs/";
public static final String REPORT_PATH = TEST_OUTPUT_PATH +
"Reports/";
public static final String REPORT_CONFIG_FILE =
"src/main/java/com/framework/ux/utils/chapter10/extent-config.xml";
// suite timeout defaults
public static final int TIMEOUT_MINUTE = 60;
public static final int TIMEOUT_ELEMENT = 10;
}
Global_VARS
类定义了一些全局变量,包括默认的浏览器、平台、环境,以及测试输出路径、日志文件路径、报告路径等。
6.3 TestNG_ConsoleRunner.java
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.TestListenerAdapter;
import java.io.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author Carl Cocchiaro
*
* TestNG Listener Utility Class
*
*/
public class TestNG_ConsoleRunner extends TestListenerAdapter {
private static String logFile = null;
/**
* onStart method
*
* @param testContext
*/
@Override
public void onStart(ITestContext testContext) {
super.onStart(testContext);
}
/**
* onFinish method
*
* @param testContext
*/
@Override
public void onFinish(ITestContext testContext) {
log("\nTotal Passed = "
+ getPassedTests().size()
+ ", Total Failed = "
+ getFailedTests().size()
+ ", Total Skipped = "
+ getSkippedTests().size()
+ "\n");
super.onFinish(testContext);
}
/**
* onTestStart method
*
* @param tr
*/
@Override
public void onTestStart(ITestResult tr) {
if ( logFile == null ) {
logFile = Global_VARS.LOGFILE_PATH
+ Global_VARS.SUITE_NAME
+ "-"
+ new SimpleDateFormat("MM.dd.yy.HH.mm.ss")
.format(new Date())
+ ".log";
}
log("\n---------------------------------- Test '"
+ tr.getName()
+ getTestDescription(tr)
+ "' ----------------------------------\n");
log(tr.getStartMillis(),
"START-> "
+ tr.getName() + "\n");
log(" ***Test Parameters = "
+ getTestParams(tr)
+ "\n");
super.onTestStart(tr);
}
/**
* onTestSuccess method
*
* @param tr
*/
@Override
public void onTestSuccess(ITestResult tr) {
log(" ***Result = PASSED\n");
log(tr.getEndMillis(),
"END -> "
+ tr.getName());
log("\n---\n");
super.onTestSuccess(tr);
}
/**
* onTestFailure method
*
* @param tr
*/
@Override
public void onTestFailure(ITestResult tr) {
if ( !getTestMessage(tr).equals("") ) {
log(getTestMessage(tr) + "\n");
}
log(" ***Result = FAILED\n");
log(tr.getEndMillis(),
"END -> "
+ tr.getInstanceName()
+ "." + tr.getName());
log("\n---\n");
super.onTestFailure(tr);
}
/**
* onTestSkipped method
*
* @param tr
*/
@Override
public void onTestSkipped(ITestResult tr) {
if ( !getTestMessage(tr).equals("") ) {
log(getTestMessage(tr)
+ "\n");
}
log(" ***Result = SKIPPED\n");
log(tr.getEndMillis(),
"END -> "
+ tr.getInstanceName()
+ "."
+ tr.getName());
log("\n---\n");
super.onTestSkipped(tr);
}
/**
* onConfigurationSuccess method
*
* @param itr
*/
@Override
public void onConfigurationSuccess(ITestResult itr) {
super.onConfigurationSuccess(itr);
}
/**
* onConfigurationFailure method
*
* @param tr
*/
@Override
public void onConfigurationFailure(ITestResult tr) {
if ( !getTestMessage(tr).equals("") ) {
log(getTestMessage(tr)
+ "\n");
}
log(" ***Result = CONFIGURATION FAILED\n");
log(tr.getEndMillis(),
"END CONFIG -> "
+ tr.getInstanceName()
+ "."
+ tr.getName());
log("\n---\n");
super.onConfigurationFailure(tr);
}
/**
* onConfigurationSkip method
*
* @param tr
*/
@Override
public void onConfigurationSkip(ITestResult tr) {
log(getTestMessage(tr));
log(" ***Result = CONFIGURATION SKIPPED\n");
log(tr.getEndMillis(),
"END CONFIG -> "
+ tr.getInstanceName()
+ "."
+ tr.getName());
log("\n---\n");
super.onConfigurationSkip(tr);
}
/**
* log method
*
* @param dateMillis
* @param line
*/
public void log(long dateMillis,String line) {
System.out.format("%s: %s%n",
String.valueOf(new Date(dateMillis)),line);
if ( logFile != null ) {
writeTestngLog(logFile,
line);
}
}
/**
* log method
*
* @param line
*/
public void log(String line) {
System.out.format("%s%n", line);
if ( logFile != null ) {
writeTestngLog(logFile, line);
}
}
/**
* getTestMessage method
*
* @param tr
* @return String
*/
public String getTestMessage(ITestResult tr) {
Boolean found = false;
if ( tr != null && tr.getThrowable() != null ) {
found = true;
}
if ( found == true ) {
return tr.getThrowable().getMessage() ==
null ? "" : tr.getThrowable().getMessage();
}
else {
return "";
}
}
/**
* getTestParams method
*
* @param tr
* @return String
*/
public String getTestParams(ITestResult tr) {
int iLength = tr.getParameters().length;
String message = "";
try {
if ( tr.getParameters().length > 0 ) {
message = tr.getParameters()[0].toString();
for ( int iCount = 0; iCount < iLength; iCount++ ) {
if ( iCount == 0 ) {
message = tr.getParameters()[0].toString();
}
else {
message = message
+ ", "
+ tr.getParameters()
[iCount].toString();
}
}
}
}
catch(Exception e) {
// do nothing...
}
return message;
}
/**
* getTestDescription method
*
* @param tr
* @return String
*/
public String getTestDescription(ITestResult tr) {
String message = "";
try {
if ( tr.getParameters().length > 0 ) {
message = ": "
+ tr.getParameters()[1].toString();
}
}
catch(Exception e) {
// do nothing...
}
return message;
}
/**
* writeTestngLog method
*
* @param logFile
* @param line
*/
public void writeTestngLog(String logFile,String line) {
DateFormat dateFormat =
new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
Date date = new Date();
File directory = new File(Global_VARS.LOGFILE_PATH);
File file = new File(logFile);
try {
if ( !directory.exists() ) {
directory.mkdirs();
}
else if ( !file.exists() ) {
file.createNewFile();
}
BufferedWriter writer =
new BufferedWriter(new FileWriter(logFile, true));
if ( line.contains("START") || line.contains("END") ) {
writer.append("["
+ dateFormat.format(date)
+ "] "
+ line);
}
else {
writer.append(line);
}
writer.newLine();
writer.close();
}
catch(IOException e) {
// do nothing...
}
}
}
TestNG_ConsoleRunner
类是一个 TestNG 监听器,用于记录测试执行的日志。它会在测试开始、成功、失败、跳过以及配置成功、失败、跳过时记录相应的信息,并将日志写入文件。
7. 总结与应用建议
7.1 总结
- 第三方插件在 Selenium 框架中具有重要作用,如 Sauce Labs 的 TestObject 真实设备云、Jenkins 插件等,它们能方便地集成到框架中,提高测试效率。
- 自建 Selenium 网格和使用第三方网格各有优缺点,需要根据项目的具体需求进行选择。
- 示例框架提供了完整的代码结构,包括驱动类、实用类、页面对象类和测试类等,为实际项目开发提供了参考。
7.2 应用建议
- 选择合适的测试方式 :如果项目需要测试多种平台和设备,且对维护成本敏感,可选择第三方网格服务;如果对性能要求较高,且有足够的维护资源,可考虑自建网格。
- 优化测试代码 :编写小而模块化的测试用例,并并行运行,以提高测试速度。
- 利用插件和 API :充分利用 IntelliJ、Jenkins 等工具的插件,以及 ExtentReports API 等,提升测试结果的可视化和分析能力。
7.3 测试流程总结 mermaid 图
graph LR
A[环境准备] --> B[选择测试平台]
B --> C[配置测试环境]
C --> D[编写测试用例]
D --> E[运行测试]
E --> F[生成测试报告]
F --> G{测试是否通过}
G -- 是 --> H[结束测试]
G -- 否 --> I[分析失败原因]
I --> J[修复问题]
J --> E
通过以上内容,我们全面了解了 Selenium 框架中第三方服务的使用、自建与第三方网格的对比,以及示例框架的代码实现和应用建议。希望这些信息能帮助你更好地进行 Web 和移动应用的测试工作。
超级会员免费看
1646

被折叠的 条评论
为什么被折叠?



