当前位置: 首页 > news >正文

第六篇:《Page Object设计模式:让UI测试代码可维护、可复用》

随着UI测试用例增多,你会发现大量重复的定位器和操作代码散落在各处。一旦页面元素发生变化,需要修改几十个地方。Page Object模式是业界公认的解决方案。本文将用最直观的方式,带你从“脚本式”代码重构为“页面对象式”代码,并分享进阶技巧。

一、什么是不好的UI测试代码?

假设我们要测试登录功能,没有使用Page Object的代码可能长这样:

publicclassLoginTest{@TestpublicvoidtestLoginSuccess(){WebDriverdriver=newChromeDriver();driver.get("https://example.com/login");driver.findElement(By.id("username")).sendKeys("testuser");driver.findElement(By.id("password")).sendKeys("123456");driver.findElement(By.cssSelector("button[type='submit']")).click();Assert.assertTrue(driver.findElement(By.id("welcome")).isDisplayed());driver.quit();}@TestpublicvoidtestLoginWrongPassword(){WebDriverdriver=newChromeDriver();driver.get("https://example.com/login");driver.findElement(By.id("username")).sendKeys("testuser");driver.findElement(By.id("password")).sendKeys("wrong");driver.findElement(By.cssSelector("button[type='submit']")).click();Assert.assertTrue(driver.findElement(By.className("error-msg")).isDisplayed());driver.quit();}}

问题:

定位器(如#username)在多个测试中重复

操作逻辑(输入用户名密码)重复

页面URL硬编码

如果登录页的id变了,需要修改所有测试方法

二、Page Object模式的核心思想

Page Object = 一个类对应一个页面,类中封装:

该页面的元素定位器(或WebElement对象)

该页面上的操作(方法)

返回其他Page Object的方法(用于流转)

原则:

测试用例只调用Page Object的方法,不直接操作WebDriver

Page Object的方法应返回有意义的东西(其他Page Object或断言数据)

不要在Page Object内部写断言,断言属于测试用例层

三、第一个Page Object:LoginPage

以登录页面为例。

Java版本

importorg.openqa.selenium.By;importorg.openqa.selenium.WebDriver;importorg.openqa.selenium.WebElement;importorg.openqa.selenium.support.FindBy;importorg.openqa.selenium.support.PageFactory;importorg.openqa.selenium.support.ui.ExpectedConditions;importorg.openqa.selenium.support.ui.WebDriverWait;publicclassLoginPage{privateWebDriverdriver;privateWebDriverWaitwait;// 方式1:使用By定位器privateByusernameInput=By.id("username");privateBypasswordInput=By.id("password");privateBysubmitButton=By.cssSelector("button[type='submit']");privateByerrorMsg=By.className("error-msg");// 方式2:使用@FindBy注解(需要PageFactory.initElements)@FindBy(id="username")privateWebElementusernameElem;publicLoginPage(WebDriverdriver){this.driver=driver;this.wait=newWebDriverWait(driver,Duration.ofSeconds(10));PageFactory.initElements(driver,this);// 初始化@FindBy}// 页面操作方法publicLoginPageenterUsername(Stringusername){wait.until(ExpectedConditions.visibilityOfElementLocated(usernameInput)).sendKeys(username);returnthis;// 返回自身支持链式调用}publicLoginPageenterPassword(Stringpassword){driver.findElement(passwordInput).sendKeys(password);returnthis;}publicHomePageclickSubmitSuccess(){driver.findElement(submitButton).click();returnnewHomePage(driver);// 成功后跳转到首页}publicLoginPageclickSubmitExpectingError(){driver.findElement(submitButton).click();returnthis;// 依然停留在登录页}publicStringgetErrorMessage(){returnwait.until(ExpectedConditions.visibilityOfElementLocated(errorMsg)).getText();}// 组合操作:完整登录流程publicHomePageloginAs(Stringusername,Stringpassword){returnenterUsername(username).enterPassword(password).clickSubmitSuccess();}}

Python版本

fromselenium.webdriver.common.byimportByfromselenium.webdriver.remote.webdriverimportWebDriverfromselenium.webdriver.support.uiimportWebDriverWaitfromselenium.webdriver.supportimportexpected_conditionsasECclassLoginPage:# 定位器(类属性)USERNAME_INPUT=(By.ID,"username")PASSWORD_INPUT=(By.ID,"password")SUBMIT_BUTTON=(By.CSS_SELECTOR,"button[type='submit']")ERROR_MSG=(By.CLASS_NAME,"error-msg")def__init__(self,driver:WebDriver):self.driver=driver self.wait=WebDriverWait(driver,10)defenter_username(self,username:str):self.wait.until(EC.visibility_of_element_located(self.USERNAME_INPUT)).send_keys(username)returnselfdefenter_password(self,password:str):self.driver.find_element(*self.PASSWORD_INPUT).send_keys(password)returnselfdefclick_submit_success(self):self.driver.find_element(*self.SUBMIT_BUTTON).click()from.home_pageimportHomePage# 避免循环导入returnHomePage(self.driver)defclick_submit_expecting_error(self):self.driver.find_element(*self.SUBMIT_BUTTON).click()returnselfdefget_error_message(self)->str:returnself.wait.until(EC.visibility_of_element_located(self.ERROR_MSG)).textdeflogin_as(self,username:str,password:str):returnself.enter_username(username).enter_password(password).click_submit_success()

四、HomePage示例

publicclassHomePage{privateWebDriverdriver;privateBywelcomeMsg=By.id("welcome");publicHomePage(WebDriverdriver){this.driver=driver;}publicbooleanisWelcomeDisplayed(){returndriver.findElement(welcomeMsg).isDisplayed();}publicStringgetWelcomeText(){returndriver.findElement(welcomeMsg).getText();}}

五、使用Page Object的测试用例

Java (TestNG):

importorg.testng.Assert;importorg.testng.annotations.Test;publicclassLoginTest{privateWebDriverdriver;privateLoginPageloginPage;@BeforeMethodpublicvoidsetup(){driver=newChromeDriver();driver.get("https://example.com/login");loginPage=newLoginPage(driver);}@TestpublicvoidtestLoginSuccess(){HomePagehome=loginPage.loginAs("testuser","correct123");Assert.assertTrue(home.isWelcomeDisplayed(),"登录后未显示欢迎信息");}@TestpublicvoidtestLoginWrongPassword(){loginPage.loginAs("testuser","wrong");Stringerror=loginPage.getErrorMessage();Assert.assertTrue(error.contains("密码错误"));}@AfterMethodpublicvoidteardown(){if(driver!=null)driver.quit();}}

Python (pytest):

importpytestfromseleniumimportwebdriverfrompages.login_pageimportLoginPageclassTestLogin:@pytest.fixturedefdriver(self):driver=webdriver.Chrome()driver.get("https://example.com/login")yielddriver driver.quit()deftest_login_success(self,driver):login_page=LoginPage(driver)home_page=login_page.login_as("testuser","correct123")asserthome_page.is_welcome_displayed()deftest_login_wrong_password(self,driver):login_page=LoginPage(driver)login_page.login_as("testuser","wrong")assert"密码错误"inlogin_page.get_error_message()

六、进阶技巧

6.1 页面对象中的等待封装
不要在每个方法里都写WebDriverWait,可以在Page Object基类中封装。

publicabstractclassBasePage{protectedWebDriverdriver;protectedWebDriverWaitwait;publicBasePage(WebDriverdriver){this.driver=driver;this.wait=newWebDriverWait(driver,Duration.ofSeconds(10));}protectedvoidwaitForVisibility(Bylocator){wait.until(ExpectedConditions.visibilityOfElementLocated(locator));}protectedvoidclick(Bylocator){wait.until(ExpectedConditions.elementToBeClickable(locator)).click();}}

6.2 页面对象之间的流转
方法返回新的Page Object,体现用户的操作流。

publicclassLoginPageextendsBasePage{publicHomePageloginSuccess(Stringuser,Stringpwd){// 输入并提交returnnewHomePage(driver);}}

6.3 使用PageFactory(Java专用)
PageFactory.initElements可以延迟定位元素,每次访问时重新查找,避免StaleElementReferenceException。但需注意配合@CacheLookup使用。

publicclassLoginPage{@FindBy(id="username")privateWebElementusernameInput;publicLoginPage(WebDriverdriver){PageFactory.initElements(driver,this);}publicvoidenterUsername(Stringtext){usernameInput.sendKeys(text);// 每次调用时定位}}

6.4 页面对象的单一职责
一个Page Object只对应一个页面或一个页面的大模块(如导航栏组件)。

不要将整个复杂页面的所有元素塞进一个类,可按组件拆分(如HeaderComponent、FooterComponent)。

七、Page Object的常见误区

八、总结与收益

采用Page Object模式后:

可维护性:页面修改只需改一个类,所有测试自动生效。

可复用性:多个测试可以共享同一个Page Object的方法。

可读性:测试代码读起来像业务剧本:loginPage.loginAs(“user”,“pwd”).clickSomething()。

并行开发:页面未完成时,可先定义接口,测试用例使用Mock。

作业:

http://www.jsqmd.com/news/688349/

相关文章:

  • 3分钟掌握星穹铁道抽卡数据分析,告别盲目氪金!
  • 链游革命2.0:源码开放与智能合约驱动的下一代游戏经济体
  • 如何快速提取Godot游戏资源:专业解包工具使用指南
  • 2026年乌鲁木齐房屋防水修缮服务商深度横评:从漏水诊断到质保承诺 - 优质企业观察收录
  • 3步快速恢复加密压缩包密码:ArchivePasswordTestTool实战指南
  • FlexASIO配置终极指南:从零开始掌握专业音频驱动调优
  • 大模型服务化落地卡点突破:基于CUDA 13 Stream Ordered Memory Allocator的动态batching算子框架(含GitHub Star≥1.2k的开源实现)
  • 2026年乌鲁木齐房屋防水修缮完全指南:从漏水诊断到官方服务商直达 - 优质企业观察收录
  • 2026年乌鲁木齐房屋防水修缮与阳台漏水维修完全指南 - 优质企业观察收录
  • 2026 年国内金丝楠木培育基地实力厂商汇总 适配工程与庭院种植实用参考 - 深度智识库
  • Xiaomi MiMo-V2.5 系列模型公测,推理速度更快、成本更低,还推订阅优惠!
  • 3分钟学会:ChanlunX缠论插件如何帮你自动识别股票买卖点
  • 2026 年 4 月女鞋采购指南:单鞋、高跟鞋、增高鞋、内增高鞋、长靴、短靴、尖头鞋、芭蕾舞鞋、凉鞋优质供货厂家推荐 - 海棠依旧大
  • 2026年Q2最新控制电缆品牌排名:全国权威推荐TOP5 - 安互工业信息
  • 快速上手Z-Image-Turbo:5分钟教程,让你成为AI绘画高手
  • 从公式到代码:手把手教你用Python实现CIDEr指标(附避坑指南)
  • 地平线首款舱驾融合芯片即将量产;速腾聚创发布创世架构推出双旗舰感知芯片;多项固态电池技术重大突破;蔡司研发全息透明显示技术
  • 2026 GPON OLT厂家性价比排行解读:国内高性价比靠谱品牌推荐 - 博客湾
  • 2026年乌鲁木齐房屋漏水维修:防水修缮专业服务商深度评测与选购指南 - 优质企业观察收录
  • AI Agent公司集体转型:从“卖铲子”到下场做漫剧,内容为王时代已至!
  • 多智能体博弈:竞争、协商与合约机制
  • 全网都在谈的网络安全,到底是什么?一篇讲透核心逻辑与未来趋势
  • 小程序富文本渲染难题如何解决?mp-html组件实战指南
  • 2026年乌鲁木齐房屋修缮与防水维修:从漏水诊断到屋面防水的完整解决方案 - 优质企业观察收录
  • Zotero Better BibTeX企业级架构设计:LaTeX文献管理的高性能实现方案
  • 从零到一:在SpringBoot项目中集成sensitive-word实现敏感词实时过滤
  • 城市夜景视频商用素材哪里找?2026年正版下载平台推荐 - Fzzf_23
  • 如何将B站视频内容转化为可编辑文字资源:Bili2text使用指南
  • OpenGL逻辑学快速入门 卷一 世界观:OpenGL 究竟是个什么东西
  • 2026贝赛思入学备考特训机构怎么选 靠谱冲刺班与提分特训机构推荐 - 品牌2026