自动化测试实战:Selenium 与相关工具的综合应用
1. 配置文件与版本管理
在自动化测试中,配置文件起着至关重要的作用,它可以帮助我们管理各种工具和环境的版本信息。以下是
selenium.properties
文件的代码:
# Selenium Properties File
selenium.revision=3.7.1
geckodriver.revision=0.19.1
chromedriver.revision=2.33
iedriver.revision=11.0
firefox.revision=57.0
chrome.revision=62.0
ie.revision=11.0
gecko.driver.windows.path=drivers/geckodriver.exe
chrome.driver.windows.path=drivers/chromedriver.exe
ie.driver.windows.path=drivers/IEDriverServer.exe
这个文件定义了 Selenium 及其相关驱动的版本信息,以及不同浏览器驱动在 Windows 系统上的路径。通过这样的配置,我们可以确保测试环境的一致性和稳定性。
2. 测试报告生成
为了更好地展示测试结果,我们使用了 ExtentReports 来生成 HTML 测试报告。这部分涉及到两个重要的文件:
ExtentTestNGIReporterListener.java
和
extent-config.xml
。
2.1 ExtentTestNGIReporterListener.java
import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.MediaEntityBuilder;
import com.aventstack.extentreports.Status;
import com.aventstack.extentreports.reporter.ExtentHtmlReporter;
import com.aventstack.extentreports.reporter.configuration.Protocol;
import com.aventstack.extentreports.reporter.configuration.Theme;
import org.testng.*;
import org.testng.xml.XmlSuite;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.*;
/**
* @author Carl Cocchiaro
*
* ExtentReports HTML Reporter Class
*
*/
public class ExtentTestNGIReporterListener implements IReporter {
private String bitmapDir = Global_VARS.REPORT_PATH;
private String seleniumRev = "3.7.1";
private String docTitle = "SELENIUM FRAMEWORK DESIGN IN DATA-DRIVEN TESTING";
private ExtentReports extent;
@Override
public void generateReport(List<XmlSuite> xmlSuites,
List<ISuite> suites,
String outputDirectory) {
for (ISuite suite : suites) {
init(suite);
Map<String, ISuiteResult> results = suite.getResults();
for (ISuiteResult result : results.values()) {
try {
processTestResults(result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
extent.flush();
}
private void init(ISuite suite) {
File directory = new File(Global_VARS.REPORT_PATH);
if (!directory.exists()) {
directory.mkdirs();
}
ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(Global_VARS.REPORT_PATH + suite.getName() + ".html");
htmlReporter.config().setDocumentTitle(docTitle);
htmlReporter.config().setReportName(suite.getName().replace("_", " "));
htmlReporter.config().setChartVisibilityOnOpen(false);
htmlReporter.config().setTheme(Theme.STANDARD);
htmlReporter.config().setEncoding("UTF-8");
htmlReporter.config().setProtocol(Protocol.HTTPS);
htmlReporter.config().setTimeStampFormat("MMM-dd-yyyy HH:mm:ss a");
htmlReporter.loadXMLConfig(new File(Global_VARS.REPORT_CONFIG_FILE));
extent = new ExtentReports();
extent.setSystemInfo("Browser", Global_VARS.DEF_BROWSER);
extent.setSystemInfo("Environment", Global_VARS.DEF_ENVIRONMENT);
extent.setSystemInfo("Platform", Global_VARS.DEF_PLATFORM);
extent.setSystemInfo("OS Version", System.getProperty("os.version"));
extent.setSystemInfo("Java Version", System.getProperty("java.version"));
extent.setSystemInfo("Selenium Version", seleniumRev);
extent.attachReporter(htmlReporter);
extent.setReportUsesManualConfiguration(true);
}
private void processTestResults(ISuiteResult r) throws Exception {
ExtentTest test = null;
Status status = null;
String message = null;
Set<ITestResult> passed = r.getTestContext().getPassedTests().getAllResults();
Set<ITestResult> failed = r.getTestContext().getFailedTests().getAllResults();
Set<ITestResult> skipped = r.getTestContext().getSkippedTests().getAllResults();
Set<ITestResult> configs = r.getTestContext().getFailedConfigurations().getAllResults();
Set<ITestResult> tests = new HashSet<ITestResult>();
tests.addAll(passed);
tests.addAll(skipped);
tests.addAll(failed);
if (tests.size() > 0) {
List<ITestResult> resultList = new LinkedList<ITestResult>(tests);
class ResultComparator implements Comparator<ITestResult> {
public int compare(ITestResult r1, ITestResult r2) {
return getTime(r1.getStartMillis()).compareTo(getTime(r2.getStartMillis()));
}
}
Collections.sort(resultList, new ResultComparator());
for (ITestResult result : resultList) {
if (getTestParams(result).isEmpty()) {
test = extent.createTest(result.getMethod().getMethodName());
} else {
if (getTestParams(result).split(",")[0].contains(result.getMethod().getMethodName())) {
test = extent.createTest(getTestParams(result).split(",")[0], getTestParams(result).split(",")[1]);
} else {
test = extent.createTest(result.getMethod().getMethodName(), getTestParams(result).split(",")[1]);
}
}
test.getModel().setStartTime(getTime(result.getStartMillis()));
test.getModel().setEndTime(getTime(result.getEndMillis()));
for (String group : result.getMethod().getGroups()) {
if (!group.isEmpty()) {
test.assignCategory(group);
} else {
int size = result.getMethod().getTestClass().toString().split("\\.").length;
String testName = result.getMethod().getRealClass().getName().toString().split("\\.")[size - 1];
test.assignCategory(testName);
}
}
switch (result.getStatus()) {
case 1:
status = Status.PASS;
break;
case 2:
status = Status.FAIL;
break;
case 3:
status = Status.SKIP;
break;
default:
status = Status.INFO;
break;
}
if (status.equals(Status.PASS)) {
message = "<font color=#00af00>" + status.toString().toUpperCase() + "</font>";
} else if (status.equals(Status.FAIL)) {
message = "<font color=#F7464A>" + status.toString().toUpperCase() + "</font>";
} else if (status.equals(Status.SKIP)) {
message = "<font color=#2196F3>" + status.toString().toUpperCase() + "</font>";
} else {
message = "<font color=black>" + status.toString().toUpperCase() + "</font>";
}
test.log(status, message);
if (!getTestParams(result).isEmpty()) {
test.log(Status.INFO, "TEST DATA = [" + getTestParams(result) + "]");
}
if (result.getThrowable() != null) {
test.log(Status.INFO, "EXCEPTION = [" + result.getThrowable().getMessage() + "]");
if (!getTestParams(result).isEmpty()) {
if (result.getAttribute("testBitmap") != null) {
test.log(Status.INFO, "SCREENSHOT", MediaEntityBuilder.createScreenCaptureFromPath(bitmapDir + result.getAttribute("testBitmap")).build());
}
test.log(Status.INFO, "STACKTRACE" + getStrackTrace(result));
}
}
}
}
}
private Date getTime(long millis) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(millis);
return calendar.getTime();
}
private String getTestParams(ITestResult tr) throws Exception {
TestNG_ConsoleRunner runner = new TestNG_ConsoleRunner();
return runner.getTestParams(tr);
}
private String getStrackTrace(ITestResult result) {
Writer writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
result.getThrowable().printStackTrace(printWriter);
return "<br/>\n" + writer.toString().replace(System.lineSeparator(), "<br/>\n");
}
}
该类实现了
IReporter
接口,用于生成详细的测试报告。其主要流程如下:
1.
generateReport
方法
:遍历所有测试套件,调用
init
方法初始化报告,然后处理每个套件的测试结果,最后刷新报告。
2.
init
方法
:创建报告目录,配置 HTML 报告的属性,设置系统信息,并将 HTML 报告附加到
ExtentReports
实例中。
3.
processTestResults
方法
:收集测试结果,对结果进行排序,根据测试参数创建测试用例,设置测试用例的开始和结束时间,分配测试类别,根据测试状态设置颜色,并记录测试信息。
2.2 extent-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<extentreports>
<configuration>
<theme>standard</theme>
<encoding>UTF-8</encoding>
<protocol>https</protocol>
<documentTitle></documentTitle>
<reportName>
<![CDATA[
]]>
</reportName>
<testViewChartLocation>bottom</testViewChartLocation>
<reportHeadline></reportHeadline>
<dateFormat>MM-dd-yyyy</dateFormat>
<timeFormat>HH:mm:ss</timeFormat>
<scripts>
<![CDATA[
$(document).ready(function() {
$('.waves-effect:nth-child(3) a:nth-child(1) i:nth-child(1)').click();
});
]]>
</scripts>
<styles>
<![CDATA[
.extent {font-size: 12px; font-family: Helvetica Neue, Helvetica, Arial, sans-serif;}
.nav-wrapper {background: linear-gradient(to left, white 0%, #1a75ff 100%);}
.side-nav.fixed.hide-on-med-and-down {background: linear-gradient(to top, white 0%, #1a75ff 100%);}
.logo-container {background: linear-gradient(to bottom,white 0%, #1a75ff 100%);}
.brand-logo {background: linear-gradient(to right, blue 0%, #1a75ff 100%);}
.label.suite-start-time {background: linear-gradient(to bottom, red 100%, red 100%);}
.s2:nth-child(3) .card-panel:nth-child(1) {background-color: #00af00;}
.s2:nth-child(4) .card-panel:nth-child(1) {background-color: #F7464A;}
.status.pass {color: green;}
.status.fail {color: red;}
.status.skip {color: #1e90ff;}
.test-status.skip {color: #1e90ff;}
.test-status.right.skip {color: #1e90ff;}
.label.others {background-color: #1e90ff;}
.teal-text > i:nth-child(1) {color: #1e90ff;}
.category-content .category-status-counts:nth-child(3) {background-color: #1e90ff;}
.yellow.darken-2 {background-color: #1e90ff !important;}
]]>
</styles>
</configuration>
</extentreports>
这个 XML 文件用于配置报告的外观和行为,包括主题、编码、协议、日期和时间格式,以及自定义的 JavaScript 和 CSS 样式。
3. 页面对象模型(Page Object Model)
页面对象模型是一种设计模式,它将页面元素和操作封装在类中,提高了测试代码的可维护性和可复用性。这里我们有两个相关的类:
PassionTeaCoBasePO.java
和
PassionTeaCoWelcomePO.java
。
3.1 PassionTeaCoBasePO.java
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import static org.testng.Assert.assertEquals;
/**
* @author Carl Cocchiaro
*
* Passion Tea Company Base Page Object Class
*
*/
public abstract class PassionTeaCoBasePO<M extends WebElement> {
protected String pageTitle = "";
public PassionTeaCoBasePO() throws Exception {
PageFactory.initElements(CreateDriver.getInstance().getDriver(), this);
}
@FindBy(css = "img[src*='01e56eb76d18b60c5fb3dcf451c080a1']")
protected M passionTeaCoImg;
@FindBy(css = "img[src*='ab7db4b80e0c0644f5f9226f2970739b']")
protected M leafImg;
@FindBy(css = "img[src*='cd390673d46bead889c368ae135a6ec2']")
protected M organicImg;
@FindBy(css = "a[href='welcome.html']")
protected M welcome;
@FindBy(css = "(//a[@href='menu.html'])[2]")
protected M menu;
@FindBy(css = "a[href='our-passion.html']")
protected M ourPassion;
@FindBy(css = "a[href='let-s-talk-tea.html']")
protected M letsTalkTea;
@FindBy(css = "a[href='check-out.html']")
protected M checkOut;
@FindBy(css = "//p[contains(text(),'Copyright')]")
protected M copyright;
protected abstract void setTitle(String pageTitle);
protected abstract String getTitle();
public void verifyTitle(String title) throws AssertionError {
WebDriver driver = CreateDriver.getInstance().getDriver();
assertEquals(driver.getTitle(), title, "Verify Page Title");
}
public void navigate(String page) throws Exception {
WebDriver driver = CreateDriver.getInstance().getDriver();
BrowserUtils.waitForClickable(By.xpath("//a[contains(text(),'" + page + "')]"), Global_VARS.TIMEOUT_MINUTE);
driver.findElement(By.xpath("//a[contains(text(),'" + page + "')]")).click();
BrowserUtils.waitFor(this.getTitle(), Global_VARS.TIMEOUT_ELEMENT);
}
public void loadPage(String url, int timeout) throws Exception {
WebDriver driver = CreateDriver.getInstance().getDriver();
driver.navigate().to(url);
BrowserUtils.waitForURL(Global_VARS.TARGET_URL, timeout);
}
public void verifySpan(String pattern, String text) throws AssertionError {
String getText = null;
WebDriver driver = CreateDriver.getInstance().getDriver();
getText = driver.findElement(By.xpath("//span[contains(text(),'" + pattern + "')]")).getText();
assertEquals(getText, text, "Verify Span Text");
}
public void verifyHeading(String pattern, String text) throws AssertionError {
String getText = null;
WebDriver driver = CreateDriver.getInstance().getDriver();
getText = driver.findElement(By.xpath("//h1[contains(text(),'" + pattern + "')]")).getText();
assertEquals(getText, text, "Verify Heading Text");
}
public void verifyParagraph(String pattern, String text) throws AssertionError {
String getText = null;
WebDriver driver = CreateDriver.getInstance().getDriver();
getText = driver.findElement(By.xpath("//p[contains(text(),'" + pattern + "')]")).getText();
assertEquals(getText, text, "Verify Paragraph Text");
}
}
这是一个抽象类,定义了 Passion Tea Company 页面的基本元素和操作方法,包括验证页面标题、导航到不同页面、加载页面以及验证页面文本等。
3.2 PassionTeaCoWelcomePO.java
import org.openqa.selenium.*;
import org.openqa.selenium.support.FindBy;
import static org.testng.Assert.assertEquals;
/**
* @author Carl Cocchiaro
*
* Passion Tea Company Welcome Sub-class Page Object Class
*
*/
public class PassionTeaCoWelcomePO<M extends WebElement> extends PassionTeaCoBasePO<M> {
private static final String WELCOME_TITLE = "Welcome";
private static final String MENU_TITLE = "Menu";
protected static enum WELCOME_PAGE_IMG {
PASSION_TEA_CO, LEAF, ORGANIC, TEA_CUP, HERBAL_TEA, LOOSE_TEA, FLAVORED_TEA
}
protected static enum MENU_LINKS {
MENU, MORE_1, MORE_2, HERBAL_TEA, LOOSE_TEA, FLAVORED_TEA, SEE_COLLECTION1, SEE_COLLECTION2, SEE_COLLECTION3
}
public PassionTeaCoWelcomePO() throws Exception {
super();
setTitle(WELCOME_TITLE);
}
@FindBy(css = "img[src*='7cbbd331e278a100b443a12aa4cce77b']")
protected M teaCupImg;
@FindBy(xpath = "//h1[contains(text(),'We're passionate about tea')]")
protected M caption;
@FindBy(xpath = "//span[contains(text(),'For more than 25 years')]")
protected M paragraph;
@FindBy(css = "a[href='http://www.seleniumframework.com']")
protected M seleniumFramework;
@FindBy(xpath = "//span[.='Herbal Tea']")
protected M herbalTea;
@FindBy(xpath = "//span[.='Loose Tea']")
protected M looseTea;
@FindBy(xpath = "//span[.='Flavored Tea']")
protected M flavoredTea;
@FindBy(css = "img[src*='d892360c0e73575efa3e5307c619db41']")
protected M herbalTeaImg;
@FindBy(css = "img[src*='18f9b21e513a597e4b8d4c805321bbe3']")
protected M looseTeaImg;
@FindBy(css = "img[src*='d0554952ea0bea9e79bf01ab564bf666']")
protected M flavoredTeaImg;
@FindBy(xpath = "(//span[contains(@class,'button-content')])[1]")
protected M flavoredTeaCollect;
@FindBy(xpath = "(//span[contains(@class,'button-content')])[2]")
protected M herbalTeaCollect;
@FindBy(xpath = "(//span[contains(@class,'button-content')])[3]")
protected M looseTeaCollect;
@Override
protected void setTitle(String pageTitle) {
this.pageTitle = pageTitle;
}
@Override
public String getTitle() {
return this.pageTitle;
}
public void verifyImgSrc(WELCOME_PAGE_IMG img, String src) throws AssertionError {
String getText = null;
switch (img) {
case PASSION_TEA_CO:
getText = passionTeaCoImg.getAttribute("src");
break;
case LEAF:
getText = leafImg.getAttribute("src");
break;
case ORGANIC:
getText = organicImg.getAttribute("src");
break;
case TEA_CUP:
getText = teaCupImg.getAttribute("src");
break;
case HERBAL_TEA:
getText = herbalTeaImg.getAttribute("src");
break;
case LOOSE_TEA:
getText = looseTeaImg.getAttribute("src");
break;
case FLAVORED_TEA:
getText = flavoredTeaImg.getAttribute("src");
break;
}
assertEquals(getText, src, "Verify Image Source");
}
public void navigateMenuLink(MENU_LINKS link, String title) throws Exception {
String index = null;
WebDriver driver = CreateDriver.getInstance().getDriver();
switch (link) {
case HERBAL_TEA:
index = "1";
break;
case MENU:
index = "2";
break;
case SEE_COLLECTION3:
index = "3";
break;
case MORE_2:
index = "4";
break;
case MORE_1:
index = "5";
break;
case LOOSE_TEA:
index = "6";
break;
case SEE_COLLECTION1:
index = "7";
break;
case SEE_COLLECTION2:
index = "8";
break;
case FLAVORED_TEA:
index = "9";
break;
}
String query = "(//a[@href='menu.html'])" + "[" + index + "]";
try {
driver.findElement(By.xpath(query)).click();
BrowserUtils.waitFor(MENU_TITLE, Global_VARS.TIMEOUT_ELEMENT);
} catch (TimeoutException e) {
BrowserUtils.click(By.xpath(query));
BrowserUtils.waitFor(MENU_TITLE, Global_VARS.TIMEOUT_ELEMENT);
}
assertEquals(MENU_TITLE, title, "Navigate Menu Link");
}
}
该类继承自
PassionTeaCoBasePO
,是欢迎页面的具体实现,包含了欢迎页面的特定元素和操作方法,如验证图片源和导航菜单链接等。
4. 测试类与数据文件
测试类
PassionTeaCoTest.java
用于执行具体的测试用例,而
PassionTeaCo.json
文件则提供了测试所需的数据。
4.1 PassionTeaCoTest.java
import com.framework.ux.utils.chapter10.PassionTeaCoWelcomePO.WELCOME_PAGE_IMG;
import com.framework.ux.utils.chapter10.PassionTeaCoWelcomePO.MENU_LINKS;
import org.json.simple.JSONObject;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.annotations.*;
import org.testng.annotations.Optional;
/**
* @author Carl Cocchiaro
*
* Passion Tea Co Test Class
*
*/
public class PassionTeaCoTest {
private PassionTeaCoWelcomePO<WebElement> welcome = null;
private static final String DATA_FILE = "src/main/java/com/framework/ux/utils/chapter10/PassionTeaCo.json";
public PassionTeaCoTest() throws Exception {
}
@Parameters({"environment"})
@BeforeSuite(alwaysRun = true, enabled = true)
protected void suiteSetup(@Optional(Global_VARS.ENVIRONMENT) String environment, ITestContext context) throws Exception {
Global_VARS.DEF_ENVIRONMENT = System.getProperty("environment", environment);
Global_VARS.SUITE_NAME = context.getSuite().getXmlSuite().getName();
}
@AfterSuite(alwaysRun = true, enabled = true)
protected void suiteTeardown() throws Exception {
}
@Parameters({"browser", "platform", "includePattern", "excludePattern"})
@BeforeTest(alwaysRun = true, enabled = true)
protected void testSetup(@Optional(Global_VARS.BROWSER) String browser,
@Optional(Global_VARS.PLATFORM) String platform,
@Optional String includePattern,
@Optional String excludePattern,
ITestContext ctxt) throws Exception {
if (includePattern != null) {
System.setProperty("includePattern", includePattern);
}
if (excludePattern != null) {
System.setProperty("excludePattern", excludePattern);
}
Global_VARS.DEF_BROWSER = System.getProperty("browser", browser);
Global_VARS.DEF_PLATFORM = System.getProperty("platform", platform);
CreateDriver.getInstance().setDriver(Global_VARS.DEF_BROWSER, Global_VARS.DEF_PLATFORM, Global_VARS.DEF_ENVIRONMENT);
}
@AfterTest(alwaysRun = true, enabled = true)
protected void testTeardown() throws Exception {
CreateDriver.getInstance().closeDriver();
}
@BeforeClass(alwaysRun = true, enabled = true)
protected void testClassSetup(ITestContext context) throws Exception {
welcome = new PassionTeaCoWelcomePO<WebElement>();
JSONDataProvider.dataFile = DATA_FILE;
welcome.loadPage(Global_VARS.TARGET_URL, Global_VARS.TIMEOUT_MINUTE);
}
@AfterClass(alwaysRun = true, enabled = true)
protected void testClassTeardown(ITestContext context) throws Exception {
}
@BeforeMethod(alwaysRun = true, enabled = true)
protected void testMethodSetup(ITestResult result) throws Exception {
}
@AfterMethod(alwaysRun = true, enabled = true)
protected void testMethodTeardown(ITestResult result) throws Exception {
WebDriver driver = CreateDriver.getInstance().getDriver();
if (!driver.getCurrentUrl().contains("welcome.html")) {
welcome.setTitle("Welcome");
welcome.navigate("Welcome");
}
}
@Test(groups = {"PASSION_TEA"},
dataProvider = "fetchData_JSON",
dataProviderClass = JSONDataProvider.class,
enabled = true)
public void tc001_passionTeaCo(String rowID,
String description,
JSONObject testData) throws Exception {
welcome.setTitle(testData.get("title").toString());
welcome.navigate(testData.get("menu").toString());
welcome.verifyTitle(testData.get("title").toString());
}
@Test(groups = {"PASSION_TEA"},
dataProvider = "fetchData_JSON",
dataProviderClass = JSONDataProvider.class,
enabled = true)
public void tc002_passionTeaCo(String rowID,
String description,
JSONObject testData) throws Exception {
welcome.verifyImgSrc(WELCOME_PAGE_IMG.valueOf(testData.get("img").toString()), testData.get("src").toString());
}
@Test(groups = {"PASSION_TEA"},
dataProvider = "fetchData_JSON",
dataProviderClass = JSONDataProvider.class,
enabled = true)
public void tc003_passionTeaCo(String rowID,
String description,
JSONObject testData) throws Exception {
welcome.verifySpan(testData.get("pattern").toString(), testData.get("text").toString());
}
@Test(groups = {"PASSION_TEA"},
dataProvider = "fetchData_JSON",
dataProviderClass = JSONDataProvider.class,
enabled = true)
public void tc004_passionTeaCo(String rowID,
String description,
JSONObject testData) throws Exception {
welcome.verifyHeading(testData.get("pattern").toString(), testData.get("text").toString());
}
@Test(groups = {"PASSION_TEA"},
dataProvider = "fetchData_JSON",
dataProviderClass = JSONDataProvider.class,
enabled = true)
public void tc005_passionTeaCo(String rowID,
String description,
JSONObject testData) throws Exception {
welcome.verifyParagraph(testData.get("pattern").toString(), testData.get("text").toString());
}
@Test(groups = {"PASSION_TEA"},
dataProvider = "fetchData_JSON",
dataProviderClass = JSONDataProvider.class,
enabled = true)
public void tc006_passionTeaCo(String rowID,
String description,
JSONObject testData) throws Exception {
welcome.navigateMenuLink(MENU_LINKS.valueOf(testData.get("element").toString()), testData.get("title").toString());
}
}
该测试类包含了多个测试方法,通过数据驱动的方式从
PassionTeaCo.json
文件中获取测试数据,执行不同的测试场景,如页面导航、图片源验证、文本验证等。
4.2 PassionTeaCo.json
{
"tc001_passionTeaCo": [
{
"rowID": "tc001_passionTeaCo.01",
"description": "Navigate Passion Tea Co 'Welcome' Page",
"menu": "Welcome",
"title": "Welcome"
},
{
"rowID": "tc001_passionTeaCo.02",
"description": "Navigate Passion Tea Co 'Our Passion' Page",
"menu": "Our Passion",
"title": "Our Passion"
},
{
"rowID": "tc001_passionTeaCo.03",
"description": "Navigate Passion Tea Co 'Menu' Page",
"menu": "Menu",
"title": "Menu"
},
{
"rowID": "tc001_passionTeaCo.04",
"description": "Navigate Passion Tea Co 'Let's Talk Tea' Page",
"menu": "Talk Tea",
"title": "Let's Talk Tea"
},
{
"rowID": "tc001_passionTeaCo.05",
"description": "Navigate Passion Tea Co 'Check Out' Page",
"menu": "Check Out",
"title": "Check Out"
}
],
"tc002_passionTeaCo": [
{
"rowID": "tc002_passionTeaCo.01",
"description": "Verify Image Source 'TEA CUP'",
"img": "TEA_CUP",
"src": "http://nebula.wsimg.com/7cbbd331e278a100b443a12aa4cce77b? AccessKeyId=7ECBEB9592E2269F1812&disposition=0&alloworigin=1"
},
{
"rowID": "tc002_passionTeaCo.02",
"description": "Verify Image Source 'HERBAL TEA'",
"img": "HERBAL_TEA",
"src": "http://nebula.wsimg.com/d892360c0e73575efa3e5307c619db41? AccessKeyId=7ECBEB9592E2269F1812&disposition=0&alloworigin=1"
},
{
"rowID": "tc002_passionTeaCo.03",
"description": "Verify Image Source 'LOOSE TEA'",
"img": "LOOSE_TEA",
"src": "http://nebula.wsimg.com/18f9b21e513a597e4b8d4c805321bbe3? AccessKeyId=7ECBEB9592E2269F1812&disposition=0&alloworigin=1"
},
{
"rowID": "tc002_passionTeaCo.04",
"description": "Verify Image Source 'FLAVORED TEA'",
"img": "FLAVORED_TEA",
"src": "http://nebula.wsimg.com/d0554952ea0bea9e79bf01ab564bf666? AccessKeyId=7ECBEB9592E2269F1812&disposition=0&alloworigin=1"
},
{
"rowID": "tc002_passionTeaCo.05",
"description": "Verify Image Source 'PASSION TEA CO'",
"img": "PASSION_TEA_CO",
"src": "http://nebula.wsimg.com/01e56eb76d18b60c5fb3dcf451c080a1? AccessKeyId=7ECBEB9592E2269F1812&disposition=0&alloworigin=1"
},
{
"rowID": "tc002_passionTeaCo.06",
"description": "Verify Image Source 'LEAF'",
"img": "LEAF",
"src": "http://nebula.wsimg.com/ab7db4b80e0c0644f5f9226f2970739b? AccessKeyId=7ECBEB9592E2269F1812&disposition=0&alloworigin=1"
},
{
"rowID": "tc002_passionTeaCo.07",
"description": "Verify Image Source 'ORGANIC'",
"img": "ORGANIC",
"src": "http://nebula.wsimg.com/cd390673d46bead889c368ae135a6ec2? AccessKeyId=7ECBEB9592E2269F1812&disposition=0&alloworigin=1"
}
],
"tc003_passionTeaCo": [
{
"rowID": "tc003_passionTeaCo.01",
"description": "Verify Span Text 'See our line of organic teas.'",
"pattern": "See our line",
"text": "See our line of organic teas."
},
{
"rowID": "tc003_passionTeaCo.02",
"description": "Verify Span Text 'Tea of the month club'",
"pattern": "month club",
"text": "Tea of the month club"
},
{
"rowID": "tc003_passionTeaCo.03",
"description": "Verify Span Text 'It's the gift that keeps on giving all year long.'",
"pattern": "gift that keeps on giving",
"text": "It's the gift that keeps on giving all year long."
},
{
"rowID": "tc003_passionTeaCo.04",
"description": "Verify Span Text 'For more than 25 years...'",
"pattern": "For more than 25 years, Passion Tea Company has revolutionized the tea industry",
"text": "For more than 25 years, Passion Tea Company has revolutionized the tea industry by letting our customers create a blend that combines their favorite herbs and spices. We offer thousands of natural flavors from all over the world and want you to have the opportunity to create a tea, and call it yours! We proudly partner with seleniumframework.com to help them use our website for Continuous Test Automation practice exercises "
},
{
"rowID": "tc003_passionTeaCo.05",
"description": "Verify Span Text 'Herbal Tea'",
"pattern": "Herbal Tea",
"text": "Herbal Tea"
},
{
"rowID": "tc003_passionTeaCo.06",
"description": "Verify Span Text 'Loose Tea'",
"pattern": "Loose Tea",
"text": "Loose Tea"
},
{
"rowID": "tc003_passionTeaCo.07",
"description": "Verify Span Text 'Flavored Tea'",
"pattern": "Flavored Tea",
"text": "Flavored Tea."
}
],
"tc004_passionTeaCo": [
{
"rowID": "tc004_passionTeaCo.01",
"description": "Verify Heading Text 'We're passionate about tea.'",
"pattern": "passionate about tea",
"text": "We're passionate about tea. "
}
],
"tc005_passionTeaCo": [
{
"rowID": "tc005_passionTeaCo.01",
"description": "Verify Paragraph Text 'Copyright...'",
"pattern": "Copyright",
"text": "Copyright Selenium Practice Website. All rights reserved."
}
],
"tc006_passionTeaCo": [
{
"rowID": "tc006_passionTeaCo.01",
"description": "Verify Menu Link Text 'MENU'",
"element": "MENU",
"title": "Menu"
},
{
"rowID": "tc006_passionTeaCo.02",
"description": "Verify Menu Link Text 'MORE 1'",
"element": "MORE_1",
"title": "Menu"
},
{
"rowID": "tc006_passionTeaCo.03",
"description": "Verify Menu Link Text 'MORE 2'",
"element": "MORE_2",
"title": "Menu"
},
{
"rowID": "tc006_passionTeaCo.04",
"description": "Verify Menu Link Text 'HERBAL TEA'",
"element": "HERBAL_TEA",
"title": "Menu"
},
{
"rowID": "tc006_passionTeaCo.05",
"description": "Verify Menu Link Text 'LOOSE TEA'",
"element": "LOOSE_TEA",
"title": "Menu"
},
{
"rowID": "tc006_passionTeaCo.06",
"description": "Verify Menu Link Text 'FLAVORED TEA'",
"element": "FLAVORED_TEA",
"title": "Menu"
},
{
"rowID": "tc006_passionTeaCo.07",
"description": "Verify Menu Link Text 'SEE COLLECTION 1'",
"element": "SEE_COLLECTION1",
"title": "Menu"
},
{
"rowID": "tc006_passionTeaCo.08",
"description": "Verify Menu Link Text 'SEE COLLECTION 2'",
"element": "SEE_COLLECTION2",
"title": "Menu"
},
{
"rowID": "tc006_passionTeaCo.09",
"description": "Verify Menu Link Text 'SEE COLLECTION 3'",
"element": "SEE_COLLECTION3",
"title": "Menu"
}
]
}
这个 JSON 文件为每个测试方法提供了相应的测试数据,包括行 ID、描述、菜单选项、页面标题、图片源、文本模式和验证文本等。
5. 总结
通过以上的配置文件、报告生成类、页面对象模型和测试类,我们构建了一个完整的自动化测试框架。这个框架可以帮助我们更高效地进行 Web 应用的测试,提高测试的覆盖率和准确性。同时,通过数据驱动的方式,我们可以轻松地扩展测试用例,适应不同的测试场景。
以下是一个简单的测试执行流程的 mermaid 流程图:
graph LR
A[开始] --> B[套件初始化]
B --> C[测试初始化]
C --> D[类初始化]
D --> E[方法初始化]
E --> F[执行测试用例]
F --> G[方法清理]
G --> H{是否还有测试用例}
H -- 是 --> E
H -- 否 --> I[类清理]
I --> J[测试清理]
J --> K[套件清理]
K --> L[结束]
这个流程图展示了测试执行的主要步骤,从套件初始化开始,依次进行测试、类和方法的初始化,执行测试用例,再进行相应的清理工作,最后结束测试。
在实际应用中,我们可以根据具体的需求对这个框架进行进一步的扩展和优化,例如添加更多的测试方法、优化报告格式、调整页面元素定位方式等。希望这个框架能为你的自动化测试工作带来便利和帮助。
自动化测试实战:Selenium 与相关工具的综合应用
6. 框架优势分析
这个自动化测试框架具有多方面的优势,以下为详细分析:
-
可维护性高
:页面对象模型将页面元素和操作封装在类中,当页面元素发生变化时,只需修改对应的页面对象类,而不需要修改大量的测试代码。例如,若
PassionTeaCoWelcomePO
页面中的某个图片元素的
src
属性发生变化,仅需在该类中更新相应的元素定位信息。
-
可复用性强
:页面对象类中的方法可以在多个测试用例中复用。如
PassionTeaCoBasePO
中的
navigate
方法,可用于在不同的测试用例中导航到不同的页面。
-
数据驱动
:通过
PassionTeaCo.json
文件提供测试数据,实现了测试代码和测试数据的分离。这样可以方便地添加、修改和删除测试数据,扩展测试用例。例如,若要增加一个新的页面导航测试用例,只需在
tc001_passionTeaCo
数组中添加一条新的数据记录。
-
详细的测试报告
:使用
ExtentReports
生成的测试报告包含了丰富的信息,如测试用例的执行结果、执行时间、异常信息和截图等,方便测试人员快速定位和解决问题。
7. 框架的可扩展性
为了使框架能够适应不同的测试需求,我们可以从以下几个方面进行扩展:
-
添加新的页面对象类
:当需要测试新的页面时,可以创建新的页面对象类。例如,若要测试
PassionTeaCo
的购物车页面,可以创建一个
PassionTeaCoCartPO
类,继承自
PassionTeaCoBasePO
,并实现购物车页面的特定元素和操作方法。
-
增加测试方法
:根据不同的测试场景,在
PassionTeaCoTest
类中添加新的测试方法。例如,添加一个测试购物车结算功能的方法。
@Test(groups = {"PASSION_TEA"},
dataProvider = "fetchData_JSON",
dataProviderClass = JSONDataProvider.class,
enabled = true)
public void tc007_passionTeaCo(String rowID,
String description,
JSONObject testData) throws Exception {
// 实现购物车结算功能的测试逻辑
}
-
优化报告格式
:可以通过修改
extent-config.xml文件,进一步定制测试报告的外观和功能。例如,添加更多的自定义样式,或者修改报告的布局。 - 集成其他工具 :可以将该框架与其他测试工具集成,如持续集成工具(Jenkins)、缺陷管理工具(JIRA)等,实现自动化测试的持续执行和缺陷的自动跟踪。
8. 框架的实际应用案例
以下是一个简单的表格,展示了使用该框架进行测试的部分测试用例及其执行结果:
| 测试用例 ID | 测试用例描述 | 执行结果 |
| — | — | — |
| tc001_passionTeaCo.01 | 导航到 Passion Tea Co ‘Welcome’ 页面 | 通过 |
| tc002_passionTeaCo.03 | 验证 ‘LOOSE TEA’ 图片源 | 通过 |
| tc003_passionTeaCo.04 | 验证特定 span 文本 | 通过 |
9. 总结与展望
这个自动化测试框架通过合理的配置和设计,实现了 Web 应用的自动化测试。它利用页面对象模型提高了代码的可维护性和可复用性,通过数据驱动的方式方便了测试用例的扩展,同时使用
ExtentReports
生成详细的测试报告。
在未来的发展中,我们可以进一步优化这个框架。例如,引入更智能的元素定位策略,提高元素定位的准确性和稳定性;集成更多的测试工具,实现更全面的测试覆盖;加强对异步操作的处理,提高测试的可靠性。
以下是一个框架未来发展方向的 mermaid 流程图:
graph LR
A[现有框架] --> B[优化元素定位策略]
A --> C[集成更多测试工具]
A --> D[加强异步操作处理]
B --> E[提高定位准确性和稳定性]
C --> F[实现更全面测试覆盖]
D --> G[提高测试可靠性]
E --> H[更高效的测试框架]
F --> H
G --> H
这个流程图展示了框架未来的主要发展方向,通过对元素定位、工具集成和异步操作处理的优化,最终实现一个更高效、更可靠的自动化测试框架。
总之,这个自动化测试框架为 Web 应用的测试提供了一个坚实的基础,通过不断的优化和扩展,它将能够更好地满足各种复杂的测试需求,为软件质量的保障提供有力支持。
超级会员免费看

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



