【问题标题】:Why is a pagefactory class returning null when initialised from another class为什么从另一个类初始化时pagefactory类返回null
【发布时间】:2021-07-08 01:43:06
【问题描述】:

在我的测试课程中,我为 Appium 测试设置了 DesiredCapabilities。在那个类中,我初始化了包含 pagefactory 元素的 BasePage 类。当我运行测试时,它按预期工作。

现在,我尝试将 DediredCapabilities 移到一个单独的类 CapacityManager 中,从而更具创造性。在我的测试类中,我从CapacityManager 中调用了保存DesiredCapabilities 的方法。方法调用成功,我的应用程序已启动,但 pagefactory 元素不再工作。我不知道为什么。

当我运行测试时,我在第一个移动元素上得到一个 nullPointerExceptio,这让我怀疑是 pagefactory 或驱动程序初始化问题。

工作测试类如下所示:

public class LoginTest {
    
      private final BaseUtil baseUtil = new BaseUtil();
      private static BasePage basePage;
      public static AndroidDriver<AndroidElement> driver;
      public static File classpathRoot;
    
    
      public void startApp() throws MalformedURLException {
        classpathRoot = new File(System.getProperty("user.dir"));
        File appDir = new File(classpathRoot, "");
        File app = new File(appDir, baseUtil.getMyApp());
        DesiredCapabilities cap = new DesiredCapabilities();
        cap.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
        cap.setCapability(MobileCapabilityType.PLATFORM_VERSION, "11.0");
        cap.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator");
        cap.setCapability(MobileCapabilityType.AUTOMATION_NAME, "uiautomator2");
        cap.setCapability(MobileCapabilityType.APP, app.getAbsolutePath());
        URL url = new URL("http://localhost:4723/wd/hub");
        driver = new AndroidDriver<>(url, cap);
        basePage = PageFactory.initElements(driver, BasePage.class); //pagefactory initialised
      }
    
    
    
      public void login(String sheetname, int rowNumber) throws InterruptedException, IOException, InvalidFormatException {
        FeatureUtils featureUtils = new FeatureUtils();
        File excelDir = new File(classpathRoot, "");
        File exceldoc = new File(excelDir, baseUtil.getUsersExcelDoc());
        List<Map<String, String>> testData = featureUtils.getData(exceldoc.getAbsolutePath(), sheetname);
        String username = testData.get(rowNumber).get("username");
        String password = testData.get(rowNumber).get("password");
  
        basePage.yesIAgreeButton.click(); //first element clicks successfully

这是包含pagefactory 元素的 BasePage 类:

    public class BasePage {
    
      private final WebDriver driver;

      public BasePage(WebDriver driver) { //constructor
        this.driver = driver;
      }
    
     
      @FindBy(id = "com.test")
  public WebElement yesIAgreeButton;

使用上面的两个类,LoginTest 类按预期工作。

现在,我从测试类中删除了 DesiredCapabilities 并将它们放入一个新类中,CapacityManager

public class CapacityManager {

  public static AndroidDriver<AndroidElement> driver;
  public static File classpathRoot;
  private final BaseUtil baseUtil = new BaseUtil();
  private static BasePage basePage;

  public DesiredCapabilities appDesiredCapabilities() throws MalformedURLException {
    DesiredCapabilities desiredCapabilities = null;
    classpathRoot = new File(System.getProperty("user.dir"));
    File appDir = new File(classpathRoot, "");
    File app = new File(appDir, baseUtil.getMyApp());
    DesiredCapabilities cap = new DesiredCapabilities();
    cap.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
    cap.setCapability(MobileCapabilityType.PLATFORM_VERSION, "11.0");
    cap.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator");
    cap.setCapability(MobileCapabilityType.AUTOMATION_NAME, "uiautomator2");
    cap.setCapability(MobileCapabilityType.APP, app.getAbsolutePath());
    URL url = new URL("http://localhost:4723/wd/hub");
    driver = new AndroidDriver<>(url, cap);
    basePage = PageFactory.initElements(driver, BasePage.class); //pagefactory initialisation

    return desiredCapabilities;
  }
   
}
    

然后我从测试类中调用了DesiredCapabilities,如下所示:

 public class LoginTest {
    
      private final BaseUtil baseUtil = new BaseUtil();
      private static BasePage basePage;
      public static AndroidDriver<AndroidElement> driver;
      public static File classpathRoot;
      CapacityManager capacityManager = new CapacityManager();
    
     
      public void startApp() throws MalformedURLException {
    capacityManager.appDesiredCapabilities();  //method call. App launches successfully.
    }
    
      
    
      public void login(String sheetname, int rowNumber) throws InterruptedException, IOException, InvalidFormatException {
        FeatureUtils featureUtils = new FeatureUtils();
        File excelDir = new File(classpathRoot, "");
        File exceldoc = new File(excelDir, baseUtil.getUsersExcelDoc());
        List<Map<String, String>> testData = featureUtils.getData(exceldoc.getAbsolutePath(), sheetname);
        String username = testData.get(rowNumber).get("username");
        String password = testData.get(rowNumber).get("password");

       basePage.yesIAgreeButton.click(); //getting nullpointer error on this line. Looks like an issue with the pagefactory elements initialisation. basePage is null.
       driver.pressKey(new KeyEvent(AndroidKey.TAB)); 

【问题讨论】:

    标签: java selenium-webdriver appium page-factory


    【解决方案1】:

    CapacityManager 内部的basePageLoginTest 内部的一个是两个不同的对象实例,LoginTest 内部的一个从未初始化。您需要做的就是这样:

    basePage = capacityManager.getBasePage(); // create this method returning basePage object
    basePage.yesIAgreeButton.click();
    

    或者...因为basePagestatic,将其公开并静态调用CapacityManager.basePage。也就是说,我认为这些 static 字段中的任何一个都不应该是 static

    更新

    CapacityManager 类的构造很差。对于初学者,它具有私有静态字段,在初始化后从未用于任何事情。我不会解决所有问题,但我会解决与这篇文章有关的问题。将CapacityManager 替换为我的版本并更新以解决其他字段的问题。

    public class CapacityManager {
    
      public AndroidDriver<AndroidElement> driver;
      public File classpathRoot;
      private final BaseUtil baseUtil = new BaseUtil();
      private BasePage basePage;
      private DesiredCapabilities cap;
      private File app;
    
      public CapacityManager() {
        classpathRoot = new File(System.getProperty("user.dir"));
        File appDir = new File(classpathRoot, "");
        app = new File(appDir, baseUtil.getMyApp());
      }
    
      public DesiredCapabilities appDesiredCapabilities() throws MalformedURLException {
        if (cap == null) {
            cap = new DesiredCapabilities();
            cap.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
            cap.setCapability(MobileCapabilityType.PLATFORM_VERSION, "11.0");
            cap.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator");
            cap.setCapability(MobileCapabilityType.AUTOMATION_NAME, "uiautomator2");
            cap.setCapability(MobileCapabilityType.APP, app.getAbsolutePath());
            URL url = new URL("http://localhost:4723/wd/hub");
            driver = new AndroidDriver<>(url, cap);
        }
    
        return cap;
      }
    
      public BasePage getBasePage() {
          return basePage;
      }
    }
    

    不更改LoginTest 类中的任何其他内容(我假设它“正常”工作),只需通过调用 getter 方法获取 BasePage:

    public void login(String sheetname, int rowNumber) throws InterruptedException, IOException, InvalidFormatException {
        FeatureUtils featureUtils = new FeatureUtils();
        File excelDir = new File(classpathRoot, "");
        File exceldoc = new File(excelDir, baseUtil.getUsersExcelDoc());
        List<Map<String, String>> testData = featureUtils.getData(exceldoc.getAbsolutePath(), sheetname);
        String username = testData.get(rowNumber).get("username");
        String password = testData.get(rowNumber).get("password");
    
        BasePage basePage = PageFactory.initElements(capacityDriver.driver, BasePage.class); // pass the AndroidDriver instance in CapacityDriver class
        basePage.clickYesIAgree();
    }
    

    更新 #2

    通常,当您创建工厂以提供类的实例时,您会隐藏该类的构造函数。当你有一个类的公共构造函数时,有时最好不要有一个工厂。为什么?以免混淆。当然,我刚才提到的还有很多例外。在这里,我们有一个令人困惑的实现。

    您有一个带有公共构造函数的 BasePage,该构造函数将 WebDriver 作为参数。您还拥有返回BasePage 实例的PageFactory.initElements(driver, BasePage.class)。我应该如何创建BasePage 的实例?什么时候应该使用工厂?我什么时候应该直接打电话给承包商?

    考虑到PageFactory 是Selenium 库提供的经过验证的类,有理由相信BasePage 的构造不正确和/或@FindBy 注释的使用不正确。我无法为您解决这些问题,但我可以建议我认为BasePage 应该是什么样子。给你:

    public class BasePage {
        @FindBy(id = "com.test") // this component ID looks suspicious
        private WebElement yesIAgreeButton;
    
        public void clickYesIAgree() {
            yesIAgreeButton.click();
        }
    }
    

    第二次更新后,之前的代码已经更新了。

    【讨论】:

    • @Theman 你的代码一团糟。我将重新发布我的答案以修复您最初发布的内容。
    • 谢谢。我的更新取得了很好的进展,basePage现在已完全初始化,但是驱动程序为空。我应该在我的问题中包含第一次出现的驱动程序对象。我的错。我已经更新了这个问题。基本上,在basePage.yesIAgreeButton.click() 之后,有一个驱动程序调用driver.pressKey(new KeyEvent(AndroidKey.TAB)); 为空。如何初始化它?
    • @Theman 你需要控制你的对象实例。您无缘无故地到处创建对象。例如,您有一个在CapacityManager 中完全实例化的driver,但您正在调用BasePage 中的一个调用click() 的方法。基于此,我假设您在该类中有另一个(空)driver 对象。决定你想在哪里存储对象,在那里完全初始化它们,然后提供访问器方法,以便这些对象实例可以被共享和正确使用。
    • 谢谢@hfontanez,这正是我想要做的。如果您可以使用驱动程序实例更新您的答案,我将不胜感激。我喜欢你处理实施的方式
    • @Theman 我不知道PageFactory 长什么样,但我会根据你发布的内容尽力而为。
    【解决方案2】:

    对象代表现实世界的实体。例如(从 Head First Java 引用中取出一页),猫就是一个对象。猫有腿和嘴作为特征,可以用来做某些事情,比如跑步和咬人。

    同样,当您创建CapacityManager 类并在LoginTest 中对其调用new 运算符时,您已经创建了一个对象。 CapacityManager 具有 driverBasePage(以及其他功能),您已在 appDesiredCapabilities() 方法中使用它们进行了初始化。

    在猫的例子中,我们不能用猫的腿跑和用猫的嘴咬(在java中显然有一种方法可以做到这一点,但我们现在已经超前了)。我们可以告诉猫跑和咬,但最终猫必须自己做这些操作。同样,CapacityManager 功能,如driverBasePage,我们不能在CapacityManager 之外使用。我们可以调用CapacityManager.appDesiredCapabilities()方法来初始化驱动,并在BasePage上设置页面工厂,但不能直接使用。

    简单地说,在LoginTest 中声明driverBasePage 并创建CapacityManager 的对象不会初始化driverBasePage 中的LoginTest 功能。我们必须弄清楚如何控制LoginTestCapacityManager中声明的driverBasePage。为此,我们在CapacityManager 中为driverBasePage 使用public 访问修饰符。正确的 OOP 方法是使用 getters 将功能公开,以便我们可以从外部访问它们。这显示在下面的代码更新中。

    public class CapacityManager {
    
        private DesiredCapabilities cap;
        private AndroidDriver<AndroidElement> driver;
        private BasePage basePage;
    
        public CapacityManager() {
            URL url = new URL("http://localhost:4723/wd/hub");
            this.cap = new DesiredCapabilities();
            cap.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
            cap.setCapability(MobileCapabilityType.PLATFORM_VERSION, "11.0");
            cap.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator");
            cap.setCapability(MobileCapabilityType.AUTOMATION_NAME, "uiautomator2");
            cap.setCapability(MobileCapabilityType.APP, app.getAbsolutePath());
            this.driver = new AndroidDriver<>(url, cap);
            this.basePage = PageFactory.initElements(driver, BasePage.class);
        }
    
        public AndroidDriver<AndroidElement> getDriver() {
            return driver;
        }
    
        public BasePage getBasePage() {
            return basePage;
        }
    
    }
    
    
    public class LoginTest {
    
        private File classpathRoot;
        private BaseUtil baseUtil;
        private File app;
    
        // Launch the app, initialize driver and page factory
        CapacityManager capacityManager = new CapacityManager();
    
        public void login(String sheetname, int rowNumber)
                throws InterruptedException, IOException, InvalidFormatException {
            // Intialize file objects
            baseUtil = new BaseUtil();
            classpathRoot = new File(System.getProperty("user.dir"));
            app = new File(new File(classpathRoot, ""), baseUtil.getMyApp());
    
            FeatureUtils featureUtils = new FeatureUtils();
            File excelDir = new File(capacityManager.classpathRoot, "");
            File exceldoc = new File(excelDir, baseUtil.getUsersExcelDoc());
            List<Map<String, String>> testData = featureUtils.getData(exceldoc.getAbsolutePath(), sheetname);
            String username = testData.get(rowNumber).get("username");
            String password = testData.get(rowNumber).get("password");
    
            // Get driver and BasePage from capacityManager object
            capacityManager.getBasePage().yesIAgreeButton.click(); 
            capacityManager.getDriver().pressKey(new KeyEvent(AndroidKey.TAB));
        }
    }
    

    有很多不同的更好的方法来配置它。如果你觉得更花哨,你可以创建一个类来获取测试数据。那是另一天。

    希望上述解决方案能解决您遇到的错误!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-02
      • 2019-07-15
      • 2022-06-10
      相关资源
      最近更新 更多