15. Библиотека Owner - qa-guru/knowledge-base GitHub Wiki

Что такое конфигурация тестов и для чего она используется

На своей локальной машине мы можем писать для каждой страницы и браузера создавать отдельный проект и писать код заново. Такой подход не совсем удобен и подразумевает за собой слишком много дополнительной работы, забирающей время. Можно упростить процесс, один раз написать файл конфигурации и запускать тесты для разных браузеров и страниц.

Выносим настройки в config

Для начала в проекте создадим пакет config и в нем сперва создадим enum перечисление, в котором будут храниться все браузеры, в которых мы тестируем страницу. Для этого создадим перечисление с именем Browser:

public enum Browser {

    CHROME,

    FIREFOX
}

Теперь создадим класс конфигурации с именем WebDriverConfig и вынесем в него все настройки:

public class WebDriverConfig {
    public String getBaseUrl() {
        return "https://github.com";
    }

    public Browser getBrowser() {
        return Browser.CHROME;
    }
}

И в конце напишем класс, который будет отвечать за создание веб-драйвера на основе данных из класса конфигурации. Назовем класс, к примеру, WebDriverProvider и напишем в нем следующий код:

public class WebDriverProvider implements Supplier<WebDriver> {

    private WebDriverConfig config;

    public WebDriverProvider() {
        this.config = new WebDriverConfig();

    @Override
    public WebDriver get() {
        WebDriver driver = createWebDriver();
        driver.get(config.getBaseUrl());
        return driver;
    }

    private WebDriver createWebDriver() {
        if (config.getBrowser().equals(Browser.CHROME)) {
            WebDriverManager.chromedriver().setup();
            return new ChromeDriver();
        }
        if (config.getBrowser().equals(Browser.FIREFOX)) {
            WebDriverManager.firefoxdriver().setup();
            return new FirefoxDriver();
        }
        throw new RuntimeException("No such browser");
    }

}

На этом шаге можно написать тест, который будет содержать чистый код без лишних строк. Также можно будет менять содержимое переменных в файле конфигурации и запускать тесты с разными параметрами:

public class SeleniumTest {
    private WebDriver driver = new WebDriverProvider().get();

    @Test
    public void testGithubTitle() {
        // код выполнения теста
        String title = driver.getTitle();
        assertEquals(title, "GitHub: Where the world builds software · GitHub");
    }

    @AfterEach
    public void stopDriver() {
        driver.quit();
    }
}

Запускаем тесты с разными конфигурациями из консоли

В шаге выше нам удалось вынести все конфигурационные строки кода в отдельные файлы. Но не всегда удобно каждый раз идти в файл конфигурации, менять в нем переменные и снова запускать тест. Для автоматизации этого процесса можно использовать SystemGetProperty.

Для этого модифицируем класс WebDriverConfig таким образом:

public class WebDriverConfig {
    public String getBaseUrl() {
        return "https://github.com";
    }

    public Browser getBrowser() {
        String browser = System.getProperty("browser");
        return Browser.ValueOf(browser);
    }
}

Теперь можно запускать тесты из консоли, указывая браузер, в котором следует запускать тест:

./gradlew clean test -Dbrowser=CHROME

Значение браузера по умолчанию

Теперь мы можем работать с консолью и на ходу выбирать необходимый браузер для запуска, но если выполнить команду ./gradlew clean test, то ничего полезного не произойдет и тесты упадут. Все дело в том, что мы не указали значение по умолчанию, которое будет запускаться, если не указать ничего дополнительно.

Для этого добавим еще одно условие в класс WebDriverConfig, вместе с этим модифицируем метод getBaseUrl():

public class WebDriverConfig {
    public String getBaseUrl() {
        String baseUrl = System.getProperty("baseUrl");
        if (Objects.isNull(baseUrl)) {
            baseUrl = "https://github.com";
        }
        return baseUrl;
    }

    public Browser getBrowser() {
        String browser = System.getProperty("browser");
        if (Objects.isNull(browser)) {
            browser = "CHROME";
        }
        return Browser.ValueOf(browser);
    }
}

Теперь мы можем задавать из командной строки необходимый браузер и адрес страницы:

./gradlew clean test -Dbrowser=FIREFOX -DbaseUrl=https://testing.github.com"

Библиотека Owner

Подключаем библиотеку уже привычным образом:

dependencies {
    testImplementation(
            'org.aeonbits.owner:owner:1.0.4',
    )
    testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.8.2')
}

Теперь мы можем упростить код класса WebDriverConfig, но важно отметить, что класс надо сделать интерфейсом. Указываем ключи и значения по умолчанию:

public interface WebDriverConfig extends Config {

    @Key("baseUrl")
    @DefaultValue("https://github.com")
    String getBaseUrl();

    @Key("browser")
    @DefaultValue("CHROME")
    Browser getBrowser();

    // зачитываем данные из командной строки
    @Key("remoteUrl")
    // обрабатывает дефолтное значение
    @DefaultValue("http://localhost:4444/wd/hub")
    // конвертируем в возращаемый тип
    URL getRemoteUrl();

}

После этого надо вернуться к файлу WebDriverProvider и изменить его следующим образом:

public class WebDriverProvider implements Supplier<WebDriver> {

    private WebDriverConfig config;

    public WebDriverProvider() {
        this.config = ConfigFactory.create(WebDriverConfig.class,System.getProperties());

    @Override
    public WebDriver get() {
        WebDriver driver = createWebDriver();
        driver.get(config.getBaseUrl());
        return driver;
    }

    private WebDriver createWebDriver() {
        if (config.getBrowser().equals(Browser.CHROME)) {
            WebDriverManager.chromedriver().setup();
            return new ChromeDriver();
        }
        if (config.getBrowser().equals(Browser.FIREFOX)) {
            WebDriverManager.firefoxdriver().setup();
            return new FirefoxDriver();
        }
        throw new RuntimeException("No such browser");
    }

}

Теперь мы можем также запускать тесты из консоли, но при этом файл конфигурации стал в разы меньше и его легче читать.

Источники данных

Важно: файлы конфигурации следует разбивать на маленькие фалы, отвечающие за определенную часть конфигов.

Данные для файлов конфигурации не всегда удобно хранить в SystemGetProperty. Иногда удобнее вынести все в отдельные файлы. К примеру, нам надо написать файл конфигурации для теста под Android-смартфон. Создадим файл AndroidConfig и заполним его:

@Config.Sources({
        "classpath:pixel.properties"
})
public interface AndroidConfig extends Config {

    @Key("platform.name")
    String platformName();

    @Key("platform.version")
    String platformVersion();

    @Key("device.name")
    String deviceName();

}

Теперь в папке resources создадим файл с названием pixel.properties с данными для конкретного смартфона:

platform.name=Android
platform.version=27.0
device.name=Google Pixel XL

Все данные из этого файла будут использованы в конфигурации.

Как запустить тест на нескольких платформах сразу

К примеру, нам надо запустить тест сначала на Android, а потом на iOS. Для этого сначала добавим файл с ресурсами для iOS и назовем его iphone12.properties:

platform.name=iOS
platform.version=15.0
device.name=Apple iPhone 12 Pro

И добавим небольшое изменение в файл конфигурации:

@Config.Sources({
        "classpath:${device}.properties"
})
public interface MobileConfig extends Config {

    @Key("platform.name")
    String platformName();

    @Key("platform.version")
    String platformVersion();

    @Key("device.name")
    String deviceName();

}

И теперь мы можем запускать тесты сразу для нескольких устройств:

public class MobileTest {

    @Test
    public void testAndroid() {
        System.setProperty("device", "pixel");

        MobileConfig config = ConfigFactory
                .create(MobileConfig.class, System.getProperties());

        assertThat(config.platformName()).isEqualTo("Android");
        assertThat(config.platformVersion()).isEqualTo("27.0");
        assertThat(config.deviceName()).isEqualTo("Google Pixel XL");
    }

    @Test
    public void testIphone12() {
        System.setProperty("device", "iphone12");

        MobileConfig config = ConfigFactory
                .create(MobileConfig.class, System.getProperties());

        assertThat(config.platformName()).isEqualTo("IOS");
        assertThat(config.platformVersion()).isEqualTo("14.0");
        assertThat(config.deviceName()).isEqualTo("iPhone 12 Pro Max Ulra High");
    }
}

Удаленный файл

Рассмотрим пример, когда мы тестируем систему локально и используем свои данные для входа, но на CI-системе необходимо использовать специальные данные.

В таком случае создаем следующий файл конфигурации:

@Config.Sources({
        "file:/tmp/secret.properties",
        "classpath:auth.properties"
})
public interface AuthConfig extends Config {

    @Key("username")
    String username();

    @Key("password")
    String password();

}

В этом случае система сначала будет искать данные для входа в "file:/tmp/secret.properties", но если не найдет, то перейдет к локальным данным "classpath:auth.properties".

Код теста:

public class AuthTest {
    @Test
    public void testLocalFile() throws IOException {
        // вот это будет происходить в Jenkins, вы к этому не имеете доступа
        String content = "username=secret-user\password=secret-pass";
        Path propsPath = Paths.get("/tmp/secret.properties");
        Files.write(propsPath, content.getBytes(StandardCharsets.UTF_8));
        // вот это будет происходить в Jenkins, вы к этому не имеете доступа

        AuthConfig config = ConfigFactory
                .create(AuthConfig.class, System.getProperties());
        assertThat(config.username()).isEqualTo("secret-user");
        assertThat(config.password()).isEqualTo("secret-pass");

        // вот это будет происходить в Jenkins, вы к этому не имеете доступа
        Files.delete(propsPath);
        // вот это будет происходить в Jenkins, вы к этому не имеете доступа
    }

}
⚠️ **GitHub.com Fallback** ⚠️