Инициализация драйвера, где нужно делать и почему.

если хочешь попарить и потратить мимнимум пол года на написание стабильной обёртки над селениумом и утилит для проектика с автотестами. То у тебя обязательно должны быть классы такие: BrowserFactory - с набором браузеров, свойств для них, каких-то ещё действией над браузерами; WebDriverHolder - с инстансом браузера; WebDriverInstances - в котором будет сетер браузера, гетер вебдрайвера и например "InheritableThreadLocal"для возможности ранить тесты многопоточно.

А если времени нет такого для “свобододумия”(хотя костылеклепания вообще), лучше используй готовые решения selenide, JDI(epam), прочие готовые фрейворки(обёртки над селениумом) или же в пайтон мире RobotFramework
хотя в пайтон не знаю что лучше использовать, может питонист тебе подскажет готовые удобные фреймворки

1 лайк

Для начала, Вы ответься себе на вопросы о концепции вашего тест приложения в целом, как вы будете его строить и по каким канонам. Возможно щас закритикуют, но для автотестов красота и гармония кода, как и его скорость (утрированно, так как большую часть мы не в коде, а в поисках елементов на странице) имею малую долю. Тесты должны в первую очередь быть поддерживаемые. Да, код тоже должен быть ок, иметь структурность, какие никакие патерны (страница, елемент), что бы Вы сами в нем не утонули, а значит что код должен быть не сложным, а простым и не тяжелым, а легким (Александр Соловьев, привет тебе)
Теперь о драйвере.

  1. Если вы не следуете каким либо патернам, идеомам, и опыту использования DSL подхода, то можете передавать драйвер как угодно и где угодно, смотрим правде в глаза - многие так начинали, и потом сами озонавали глупость. Собственный опыт так же необходим.
  2. Если вы понимаете что запуск драйвера является инфраструктурной конфигурируемой частью, то вы явно сможете описать синглтон для базового класса ваших страниц/елементов. Тем не менее вам так же прийдется дополнить ваши классы серьезным базовым функционалом для работы с елементами, не всегда это удобно, зато смотришь и любуешь - насколько прозрачный DSL вышел.
  3. Если вы следуете из композиционной стратегии вашего приложения, то драйвер вы передадите как агрумент главному классу приложения, а для тестов выставите публичные методы и атрибуты, передав приложение как фикстуру для тест оболочки, которая будет существовать до конца тестов - сессионная фикстура.

далее ИМХО: Я считаю что каждый для себя должен выбрать любой ему удобный подход, так как писать тесты именно ему, и поддерживать тоже. Если разработка ведется командой, то с ними этот вопрос так же необходимо решать. В любом случае, Вы так же должны понимать, что рано или позно “кирпич падает”, а работа заканчивается, и после Вас кто то другой будет это все поддерживать, так сделайте же таким образом, что бы уши у Вас не горели.
Что касается шибко умных программистов, и дзен-кодинга, то я думаю что тесты нужно писать, а не затейливо выражаться с помощью средств языка программирования.

1 лайк

Интересная темка завелась :slight_smile: Приведу свой пример, может кому-то пригодиться, или что-то дельное подскажите…

На мобильном (Appium) у меня реализовано немного криво, через ApplicationManager:

DriverSetup

public class DriverSetup {

    private static final Logger logger = LogManager.getLogger(DriverSetup.class);

    private static DriverSetup _instance = null;

    private AndroidDriver driver;
    private WebDriverWait wait;
    private URL           serverUrl;

    public int EXPLICIT_WAIT_TIME;
    public int DEFAULT_WAIT_TIME;
    public int IMPLICIT_WAIT_TIME;

    public String WAIT_ACTIVITY;
    public String APPIUM_PORT;

    public String NEW_COMMAND_TIMEOUT;
    public String DEVICE_READY_TIMEOUT;

    public String DEVICE_NAME;
    public String BROWSER_NAME;
    public String PLATFORM_VERSION;
    public String PLATFORM_NAME;
    public String APP_PKG;
    public String APP_ACTIVITY;
    public String AUTOMATION_INSTRUMENTATION;
    public String APPLICATION_NAME;
    public String PERFORMANCE_LOGGING;

    private DesiredCapabilities capabilities = new DesiredCapabilities();
    private Properties prop = new Properties();

    public DriverSetup() {

        this.loadConfigProp();
        this.setCapabilities();

        try {
            serverUrl = new URL("http://127.0.0.1:" + APPIUM_PORT + "/wd/hub");
        } catch (MalformedURLException e) {
            e.printStackTrace();
            logger.error(e);
        }
        driver = new AndroidDriver(serverUrl, capabilities);
        //EXPLICIT_WAIT_TIME
        wait = (WebDriverWait) new WebDriverWait(driver, DEFAULT_WAIT_TIME)
                .withMessage("Element was not found")
                .ignoring(MoveTargetOutOfBoundsException.class);
        //driver.manage().timeouts().implicitlyWait(IMPLICIT_WAIT_TIME, TimeUnit.SECONDS);
        // IMPLICIT_WAIT_TIME
        driver.manage().timeouts().implicitlyWait(IMPLICIT_WAIT_TIME, TimeUnit.SECONDS);
    }

    public static DriverSetup getInstance() {
        if (_instance == null) {
            _instance = new DriverSetup();
        }
        return _instance;
    }

    public AndroidDriver getDriver() {
        return driver;
    }

    public WebDriverWait getWait() {
        return wait;
    }

    public void loadConfigProp() {
        EXPLICIT_WAIT_TIME = Integer.parseInt(PropertyLoader.loadProperty("explicitWait"));
        DEFAULT_WAIT_TIME = Integer.parseInt(PropertyLoader.loadProperty("defaultWait"));
        IMPLICIT_WAIT_TIME = Integer.parseInt(PropertyLoader.loadProperty("implicitWait"));
        WAIT_ACTIVITY = PropertyLoader.loadProperty("waitActivity");
        APPIUM_PORT = PropertyLoader.loadProperty("appiumServerPort");
        NEW_COMMAND_TIMEOUT = PropertyLoader.loadProperty("newCommandTimeout");
        DEVICE_READY_TIMEOUT = PropertyLoader.loadProperty("deviceReadyTimeout");

        DEVICE_NAME = PropertyLoader.loadProperty("deviceName");
        BROWSER_NAME = PropertyLoader.loadProperty("browserName");
        PLATFORM_VERSION = PropertyLoader.loadProperty("platformVersion");
        PLATFORM_NAME = PropertyLoader.loadProperty("platformName");
        APP_PKG = PropertyLoader.loadProperty("applicationPackage");
        APP_ACTIVITY = PropertyLoader.loadProperty("applicationActivity");
        AUTOMATION_INSTRUMENTATION = PropertyLoader.loadProperty("automationInstumentation");
        APPLICATION_NAME = PropertyLoader.loadProperty("applicationPath");
        //PERFORMANCE_LOGGING = PropertyLoader.loadProperty("enablePerformanceLogging");
    }

    public void setCapabilities() {
        capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, DEVICE_NAME);
        capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, BROWSER_NAME);
        capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, PLATFORM_VERSION);
        capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, PLATFORM_NAME);
        capabilities.setCapability(MobileCapabilityType.APP_PACKAGE, APP_PKG);

        if (prop.containsKey("waitActivity")) {
            capabilities.setCapability(MobileCapabilityType.APP_WAIT_ACTIVITY, WAIT_ACTIVITY);
        } else {
            capabilities.setCapability(MobileCapabilityType.APP_ACTIVITY, APP_ACTIVITY);
        }
//        if (!APPLICATION_NAME.isEmpty()) {
//            capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AUTOMATION_INSTRUMENTATION);
//            capabilities.setCapability(MobileCapabilityType.APP, new File(
//                    ClassLoader.getSystemResource(APPLICATION_NAME).getFile()).getAbsolutePath());
//        }
        capabilities.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, NEW_COMMAND_TIMEOUT);
        capabilities.setCapability(MobileCapabilityType.DEVICE_READY_TIMEOUT, DEVICE_READY_TIMEOUT);
        //capabilities.setCapability("unicodeKeyboard", true);
    }

}

пока эксперементирую с вейтами и наверное стоит переделать на ENUM переменные и избавиться от такого loadConfigProp

ApplicationManager - тут у меня скрины инициализируются и сам драйвер с вейтом…

public class ApplicationManager {

    public AndroidDriver driver;
    public WebDriverWait wait;

    // Helpers
    private NavigationHelper navigationHelper;
    private DeviceHelper     deviceHelper;

    // Screens
    private SomeScreen    someScreen;

    public ApplicationManager() {
        driver = DriverSetup.getInstance().getDriver();
        wait = DriverSetup.getInstance().getWait();
    }

    /***************
     * Helpers
     ***************/

    public NavigationHelper getNavigationHelper() {
        if (navigationHelper == null) {
            navigationHelper = new NavigationHelper(this);
        }
        return navigationHelper;
    }

    public DeviceHelper getDeviceHelper() {
        if (deviceHelper == null) {
            deviceHelper = new DeviceHelper(this);
        }
        return deviceHelper;
    }

    /***************
     * Screens
     ***************/

    public SomeScreen getSomeScreen() {
        if (someScreen == null) {
            someScreen = new someScreen(this);
        }
        return somScreen;
    }

    /***************
     * Others
     ***************/

    public void stop() {
        if (driver != null) {
            driver.quit();
        }
    }
}

BaseScreen

public abstract class BaseScreen<T extends BaseScreen<T>> {

    private static final Logger logger = LogManager.getLogger(BaseScreen.class);

    protected ApplicationManager manager;
    protected AndroidDriver      driver;
    protected WebDriverWait      wait;

    public BaseScreen(ApplicationManager manager) {
        this.manager = manager;
        this.driver = manager.driver;
        this.wait = manager.wait;
    }

    public void loadScreen() {
        PageFactory.initElements(new AppiumFieldDecorator(driver), this);
    }

    @AndroidFindBy(className = "android.webkit.WebView")
    private WebElement androidWebView;

    @SuppressWarnings("unchecked")
    public T then() {
        return (T) this;
    }

    @SuppressWarnings("unchecked")
    public T with() {
        return (T) this;
    }

    @Step("Tap by element {0}")
    public BaseScreen tap(WebElement element) {
        element.click();
        return (T) this;
    }

    @Step("Tap with hold by element {0}")
    public T longTap(WebElement element, int longPressTime) {
        TouchAction action = new TouchAction(driver);
        action.longPress(element, longPressTime).release().perform();
        return (T) this;
    }

    @Step("Type text {1} in field {0}")
    public T typeTextInField(WebElement element, String text) {
        element.clear();
        element.sendKeys(text);
        hideKeyboard();
        return (T) this;
    }

    @Step("Select checkBox \"{0}\"")
    public T selectCheckBox(WebElement element) {
        if (!element.isSelected()) {
            element.click();
        }
        return (T) this;
    }

    public T goToAppMainScreen() {
        driver.navigate().back();
        return (T) this;
    }

    @Step("Hide keyboard")
    public T hideKeyboard() {
        Process p = null;
        try {
            p = Runtime.getRuntime().exec("adb shell dumpsys input_method | grep mInputShown");
            BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
            String outputText = "";

            while ((outputText = in.readLine()) != null) {

                if (!outputText.trim().equals("")) {
                    String keyboardProperties[] = outputText.split(" ");
                    String keyValue[] = keyboardProperties[keyboardProperties.length - 1].split("=");

                    String softkeyboardpresenseValue = keyValue[keyValue.length - 1];
                    if (softkeyboardpresenseValue.equalsIgnoreCase("false")) {
                        logger.info("Keyboard already closed");
                    } else {
                        driver.hideKeyboard();
                        logger.info("Keyboard closed");
                    }
                }
            }
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return (T) this;
    }

    @Step("Rotate device screen: LANDSCAPE / PORTRAIT")
    protected T rotateScreen() {
        driver.rotate(ScreenOrientation.LANDSCAPE);
        return (T) this;
    }

    @Step("Switch to webview")
    protected T switchToWebView() {
        //wait.until(ExpectedConditions.visibilityOf(androidWebView));
        Set<String> contextSet = driver.getContextHandles();
        for (String contextName : contextSet) {
            if (!contextName.contains("NATIVE_APP")) {
                driver.context(contextName);
                break;
            }
        }
        return (T) this;
    }

    @Step("Take screenshot")
    protected T takeScreenShot(String fileName) {
        File file    = new File(fileName + ".png");
        File tmpFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
        try {
            FileUtils.copyFile(tmpFile, file);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return (T) this;
    }
}

Собственно вот сам скрин:

public class SomeScreen extends BaseScreen<SomeScreen> {

    @AndroidFindBy(uiAutomator = "new UiSelector().text(\"Write Message\")")
    private WebElement writeMessageButton;

    public SomeScreen(ApplicationManager manager) {
        super(manager);
        loadScreen();
    }

    // методы тут идут по работе с элементами

   public SomeScreen sendMessages(String message) {
    tap(this.writeMessageButton);
    typeTextInField(this.sendMessagesField, message);
    tap(this.sendMessagesButton);
    return this;
  }

}

вот собственно мой тест:

public class SomeTest extends BaseTest {

    @Test(enabled = true, groups = "some groups", priority = 10)
    @Title("title")
    public void someTestName() {
        // --------------------- Test Data ----------------------//

        // --------------------- Test Case ----------------------//
        app.getSomeScreen()
                .sendMessages("qwerty")
                .goToAppMainScreen();
    }

}

конечно тетс сократил, там у меня еще контейнеры, в которых логика состредаточена для разных девайсов и разных имен и разных сообщений, но для примера будет понятно…

BaseTest

//@Listeners({ScreenshotListener.class, TestListener.class})
@Listeners({TestListener.class})
public class BaseTest {

    private static final Logger logger = LogManager.getLogger(BaseTest.class);

    protected ApplicationManager app;

    @BeforeSuite(alwaysRun = true)
    public void setUp() {
        app = new ApplicationManager();
    }

    @AfterSuite(alwaysRun = true)
    public static void createAllureProperties() {
        AllureProperties.create();
    }

    @AfterSuite(alwaysRun = true)
    public void tearDown() {
        //app.stop();
    }

}

Начал писать все это когда еще ничерта не понимал, и только начинал разбираться в автоматизации, поэтому не очень здорово тут все получилось, сейчас вот переписываю так, чтобы все ожидания и методы с UI элементами - перенести в отдельные классы.

Это так у меня под мобилку для Android, фреймворк такой, сейчас уже понял что не так, чуть опыта поднабрался, переписываю… На вебе там все посложнее будет, там у меня реализована многопоточность и куча браузеров и запуск под разными ОС…

При написании фреймворка в автоматизации для себя выделил такие вещи:

  1. весь код должен переиспользоваться так, чтобы в пейджах были только элементы и работа с ними
  2. при чтении тестов все должно быть предельно понятно, что происходит
  3. драйвер и ожидания не должны быть в тестах и не должны быть в пейджах
  4. все методы касающиеся элемнтов - должны находиться в своем классе
  5. все методы по ожиданиям - должны находится в своем классе
  6. фреймворк должен писаться так, чтобы я сначала проектировал тест, а из него уже генерировал методы. Проще придумать тест, каким он должен быть, а затем уже писать методы для теста
  7. везде должны соблюдаться уровни доступа
2 лайка

А я инициализирую драйвер в TestBase классе, от которого наследуются все тесты и при запуске теста происходит инициализаци )

@Listeners({LogListener.class, TestReport.class})
public class TestBase {

    /**
     * Логгер
     */
    private static final Logger LOG = LogManager.getLogger(TestBase.class.getName());

    /**
     * Профайл FireFox
     */
    private String firefoxProfile;

    /**
     * Инстанс ВебДрайвера
     */
    protected static EventFiringWebDriver driver;

    /**
     * Инициализация параметров
     * @param context контекст выполнения
     */
    private void initParameters(ITestContext context){
        firefoxProfile = context.getCurrentXmlTest().getParameter("firefoxProfile");
    }

    /**
     * Инициализация драйвера
     */
    private void initDriver(){
        WebDriver wDriver = new FirefoxDriver(initCapabilities(firefoxProfile));
        driver = new EventFiringWebDriver(new StaleTolerantWrapper(wDriver).getDriver());
        driver.register(new EventHandler());
        driver.manage().window().maximize();
        driver.manage().timeouts().pageLoadTimeout(130, TimeUnit.SECONDS);
        driver.manage().timeouts().implicitlyWait(130, TimeUnit.SECONDS);
    }

    /** Инициализация свойств
     * @param profileName имя профайла
     * @return инстанс браузера
     */
    private DesiredCapabilities initCapabilities(String profileName){
        LOG.info("Используется профайл: " + profileName);
        DesiredCapabilities firefox;
        ProfilesIni allProfiles = new ProfilesIni();
        FirefoxProfile profile = allProfiles.getProfile(profileName);
        firefox = DesiredCapabilities.firefox();
        firefox.setCapability(FirefoxDriver.PROFILE, profile);
        return firefox;
    }

    /**
     * Получить инстанс драйвера
     * @return инстанс драйвера
     */
    public static EventFiringWebDriver getDriver(){
        return driver;
    }

    /**
     * Инициализация Теста
     * @param context контекст выполнения
     */
    @BeforeSuite (description = "Инициализация драйвера")
    public void initBase(ITestContext context){
        LOG.info("Старт firefox");
        initParameters(context);
        initDriver();
    }

    /**
     * По окончанию сьюта гасим браузер
     */
    @AfterSuite(alwaysRun = true, description = "Стоп firefox")
    public void tearDown() {
        driver.quit();
    }
}

Человеку нужно разобраться в концепции для начала. А вы ему еще больше кода кидаете.

1 лайк

Дак давайте разбиремся, просто на таких примерах можно показать, что хорошо, а что плохо…

Очень распространенное решение, я и сам так делал :wink:

а как теперь делаете?)

Делал и делаю) Так же попробую описать еще один интересный подход из практики:
Есть Singleton дли инициализации драйвера (заточен на firefox)

public class WebDriverInstance {
    private static WebDriver ourInstance;

public static   WebDriver getInstance() {
    if (ourInstance == null){
    return ourInstance = Initialize();
    }
    return ourInstance;
}

private WebDriverInstance() {
}

private static WebDriver Initialize() {
    FirefoxProfile profile = new FirefoxProfile();
    profile.setPreference("network.automatic-ntlm-auth.trusted-uris", "xx.com,localhost");
    profile.setPreference("network.ntlm.send-lm-response", true);

    WebDriver driver = new FirefoxDriver(profile);    //get browser
    driver.manage().window().maximize();
    driver.manage().timeouts().implicitlyWait(20, TimeUnit.SECONDS);
    driver.manage().timeouts().pageLoadTimeout(100, TimeUnit.SECONDS);
    WebDriverRunner.setWebDriver(driver); //SELENIDE
    System.out.println("WebDriverRunner.setWebDriver(driver);");
    Configuration.timeout = 50000;
    return driver;
}

}`

Так же имеется класс EnvironmentConfiguration откуда берется драйвер для дальнейших нужд

`public class EnvironmentConfiguration {
    private boolean disposed = false;
    private String applicationUrl;
    private WebDriver driver;


public  EnvironmentConfiguration(String applicationUrl) {
    this(applicationUrl, WebDriverInstance.getInstance());
}

public EnvironmentConfiguration(String applicationUrl, WebDriver driver) {
    this.applicationUrl = applicationUrl;
    this.driver = driver;
}

}`

Далее идет абстрактный Page в котором собраны общие методы по работе со страницами и в конструкторе создается конфиг, который в свою очередь подтягивает драйвер

`public abstract class Page implements IPage {
        protected EnvironmentConfiguration configuration;
        protected int pageOpenTimeout = 500;
        protected IPage prevPage;
        protected Logger l = Logger.getLogger(Page.class);

public Page(EnvironmentConfiguration configuration) {
    if (configuration == null) {
        throw new IllegalArgumentException("configuration is null");
    }

    this.configuration = configuration;
}`

Потом от этого Page наследуются все страницы. В тесте создается страница и в нее предается конфиг.

Так как я пользовался jbehave то в базовом тестовом классе конфиг поднимался так

`public class TestBase  {
    protected EnvironmentConfiguration config;


@BeforeStory
public void beforeMethod() {
     if(config == null){
    config = new EnvironmentConfiguration(application_url);
     }
   
}`

в общем инициализацию драйвера надо делать исключительно за пределами тестов и объектов страниц или компонентов, но при этом делать это так, чтобы этот самый драйвер юзать не только в BaseScreen, но также можно было его юзать и в других класах… Подходов есть куча разных.

Самая большая проблема, наверное - это то, что в BasePage приходится скапливать все методы по работе с пейджом, зачастую туда ложатся методы ожиданий, методы связанные с кликом и прочими вещами, что не есть хорошо, поскольку лучше разделять по такому принципу:

  • методы по работе с элементами лежат в своем классе
  • методы ожиданий в своем
  • методы которые обобщенные для переиспользования на других всех скринах или большинства - лежать в BasePage

в джаве нет множественного наследования, поэтому ты можешь наследовать только BasePage, в котором будут лежать только общие методы для всех скринов. Методы по работе с элементами уже туда не сложишь. Получается надо либо ухищряться, либо делать каскадное наследование: - UiElements наследуется от WaitingElementsts, WaitingElements в свою очередь наследуется от BasePage в котором реализован Driver и так драйвер переходит в тесты.

На помощь еще могут прийти хелперы, но придется их дергать, что тест делает не очень красивым…

Вот наверное почему driver должен быть изолирован от тестов и страниц, потому что его ты будешь инициализировать в разных класах своего приложения и везде он должен работать…

Даже чтобы сделать скриншот листенером, тебе придется использовать там драйвер… А сами тесты уже будут юзать реализацию с этим драйвером. То есть не придется заботиться о драйвере, потому что это будет делать framework за тебя…

Инициализация драйвера - это же по сути вызов getDriver :slight_smile:

Сорри, за незапланированный перерыв.

Все таки хотелось бы довести обсуждение до какой то логической точки.

Итак, по моему мнению:
Условие для инициализации драйвера это запуск теста или группы тестов:)
Тестам драйвер вообще не нужен.
Драйвер нужен поидее страницам и тем компонентам, которые со страницами работают, и драйвер возможно туда стоит просто передавать.

Рассуждения более менее верные?

Начнем с того, что драйвер запускает не тесты, а браузер. Тесты запускает unit framework (к примеру, testng / junit). Для драйвера важно не “что”, а “где и когда”. При этом, “где” - в изолированном потокобезопасном контексте. “Когда” - к примеру, перед [suite, test, class, method]. Вот тут вам сразу и задачка - понять, когда лучше всего следует поднимать драйвер.

А зачем страницам нужен драйвер? Страницы - это слой вашего домена, а драйвер - фреймворка. Если вы выносите драйвер на уровень страниц, это ничем не лучше его использования в самих тестах.

Да, я понимаю, что драйвер запускает не тесты:). Я хотел написать, что условие для инициализации драйвера - запуск тестов. Мы запустили тесты и драйвер должен тоже запуститься. Каким образом пока не важно.

Страницам нужен драйвер, потому что методы страниц с помощью драйвера что-то делают на базовом уровне - методы позволяют работать с элементами страницы и совершать какие-то действия. Каким образом можно минимизировать зависимость страницы от драйвера?

Т.е. если у вас есть 10 страниц, и во всех нужно вводить какие-то значения в input поля, вы в каждой странице будете дублировать driver.findElement(locator).sendKeys(text) по отношению к каждому полю, так?

Мне кажется на данном этапе, Вы с ТС должны определиться с уровнями абстракции, а впоследующем уже и о драйвере говорить.

ТС должен сам к этому прийти. Намеков было уже предостаточно.

Сказать честно, я и сам уже потерялся.

Нет, я не имел ввиду, что для каждой страницы надо будет делать тоже самое, я понимаю, что можно будет вынести методы работы с контролами в отдельный класс или группу классов, это и будет частью фреймворка (поидее).

Наверное стоит подвести небольшой итог, из того что уже насобиралось.

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

Остается только правильно организовать зависимость между слоем с универсальными методами и DriverFactory, на счет этого еще надо подумать.

В целом, такой подход имеет шанс на успех:)?

Я бы не стал обобщать. :slight_smile:

Это 2 совершенно разных паттерна.

Но в целом верно.

Это 1 из 2х вариантов. Второй - вызов экшенов прямо из контролов. Например:

setText(inputUsername, name); // common action for all pages
// or
inputUsername.type(name); // control-specific action

Тут нет никакой зависимости. По крайней мере от DriverFactory. От драйвера - да, но не фабрики. DriverFactory лишь создает новый драйвер, сокрывая детали инициализации. Кому, как и что с ним делать дальше - уже другой вопрос.

Вам лишь необходимо понять, что все ключевые сущности, которым необходим драйвер, находятся вне зоны ответственности доменного слоя.

Большое спасибо за терпение и за мудрость:)