Access the By class created using @FindBy annotation in Selenium

Recently I needed to increase the information the NoSuchElementException contained when using Selenium’s @FindBy annotation. During this I stumbled across a way to get access to the By class created from the @FindBy.

For example say we have the following PageObject


public class testPageObject {
   @FindBy(css = "h1[name='test']")
   WebElement testElement1;

   @FindBy(xpath = "//*[class='test']")
   WebElement testElement2;

   @FindBy(id = "test")
   WebElement testElement3;


   public testPageObject() {
       //Cache the WebElements using a 5 second wait
       PageFactory.initElements(new AjaxElementLocatorFactory(driver, 5), this);
   }

   public void printBy() {
        By testElement1 = getBy("testElement1");
        By testElement2 = getBy("testElement2");
        By testElement3 = getBy("testElement3");

        System.out.println(testElement1);
        System.out.println(testElement2);
        System.out.println(testElement3);
   }

   private By getBy(String fieldName) {
       try {
            return new Annotations(this.getClass().getDeclaredField(fieldName)).buildBy();
       } catch (NoSuchFieldException e) { return null; }
   }
}

The solution is to use Selenium’s built in Annotations class which takes a field and call buildBy() to return the By class for that field.

The output of the printBy() function:

By.selector: h1[name=’test’]
By.xpath: //*[class=’test’]
By.id: test

MyBatis with Annotation’s

The MyBatis configuration xml defines the connection info to the database and the mapper interfaces available to the database.

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <environments default="TestDB">

        <environment id="TestDB">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/"/>
                <property name="username" value="TestDBUser"/>
                <property name="password" value="TestDBPW"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper class="com.test.mapper.UserMapper"/>
        <mapper class="com.test.mapper.CompanyMapper"/>
    </mappers>

</configuration>

The Mapper class is an interface that contains the queries and sets the return type.

public interface UserMapper {

    final String ALL_USERS = "SELECT * FROM USER";
    final String USER_BY_SCREEN_NAME = ALL_USERS + " WHERE USER.screenname=#{screenName}";
    
    @Select(ALL_USERS)
    @Results(value = {
            @Result(property = "firstName",     column = "firstname"),
            @Result(property = "lastName",      column = "lastname"),
            @Result(property = "userName",      column = "screenname"),
            @Result(property = "emailAddress",  column = "emailaddress")
    })
    List<User> getUsers();

    @Select(USER_BY_SCREEN_NAME)
    @Results(value = {
            @Result(property = "firstName",     column = "firstname"),
            @Result(property = "lastName",      column = "lastname"),
            @Result(property = "userName",      column = "screenname"),
            @Result(property = "emailAddress",  column = "emailaddress")
    })
    User getUserByScreenName(String screenName);
}

To use the new mapper I usually will create a DatabaseBuilder class that uses generics to return the mapper for that database.

public class DatabaseBuilder {
    private final String conf;

    public DatabaseHelper(Database database) {
        this.conf = database.getEnvironment();
    }

    public <T> T getMapper(Class<T> mapperInterface) {
        try {
            Reader reader = Resources.getResourceAsReader(conf);

            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sessionFactory = builder.build(reader);
            SqlSession session = sessionFactory.openSession();

            return session.getMapper(mapperInterface);
        } catch (IOException e) {
            e.printStackTrace();
        }
        throw new RuntimeException("Failed to return mapper");
    }

    public <T> T getMapper(Class<T> mapperInterface, String environment) {
        try {
            Reader reader = Resources.getResourceAsReader(conf);

            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sessionFactory = builder.build(reader, environment);
            SqlSession session = sessionFactory.openSession();

            return session.getMapper(mapperInterface);
        } catch (IOException e) {
            e.printStackTrace();
        }
        throw new RuntimeException("Failed to return mapper");
    }

    public <T> T getMapper(Class<T> mapperInterface, Properties properties) {
        try {
            Reader reader = Resources.getResourceAsReader(conf);

            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sessionFactory = builder.build(reader, properties);
            SqlSession session = sessionFactory.openSession();

            return session.getMapper(mapperInterface);
        } catch (IOException e) {
            e.printStackTrace();
        }
        throw new RuntimeException("Failed to return mapper");
    }

    public <T> T getMapper(Class<T> mapperInterface, String environment, Properties properties) {
        try {
            Reader reader = Resources.getResourceAsReader(conf);

            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sessionFactory = builder.build(reader, environment, properties);
            SqlSession session = sessionFactory.openSession();

            return session.getMapper(mapperInterface);
        } catch (IOException e) {
            e.printStackTrace();
        }
        throw new RuntimeException("Failed to return mapper");
    }

    public enum Database {
        //The path to your MyBatis xml file
        TESTDB("test-db.conf.xml")
        // If you have more then one DB with custom mappers place them here!
        //TESTDB2("test_db2.conf.xml")

        private String config;

        private Database(String config) {
            this.config = config;
        }

        public String getEnvironment() {
            return this.config;
        }
    }
}

Now you can use your custom queries with the following

UserMapper userMapper = new DatabaseBuilder(Database.TESTDB).getMapper(UserMapper.class);
List<User> users = userMapper.getUsers();
User testUser = userMapper.getUserByScreenName("EpicTester");