在Selenium中实现LoadableComponents时,如何优化嵌套PageObject的创建?
我正在使用Selenium、Cucumber和Java学习自动化测试,已经实现了Page Object Model(POM)设计模式,同时采用了LoadableComponents模式,觉得这个模式很实用。但遇到一个问题:当组件存在多层嵌套(需要经过多个页面/组件才能到达目标组件)时,测试类的初始化方法里需要为每个页面创建对象,代码显得不够优雅。
以Selenium文档中的Web应用结构为例:
+ ProjectPage +---+ SecuredPage +---+ EditIssue
对应的测试类代码片段如下:
public class FooTest { private EditIssue editIssue; @Before public void prepareComponents() { WebDriver driver = new FirefoxDriver(); ProjectPage project = new ProjectPage(driver, "selenium"); SecuredPage securedPage = new SecuredPage(driver, project, "example", "top secret"); editIssue = new EditIssue(driver, securedPage); } }
当嵌套层级较多时,需要创建大量PageObject对象,这种方案不够优雅。我想了解是否有更优的实现方式?
我曾研究过依赖注入框架,有SpringBoot基础,其@Autowired功能看似适用,但引入这类大型框架处理该问题有种大材小用的感觉。另外还了解了Guiceberry和PicoContainer,但未理解Guiceberry的用法,且认为PicoContainer仍需在初始化方法中创建对象,可能是我理解有误。
1. 页面导航链模式
让父页面负责封装跳转到子页面的逻辑,并返回子页面实例,这样测试类不需要手动创建所有中间页面对象,而是通过页面间的导航方法逐步获取目标页面,更符合实际业务流程。
改造页面类:
// ProjectPage 类 public class ProjectPage extends LoadableComponent<ProjectPage> { private WebDriver driver; private String projectName; public ProjectPage(WebDriver driver, String projectName) { this.driver = driver; this.projectName = projectName; } // 封装跳转到SecuredPage的逻辑,返回SecuredPage实例 public SecuredPage navigateToSecuredPage(String username, String password) { // 执行登录操作(示例代码,需根据实际页面元素调整) driver.findElement(By.id("username")).sendKeys(username); driver.findElement(By.id("password")).sendKeys(password); driver.findElement(By.id("login-btn")).click(); return new SecuredPage(driver, this); } @Override protected void load() { driver.get("https://example.com/projects/" + projectName); } @Override protected void isLoaded() throws Error { assertTrue(driver.getTitle().contains(projectName), "ProjectPage未加载完成"); } } // SecuredPage 类 public class SecuredPage extends LoadableComponent<SecuredPage> { private WebDriver driver; private ProjectPage parentPage; public SecuredPage(WebDriver driver, ProjectPage parentPage) { this.driver = driver; this.parentPage = parentPage; } // 封装跳转到EditIssue的逻辑,返回EditIssue实例 public EditIssue navigateToEditIssue() { // 执行进入编辑页面的操作 driver.findElement(By.linkText("Edit Issue")).click(); return new EditIssue(driver, this); } @Override protected void load() { // 依赖父页面已完成加载,此处可留空或做基础验证 } @Override protected void isLoaded() throws Error { assertTrue(driver.findElement(By.id("secured-content")).isDisplayed(), "SecuredPage未加载完成"); } }
简化后的测试类:
public class FooTest { private EditIssue editIssue; @Before public void prepareComponents() { WebDriver driver = new FirefoxDriver(); ProjectPage projectPage = new ProjectPage(driver, "selenium"); projectPage.get(); // 触发LoadableComponent的加载逻辑 SecuredPage securedPage = projectPage.navigateToSecuredPage("example", "top secret"); securedPage.get(); editIssue = securedPage.navigateToEditIssue(); editIssue.get(); } }
这种方式将页面跳转逻辑内聚到对应页面类中,测试类代码更简洁,也更符合页面间的实际交互逻辑。
2. 轻量级依赖注入:PicoContainer优化用法
你之前对PicoContainer的理解有误,它可以自动管理对象依赖,无需手动逐个创建实例。通过注册依赖关系,容器会自动解析并初始化所有嵌套的页面对象。
实现步骤:
- 引入PicoContainer依赖(Maven示例)
<dependency> <groupId>org.picocontainer</groupId> <artifactId>picocontainer</artifactId> <version>2.15</version> <scope>test</scope> </dependency>
- 改造测试类,利用容器管理依赖:
import org.picocontainer.DefaultPicoContainer; import org.picocontainer.MutablePicoContainer; import org.picocontainer.injectors.Inject; public class FooTest { @Inject private EditIssue editIssue; @Before public void prepareComponents() { WebDriver driver = new FirefoxDriver(); MutablePicoContainer container = new DefaultPicoContainer(); // 注册WebDriver实例 container.addComponent(WebDriver.class, driver); // 注册带参数的ProjectPage container.addComponent(ProjectPage.class, new ProjectPage(driver, "selenium")); // 注册带参数的SecuredPage(容器会自动获取已注册的ProjectPage实例) container.addComponent(SecuredPage.class, new SecuredPage(driver, container.getComponent(ProjectPage.class), "example", "top secret")); // 注册EditIssue container.addComponent(EditIssue.class); // 为测试类注入依赖 container.injectMembers(this); // 触发所有页面的LoadableComponent加载逻辑 container.getComponent(ProjectPage.class).get(); container.getComponent(SecuredPage.class).get(); editIssue.get(); } }
这样测试类只需要通过@Inject获取目标页面对象,所有中间页面的创建和依赖注入都由PicoContainer自动处理,避免了手动创建大量对象的冗余代码。
3. 自定义页面工厂类
创建一个专门的工厂类,封装所有页面对象的创建和初始化逻辑,测试类只需调用工厂方法直接获取目标页面。
实现示例:
public class PageFactory { private WebDriver driver; public PageFactory(WebDriver driver) { this.driver = driver; } // 封装获取EditIssue的完整流程 public EditIssue getEditIssue(String projectName, String username, String password) { ProjectPage projectPage = new ProjectPage(driver, projectName); projectPage.get(); SecuredPage securedPage = new SecuredPage(driver, projectPage, username, password); securedPage.get(); EditIssue editIssue = new EditIssue(driver, securedPage); editIssue.get(); return editIssue; } }
简化后的测试类:
public class FooTest { private EditIssue editIssue; @Before public void prepareComponents() { WebDriver driver = new FirefoxDriver(); PageFactory pageFactory = new PageFactory(driver); // 一行代码获取目标页面 editIssue = pageFactory.getEditIssue("selenium", "example", "top secret"); } }
这种方式将页面创建的复杂逻辑集中到工厂类中,测试类代码极度简洁,后续页面结构变化时,只需修改工厂类即可,便于维护。
内容的提问来源于stack exchange,提问作者jasonj2232

