【问题标题】:Selenium Page Object. How to read @FindBy locator from external source?Selenium 页面对象。如何从外部源读取@FindBy 定位器?
【发布时间】:2017-02-16 07:40:11
【问题描述】:

我只能在页面对象@FindBy 注释中使用硬编码值 但我想动态解析定位器。

public class LoginPage extends BasePage {

    // hardocded value works ok
    @FindBy(name = "login field")
    WebElement usernameFld;

    // does not compile
    // this is a kind of what I would like to have  
    @FindBy( getLocatorFromExternalSource() ) 
    WebElement passwordFld;

}

我看到一些帖子提到可以通过实现自定义注释/装饰器/工厂来解决此类问题,但尚未找到示例。

问题: 有人可以举一个示例说明如何实现自定义 ElementLocatorFactory 以便动态解析定位器吗?

我知道我可以使用简单的老式调用,例如:

driver.findElement( getLocatorFromExternalSource("passwordFld") ).click()

但我想使用

passwordFld.click() 

改为。

【问题讨论】:

  • 您的定位器变化很大吗?为什么要从外部文件中读取它们,而不是仅仅将它们放在易于找到和组织的页面对象中?
  • 1.我正在尝试为 ios 和 android 设备编写与平台无关的测试,并且不想复制页面对象 2. UI 本地化可能是外化定位器有用的下一个点 3. 一些定位器可以在运行时更改(元素以另一个顺序排序等)

标签: java selenium pageobjects


【解决方案1】:

我想你想知道按他们的名字对 webElements 执行操作

你需要在你的构造函数中放一些代码

public AnyConstructorName(AndroidDriver<AndroidElement> driver) {
        this.driver =driver;
        PageFactory.initElements(driver, this);
}

如果您接受其他类的驱动程序,上述代码将起作用 如果不是这种情况,请从您的构造函数中删除 this.driver =driver

初始化网页元素,如

@FindBy(xpath ="//android.widget.TextView[@text='Payments']")

WebElement pay;

你可以执行pay.click();

或者你想要的任何操作

但只能在您的页面类中执行这些操作

【讨论】:

  • @FindBy(xpath ="//android.widget.TextView[@text='Payments']") 我正在寻找一种方法,不在@FindBy 中硬编码定位器,而是从外部源读取它
【解决方案2】:

我在这里找到了一些线索:https://stackoverflow.com/a/3987430/1073584 最后通过实现 3 个类设法得到我想要的: CustomPageFactory、CustomElementLocator、CustomAnnotations。

现在我可以将我的定位器外部化为类型安全配置 并使用以下代码初始化页面对象

//=======登录页面

public class LoginPage extends AbstractPage {
    Config config;

    @FindBy(using = "CONFIG") // take locator from config
    WebElement passwordFld;

    // .. other fields skipped


    public LoginPage(WebDriver webDriver, Config config) throws IOException {
        super(webDriver);
        this.config = config;
        PageFactory.initElements(new CustomPageFactory(webDriver, config), this);
    }

}

//===== login.page.typesafe.config:

passwordFld = {
   name = "password field"
}

//============= CustomPageFactory

package com.company.pages.support;

import com.typesafe.config.Config;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.pagefactory.ElementLocator;
import org.openqa.selenium.support.pagefactory.ElementLocatorFactory;

import java.lang.reflect.Field;

public class CustomPageFactory implements ElementLocatorFactory {
    private Config config;
    private WebDriver driver;

    public CustomPageFactory(WebDriver driver, Config config) {
        this.driver = driver;
        this.config = config;
    }

    public ElementLocator createLocator(Field field) {
        return new CustomElementLocator(driver, field, config);
    }
}

//=================自定义元素定位器

package com.company.pages.support;

import com.typesafe.config.Config;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.support.pagefactory.DefaultElementLocator;

import java.lang.reflect.Field;

public class CustomElementLocator extends DefaultElementLocator {

    private Config config;

    public CustomElementLocator(SearchContext searchContext, Field field, Config config) {
        super(searchContext, new CustomAnnotations(field, config));
        this.config = config;
    }


}

//====== 自定义注释

package com.company.pages.support;

import com.typesafe.config.Config;
import com.typesafe.config.ConfigObject;
import org.openqa.selenium.By;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.pagefactory.Annotations;

import java.lang.reflect.Field;

public class CustomAnnotations extends Annotations {

    Config config;

    public CustomAnnotations(Field field, Config config) {
        super(field);
        this.config = config;
    }

    @Override
    protected By buildByFromShortFindBy(FindBy findBy) {

        if (findBy.using().equals("CONFIG")) {

            if (null != config) {

                ConfigObject fieldLocators = config.getObject(getField().getName());

                if (fieldLocators.keySet().contains("className"))
                    return By.className(fieldLocators.get("className").unwrapped().toString());

                if (fieldLocators.keySet().contains("css"))
                    return By.cssSelector(fieldLocators.get("css").unwrapped().toString());

                if (fieldLocators.keySet().contains("id"))
                    return By.id(fieldLocators.get("id").unwrapped().toString());

                if (fieldLocators.keySet().contains("linkText"))
                    return By.linkText(fieldLocators.get("linkText").unwrapped().toString());

                if (fieldLocators.keySet().contains("name"))
                    return By.name(fieldLocators.get("name").unwrapped().toString());

                if (fieldLocators.keySet().contains("partialLinkText"))
                    return By.partialLinkText(fieldLocators.get("partialLinkText").unwrapped().toString());

                if (fieldLocators.keySet().contains("tagName"))
                    return By.tagName(fieldLocators.get("tagName").unwrapped().toString());

                if (fieldLocators.keySet().contains("xpath"))
                    return By.xpath(fieldLocators.get("xpath").unwrapped().toString());
            }

        }

        return super.buildByFromShortFindBy(findBy);
    }

}

【讨论】:

  • 感谢您的帮助,但此解决方案适用于 selenium v​​2.53。我已经由 selenium-java 升级到 3.5.3,并且看不到 buildByFromShortFindBy 方法属于 Annotations 类了,它被移动到 AbstractFindByBuilder 并因此想知道如何连接和覆盖这个函数来实现我的 CustomPageFactory?
  • @BhuvaneshMani 你是对的,此代码不再适用于较新版本的 selenium
【解决方案3】:

我个人不是PageFactory 的忠实粉丝,但如果你已经在使用它,我会做类似下面的事情。

public class LoginPage extends BasePage
{
    WebDriver driver;
    WebElement usernameFld;
    @FindBy(name = "login field")
    WebElement usernameIOs;

    @FindBy(name = "something else")
    WebElement usernameAndroid;

    public LoginPage(WebDriver webDriver, Sites site)
    {
        this.driver = webDriver;
        switch (site)
        {
            case IOS:
                usernameFld = usernameIOs;
                break;
            case ANDROID:
                usernameFld = usernameAndroid;
                break;
        }
    }

    public void setUsername(String username)
    {
        usernameFld.sendKeys(username);
    }
}

你会定义的其他地方

public enum Sites
{
    IOS, ANDROID
}

我更喜欢声明定位器,然后根据需要抓取元素。我发现它的性能要高得多,并且您会遇到更少的陈旧元素问题等。

public class LoginPage extends BasePage
{
    WebDriver driver;
    By usernameLocator;
    By usernameIOsLocator = By.name("login field");
    By usernameAndroidLocator = By.name("something else");

    public LoginPage(WebDriver webDriver, Sites site)
    {
        this.driver = webDriver;
        switch (site)
        {
            case IOS:
                usernameLocator = usernameIOsLocator;
                break;
            case ANDROID:
                usernameLocator = usernameAndroidLocator;
                break;
        }
    }

    public void setUsername(String username)
    {
        driver.findElement(usernameLocator).sendKeys(username);
    }
}

【讨论】:

  • 感谢您的回答。我想我明白了你的想法。优点是它通过明确选择定位器使一切变得清晰。缺点是,一旦添加了一个新平台,比如 IOS9、IOS10、iPad,就必须修改所有页面对象的所有 switch case。我宁愿添加/切换页面配置并保持代码完整。
  • 这和你正在做的事情并没有太大的不同。添加新配置时,您必须为配置文件中每个页面上的每个元素添加一个新定位器。我必须在这里做同样的事情。不同之处在于,我的方式使页面对象方法保持干净,因为与页面有关的所有内容都在页面对象中。你的配置文件会很快变得笨拙,因为它包含所有元素的所有定位器,乘以你支持的配置数量。
  • 我同意 - 定位器的数量是相同的。但我更喜欢将测试数据与代码分开。
【解决方案4】:

注解是元数据,因此它们需要在类加载期间在运行时开始可用。您所寻求的没有直接的答案,但是使用反射有黑客攻击,恕我直言,我会避免它,如果您需要外部化定位器,那么我建议您实现对象存储库,您可以在运行时即时读取定位器一些外部来源。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-27
    • 2020-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多