All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.tourniquet.pageobjects.SeleniumControl Maven / Gradle / Ivy

/*
 * Copyright 2015-2016 DevCon5 GmbH, [email protected]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.tourniquet.pageobjects;

import static org.slf4j.LoggerFactory.getLogger;

import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;

import io.tourniquet.junit.rules.ExternalResource;
import org.openqa.selenium.WebDriver;
import org.slf4j.Logger;

/**
 * Context for running selenium based tests. After initialization, the context is kept as a thread local so that
 * PageObjects may access it to obtain the current state of the driver and test.
 */
public class SeleniumControl extends ExternalResource {

    private static final Logger LOG = getLogger(SeleniumControl.class);

    /**
     * The base URL for the current test execution.
     */
    private String baseUrl;

    /**
     * Action to be performed on Login
     */
    private BiConsumer loginAction;

    /**
     * Action to be performed for logout
     */
    private Consumer logoutAction;

    /**
     * Flag to indicate if login has been performed
     */
    private final AtomicBoolean loggedIn = new AtomicBoolean(false);

    /**
     * Action to initialize the web driver
     */
    private Optional> driverInit;

    private Instant startTime;

    private Duration testDuration;
    private Supplier driverProvider;
    /**
     * Reference to the context managed by this rule. If a context has already been initialized, this optional is empty
     */
    private Optional managedContext;
    private boolean skipSharedDriverActions;

    @Override
    protected void before() throws Throwable {

        this.managedContext = getSeleniumContext();
        this.managedContext.ifPresent(SeleniumContext::init);
        SeleniumContext.currentContext().get().setBaseUrl(baseUrl);
        SeleniumContext.currentDriver().ifPresent(d -> {
            d.get(baseUrl);
            driverInit.ifPresent(di -> di.accept(d.manage()));
        });
        this.startTime = Instant.now();
    }

    @Override
    protected void after() {

        final Instant finishTime = Instant.now();
        this.managedContext.ifPresent(SeleniumContext::destroy);
        this.testDuration = Duration.between(this.startTime, finishTime);
        LOG.info("Test executed in {} s", this.testDuration.getSeconds());
    }

    @Override
    protected void beforeClass() throws Throwable {

        before();
    }

    @Override
    protected void afterClass() {

        after();
    }

    /**
     * Checks, if a context is already present. If that is the case, an empty optional is returned. If no context is
     * initialized, a new context is created using the driver provider of the test rule
     *
     * @return an optional containing a managed context or the empty optional if a context is already active
     */
    private Optional getSeleniumContext() {

        return Optional.ofNullable((SeleniumContext) SeleniumContext.currentContext()
                                                                    .map(c -> null)
                                                                    .orElse(new SeleniumContext(driverProvider)));
    }

    /**
     * Performs the login action with the specified user
     *
     * @param user
     *         the user to login
     */
    public final void login(User user) {
        currentDriver().filter(d -> sessionActionsEnabled()).ifPresent(d -> {
            loginAction.accept(user, d);
            loggedIn.set(true);
        });
    }

    /**
     * Performs the logout action
     */
    public final void logout() {
        currentDriver().filter(d -> sessionActionsEnabled()).ifPresent(d -> {
            this.logoutAction.accept(d);
            loggedIn.set(false);
        });
    }

    private boolean sessionActionsEnabled() {

        return managedContext.isPresent() || !skipSharedDriverActions;
    }

    /**
     * Indicates if a user is logged in to the application
     *
     * @return
     *  true if login has been performed
     */
    public boolean isLoggedIn() {

        return loggedIn.get();
    }

    /**
     * The base URL of the application to test
     *
     * @return the string representing the base URL. All relative URLs (i.e. in the page object model) must be relative
     * to this page
     */
    public String getBaseUrl() {

        return baseUrl;
    }

    /**
     * Returns the driver of this context.
     *
     * @return may be null if the test is not running.
     */
    public Optional getDriver() {

        return SeleniumContext.currentDriver();
    }

    /**
     * Returns the current context. The context is only available during test execution
     *
     * @return an Optional holding the current context.
     */
    public Optional currentContext() {

        return SeleniumContext.currentContext();
    }

    /**
     * Returns the duration of the test execution.
     *
     * @return the duration of the test execution
     */
    public Duration getTestDuration() {

        return Optional.ofNullable(this.testDuration).orElseThrow(() -> new IllegalStateException("Test not finished"));
    }

    /**
     * Returns the currentContext driver. If this method is invoked outside of a test execution, the returned Optional
     * is empty
     *
     * @return the optional of a driver
     */
    public Optional currentDriver() {

        return currentContext().flatMap(ctx -> ctx.getDriver());
    }

    /**
     * Creates a new context builder for fluent setup and instantiation.
     *
     * @return a new builder
     */
    public static SeleniumControlBuilder builder() {

        return new SeleniumControlBuilder();
    }

    /**
     * Builder for creating a Selenium test context
     */
    public static class SeleniumControlBuilder {

        private Supplier driver;

        private String baseUrl;

        private BiConsumer loginAction;

        private Consumer logoutAction;

        private Consumer optionsInitializer;

        private boolean skipSharedDriverActions = true;

        SeleniumControlBuilder() {

        }

        /**
         * Define a supplier that creates the driver. Not that the rule supports a contextual (shared) driver. In case a
         * contextual driver is present, the driver provided by this supplier is ignored.
         *
         * @param driver
         *         the driver to be used by the test
         *
         * @return this builder
         */
        public SeleniumControlBuilder driver(Supplier driver) {

            this.driver = driver;
            return this;
        }

        /**
         * Specify the base URL of the application. It is the first page to be loaded for application and used to
         * resolve relative URLs
         *
         * @param baseUrl
         *         the base url of the application
         *
         * @return this builder
         */
        public SeleniumControlBuilder baseUrl(String baseUrl) {

            this.baseUrl = baseUrl;
            return this;
        }

        /**
         * Specify an action that should be executed to perform a login to the target application.
         *
         * @param loginAction
         *         the login action to perform
         *
         * @return this builder
         */
        public SeleniumControlBuilder loginAction(BiConsumer loginAction) {

            this.loginAction = loginAction;
            return this;
        }

        /**
         * Specify an action that should be executed to perform a logout to the target application.
         *
         * @param logoutAction
         *         the logout action to perform
         *
         * @return this builder
         */
        public SeleniumControlBuilder logoutAction(Consumer logoutAction) {

            this.logoutAction = logoutAction;
            return this;
        }

        /**
         * Specify options that should be applied on the driver after initialization.
         *
         * @param optionsInitializer
         *         the options to be applied to the driver
         *
         * @return this builder
         */
        public SeleniumControlBuilder driverOptions(Consumer optionsInitializer) {

            this.optionsInitializer = optionsInitializer;
            return this;
        }

        /**
         * Configure if Login and Logout Actions should be executed, if a shared (context) driver is present. Default
         * setting is true.
         *
         * @param flag
         *         the true if actions should be skipped for shared drivers, false if not
         *
         * @return this builder
         */
        public SeleniumControlBuilder skipSharedDriverActions(boolean flag) {

            this.skipSharedDriverActions = flag;
            return this;
        }

        public SeleniumControl build() {

            final SeleniumControl ctx = new SeleniumControl();
            ctx.baseUrl = this.baseUrl;
            ctx.driverProvider = this.driver;
            ctx.driverInit = Optional.ofNullable(this.optionsInitializer);
            ctx.loginAction = this.loginAction;
            ctx.logoutAction = this.logoutAction;
            ctx.skipSharedDriverActions = skipSharedDriverActions;
            return ctx;

        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy