霍格沃兹测试开发学社
ceshiren.com
http://litemall.hogwarts.ceshiren.com
@startuml
scale 400 height
autonumber
participant 登录页面 as login
participant 首页 as home
participant 类目列表页面 as cate_list
participant 创建类目页面 as cate_create
login -> home: 用户登录
home -> cate_list: 进入商品类目
cate_list -> cate_create: 点击添加
cate_create -> cate_list: 创建类目
cate_list -> cate_list: 获取创建结果
@enduml
@startuml
scale 400 height
autonumber
participant 登录页面 as login
participant 首页 as home
participant 类目列表页面 as cate_list
participant 创建类目页面 as cate_create
login -> home: 用户登录
home -> cate_list: 进入商品类目
cate_list -> cate_create: 点击添加
cate_create -> cate_list: 创建类目
cate_list -> cate_list: 点击删除
cate_list -> cate_list: 获取删除结果
@enduml
"""
__author__ = '霍格沃兹测试开发学社'
__desc__ = '更多测试开发技术探讨,请访问:https://ceshiren.com/t/topic/15860'
"""
import time
import allure
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from utils.log_utils import logger
class TestLitemall:
# 前置动作
def setup_class(self):
self.driver = webdriver.Chrome()
self.driver.implicitly_wait(3)
# 登录
self.driver.get("http://litemall.hogwarts.ceshiren.com/")
# 问题,输入框内有默认值,此时send——keys不回清空只会追加
# 解决方案: 在输入信息之前,先对输入框完成清空
# 输入用户名密码
self.driver.find_element(By.NAME, "username").clear()
self.driver.find_element(By.NAME, "username").send_keys("manage")
self.driver.find_element(By.NAME, "password").clear()
self.driver.find_element(By.NAME, "password").send_keys("manage123")
# 点击登录按钮
self.driver.find_element(By.CSS_SELECTOR,
".el-button--primary").click()
# 窗口最大化
self.driver.maximize_window()
# 后置动作
def teardown_class(self):
self.driver.quit()
def get_screen(self):
timestamp = int(time.time())
# 注意:!! 一定要提前创建好images 路径
image_path = f"./images/image_{timestamp}.PNG"
# 截图
self.driver.save_screenshot(image_path)
# 讲截图放到报告的数据中
allure.attach.file(image_path, name="picture",
attachment_type=allure.attachment_type.PNG)
# 新增功能
def test_add_type(self):
# 点击商场管理/商品类目,进入商品类目页面
# 进入商品类目页面
self.driver.find_element(By.XPATH, "//*[text()='商场管理']").click()
self.driver.find_element(By.XPATH, "//*[text()='商品类目']").click()
# 添加商品类目操作
self.driver.find_element(By.XPATH, "//*[text()='添加']").click()
self.driver.find_element(By.CSS_SELECTOR,
".el-input__inner").send_keys("新增商品测试")
#==============显示等待优化方案2: 自定义显式等待条件
def click_exception(by, element, max_attempts=5):
def _inner(driver):
# 多次点击按钮
actul_attempts = 0 # 实际点击次数
while actul_attempts<max_attempts:
# 进行点击操作
actul_attempts += 1 # 每次循环,实际点击次数加1
try:
# 如果点击过程报错,则直接执行 except 逻辑,并切继续循环
# 没有报错,则直接return 循环结束
driver.find_element(by, element).click()
return True
except Exception:
logger.debug("点击的时候出现了一次异常")
# 当实际点击次数大于最大点击次数时,结束循环并抛出异常
raise Exception("超出了最大点击次数")
# return _inner() 错误写法
return _inner
WebDriverWait(self.driver, 10).until(click_exception(By.CSS_SELECTOR,
".dialog-footer .el-button--primary"))
# ===========================使用显式等待优化
# 如果没找到,程序也不应该报错
res = self.driver.find_elements(By.XPATH,
"//*[text()='新增商品测试']")
self.get_screen()
# 数据的清理一定到放在断言操作之后完成,要不然可能会影响断言结果
self.driver.find_element(By.XPATH,
"//*[text()='新增商品测试']/../..//*[text()='删除']").click()
logger.info(f"断言获取到的实际结果为{res}")
# 断言产品新增后是否成功找到
assert res != []
# 删除功能
def test_delete_type(self):
# ================ 造数据步骤
# 点击商场管理/商品类目,进入商品类目页面
# 进入商品类目页面
self.driver.find_element(By.XPATH, "//*[text()='商场管理']").click()
self.driver.find_element(By.XPATH, "//*[text()='商品类目']").click()
# 添加商品类目操作
self.driver.find_element(By.XPATH, "//*[text()='添加']").click()
self.driver.find_element(By.CSS_SELECTOR,
".el-input__inner").send_keys("删除商品测试")
ele = WebDriverWait(self.driver,10).until(
expected_conditions.element_to_be_clickable(
(By.CSS_SELECTOR, ".dialog-footer .el-button--primary")))
ele.click()
# ============完成删除步骤
self.driver.find_element(By.XPATH,
"//*[text()='删除商品测试']/../..//*[text()='删除']").click()
# 断言
WebDriverWait(self.driver, 10).until_not(
expected_conditions.visibility_of_any_elements_located((By.XPATH,
"//*[text()='删除商品测试']")))
# 问题: 因为代码执行速度过快,元素还未消失就捕获了。
# 解决: 确认该元素不存在后,再捕获
res = self.driver.find_elements(By.XPATH,
"//*[text()='删除商品测试']")
logger.info(f"断言获取到的实际结果为{res}")
assert res == []
/*
* @Author: 霍格沃兹测试开发学社
* @Desc: '更多测试开发技术探讨,请访问:https://ceshiren.com/t/topic/15860'
*/
package com.ceshiren.litemall;
import io.qameta.allure.Allure;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.time.Duration;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class LitemallPlusTest {
private static WebDriver driver;
private static WebDriverWait wait;
private static FluentWait<WebDriver> fluentWait;
private static Logger logger;
// 前置
@BeforeAll
static void setUpClass() throws InterruptedException {
driver = new ChromeDriver();
wait = new WebDriverWait(driver, Duration.ofSeconds(10));
logger = LoggerFactory.getLogger(LitemallPlusTest.class);
fluentWait = new FluentWait<WebDriver>(driver).
withTimeout(Duration.ofSeconds(10)).
pollingEvery(Duration.ofMillis(500)).
ignoring(ElementClickInterceptedException.class);
// 隐式等待配置,不要加太长
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
logger.debug("开始自动化测试");
// 登录行为式所有用例执行前的准备工作,所以需要放到最前面, before all
driver.get("http://litemall.hogwarts.ceshiren.com/#/login");
driver.manage().window().maximize();
// 输入用户名密码
driver.findElement(By.name("username")).clear();
driver.findElement(By.name("username")).sendKeys("manage");
driver.findElement(By.name("password")).clear();
driver.findElement(By.name("password")).sendKeys("manage123");
driver.findElement(By.cssSelector("button")).click();
// 进入商品类目(被测页面)
logger.debug("切换到商品类目页面");
// Thread.sleep(1000);
wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//ul//*[text()='商场管理']")))
.click();
// driver.findElement(By.xpath("//ul//*[text()='商场管理']")).click();
driver.findElement(By.xpath("//ul//*[text()='商品类目']")).click();
}
// 后置
@AfterAll
static void tearDownClass(){
driver.quit();
}
// 保存截图
void saveScreen(){
try{
// 生成时间戳
long nowTime = System.currentTimeMillis();
String fileName = "./files/"+nowTime+".png";
// 生成截图文件
File currentScreen = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(currentScreen, new File(fileName));
// 将截图文件配置到allure中
Allure.addAttachment("picture", "image/png", new FileInputStream(fileName), ".jpg");
}catch (Exception e){
logger.warn("截图错误"+e);
}
}
@Test
void addProductType() throws InterruptedException {
driver.findElement(By.cssSelector(".el-icon-edit")).click();
driver.findElement(By.cssSelector(".el-form-item:nth-child(1) input")).sendKeys("测试添加商品");
fluentWait.until(driver1->{
driver1.findElement(By.cssSelector(".el-dialog__footer .el-button--primary")).click();
return driver.findElement(By.cssSelector(".el-notification__title"));
});
List<WebElement> eles = driver.findElements(By.xpath("//*[text()='测试添加商品']"));
driver.findElement(By.xpath("//*[text()='测试添加商品']/../..//*[text()='删除']")).click();
logger.info("断言获取到的元素的大小为"+eles.size());
assertEquals(1, eles.size());
}
@Test
void deleteProductType() throws InterruptedException {
driver.findElement(By.cssSelector(".el-icon-edit")).click();
driver.findElement(By.cssSelector(".el-form-item:nth-child(1) input")).sendKeys("测试删除商品");
fluentWait.until(driver1->{
driver1.findElement(By.cssSelector(".el-dialog__footer .el-button--primary")).click();
return driver.findElement(By.cssSelector(".el-notification__title"));
});
driver.findElement(By.xpath("//*[text()='测试删除商品']/../..//*[text()='删除']")).click();
wait.until(ExpectedConditions.invisibilityOfElementLocated(By.xpath("//*[text()='测试删除商品']")));
List<WebElement> eles = driver.findElements(By.xpath("//*[text()='测试删除商品']"));
logger.info("断言获取到的元素的大小为"+eles.size());
saveScreen();
assertEquals(0, eles.size());
}
}
@startmindmap
* HowToDo
** 梳理测试用例
*** 业务操作流程
*** 前置后置动作
** 构造PO模型
*** 页面类和方法
** 编写测试用例
*** 链式调用
** 业务具体实现
*** 封装BasePage
*** 实现页面方法
*** 封装页面元素
@endmindmap
@startuml
scale 400 height
autonumber
participant 登录页面 as login
participant 首页 as home
participant 类目列表页面 as cate_list
participant 创建类目页面 as cate_create
login -> home: 用户登录
home -> cate_list: 进入商品类目
cate_list -> cate_create: 点击添加
cate_create -> cate_list: 创建类目
cate_list -> cate_list: 获取操作结果
@enduml
@startuml
class BasePage{
构造方法
find()
finds()s
}
class LoginPage{
_BTN_LOGIN元素
login()
}
class HomePage{
_MENU_MALL_MANAGE元素
go_to_category()
}
class CategoryPage{
_BTN_ADD元素
get_list()
}
BasePage <|-- LoginPage: 继承
BasePage <|-- HomePage: 继承
BasePage <|-- CategoryPage: 继承
@enduml
"""
__author__ = '霍格沃兹测试开发学社'
__desc__ = '更多测试开发技术探讨,请访问:https://ceshiren.com/t/topic/15860'
"""
import time
import allure
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from utils.web_utils import click_exception
class BasePage:
_BASE_URL = ""
def __init__(self, base_driver=None):
if base_driver:
self.driver = base_driver
else:
self.driver = webdriver.Chrome()
self.driver.implicitly_wait(5)
self.driver.maximize_window()
if not self.driver.current_url.startswith("http"):
self.driver.get(self._BASE_URL)
def do_find(self, by, locator=None):
"""获取单个元素"""
if locator:
return self.driver.find_element(by, locator)
else:
return self.driver.find_element(*by)
def do_finds(self, by, locator=None):
"""获取多个元素"""
if locator:
return self.driver.find_elements(by, locator)
else:
return self.driver.find_elements(*by)
def do_send_keys(self, value, by, locator=None):
"""获取单个元素并输入内容"""
ele = self.do_find(by, locator)
ele.clear()
ele.send_keys(value)
def do_quit(self):
"""退出浏览器"""
self.driver.quit()
def wait_element_until_visible(self, locator: tuple):
return WebDriverWait(self.driver, 10).until(
expected_conditions.visibility_of_element_located(locator))
public class BasePage {
public WebDriver driver;
public Logger logger = LoggerFactory.getLogger(BasePage.class);
public String baseURL;
public BasePage() {
}
public BasePage(WebDriver baseDriver){
driver = baseDriver;
}
public BasePage(String url) {
baseURL = url;
driver = new ChromeDriver();
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
driver.get(baseURL);
}
public WebDriverWait waitFor(){
return new WebDriverWait(driver, Duration.ofSeconds(10));
}
public FluentWait<WebDriver> fluentWaitFor(){
return new FluentWait<>(driver).
withTimeout(Duration.ofSeconds(10)).
pollingEvery(Duration.ofMillis(500)).
ignoring(ElementClickInterceptedException.class);
}
public WebElement find(By by){
logger.info("查找的元素为"+by);
return driver.findElement(by);
}
public List<WebElement> finds(By by){
return driver.findElements(by);
}
public void quitDriver(){
driver.quit();
}
public void saveScreen(){
}
}