com.cognifide.qa.bb.aem.ui.parsys.AemParsys.java Source code

Java tutorial

Introduction

Here is the source code for com.cognifide.qa.bb.aem.ui.parsys.AemParsys.java

Source

/*-
 * #%L
 * Bobcat
 * %%
 * Copyright (C) 2016 Cognifide Ltd.
 * %%
 * 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.
 * #L%
 */
package com.cognifide.qa.bb.aem.ui.parsys;

import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;

import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.Point;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.cognifide.qa.bb.aem.exception.NoSuchComponentException;
import com.cognifide.qa.bb.aem.ui.component.AemComponentHandler;
import com.cognifide.qa.bb.aem.ui.menu.AemContextMenu;
import com.cognifide.qa.bb.aem.ui.menu.MenuOption;
import com.cognifide.qa.bb.aem.ui.window.ConfirmationWindow;
import com.cognifide.qa.bb.constants.AemConfigKeys;
import com.cognifide.qa.bb.constants.Timeouts;
import com.cognifide.qa.bb.dragdrop.DragAndDropFactory;
import com.cognifide.qa.bb.dragdrop.Droppable;
import com.cognifide.qa.bb.provider.selenium.BobcatWait;
import com.cognifide.qa.bb.qualifier.CurrentFrame;
import com.cognifide.qa.bb.qualifier.CurrentScope;
import com.cognifide.qa.bb.qualifier.Frame;
import com.cognifide.qa.bb.qualifier.Global;
import com.cognifide.qa.bb.qualifier.PageObject;
import com.cognifide.qa.bb.scope.CurrentScopeHelper;
import com.cognifide.qa.bb.scope.frame.FramePath;
import com.cognifide.qa.bb.utils.PageObjectInjector;
import com.google.inject.Inject;
import com.google.inject.name.Named;

/**
 * Represents a parsys instance on the page.
 */
@PageObject
@Frame("$cq")
public class AemParsys {

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

    private static final String SELECTOR_FOR_COMPONENT_IN_PARSYS = "div.section:not(.new)";

    @Inject
    private BobcatWait wait;

    @Inject
    private PageObjectInjector pageObjectInjector;

    @FindBy(xpath = "//div[contains(@class, 'cq-insertdialog') and contains(@style, 'visibility: " + "visible')]")
    @Global
    private AemInsertWindow insertWindow;

    @FindBy(xpath = "//div[contains(@class, 'x-menu') and contains(@style, 'visibility: visible')]")
    @Global
    private AemContextMenu contextMenu;

    @FindBy(css = ".cq-editrollover-insert-container")
    @Global
    private List<WebElement> clickableParsyses;

    @Inject
    @CurrentScope
    private WebElement currentScope;

    @CurrentFrame
    private FramePath currentFrame;

    @FindBy(css = "div.new")
    private WebElement insertComponentArea;

    @Inject
    @Named(AemConfigKeys.COMPONENT_LOCATOR_FORMAT)
    private String componentLocatorFormat;

    @Inject
    private DragAndDropFactory factory;

    @Inject
    private CurrentScopeHelper webElementHelper;

    @Inject
    private Actions actions;

    @Inject
    private ConfirmationWindow confirmationWindow;

    @Inject
    @Named(AemConfigKeys.PARSYS_FINDER_OFFSET)
    private int parsysOffset;

    public void waitToBeReady() {
        wait.withTimeout(Timeouts.MEDIUM).until(ExpectedConditions.visibilityOf(insertComponentArea));
    }

    /**
     * This should be overriden only once at the beginning. Method allows to control nested parsys.
     *
     * @param insertComponentArea selector to component area
     */
    public void setInsertComponentArea(By insertComponentArea) {
        this.insertComponentArea = currentScope.findElement(insertComponentArea);
    }

    /**
     * Opens insert window by double clicking the insert area.
     *
     * @return Insert window instance.
     */
    public AemInsertWindow openInsertWindow() {
        wait.withTimeout(Timeouts.BIG).until(webDriver -> {
            actions.doubleClick(getClickableParsys()).perform();
            return webElementHelper.isCurrentScopeVisible(insertWindow);
        }, 2);
        return insertWindow;
    }

    /**
     * Opens insert window by context clicking the insert area and picking New... option from
     * ContextMenu.
     *
     * @return Insert window instance.
     */
    public AemInsertWindow openInsertWindowByContextMenu() {
        wait.withTimeout(Timeouts.BIG).until(webDriver -> {
            contextMenu.open(getClickableParsys());
            contextMenu.clickOption(MenuOption.NEW);
            return webElementHelper.isCurrentScopeVisible(insertWindow);
        }, 2);
        return insertWindow.waitToBeDisplayed();
    }

    /**
     * Pastes component by context clicking the insert area and picking Paste option from ContextMenu.
     */
    public void pasteComponentByContextMenu() {
        final int componentsCount = componentsCount();
        wait.withTimeout(Timeouts.BIG).until(webDriver -> {
            contextMenu.open(getClickableParsys());
            contextMenu.clickOption(MenuOption.PASTE);
            return componentsCount + 1 == componentsCount();
        }, 2);
    }

    /**
     * Opens context menu for the n-th component of type componentClass. Indexing starts at 0.
     *
     * @param componentClass component class
     * @param n              component index
     * @return opened contextMenu
     */
    public AemContextMenu openContextMenuNthComponent(Class<?> componentClass, int n) {
        contextMenu.open(getComponentScope(componentClass, n));
        return contextMenu;
    }

    /**
     * Opens context menu for the first component of type componentClass.
     *
     * @param componentClass component class
     * @return opened contextMenu
     */
    public AemContextMenu openContextMenuFirstComponent(Class<?> componentClass) {
        return openContextMenuNthComponent(componentClass, 0);
    }

    /**
     * Opens context menu for n-th component, no matter what type it is. Indexing starts at 0.
     *
     * @param n option index
     * @return opened contextMenu
     */
    public AemContextMenu openContextMenu(int n) {
        contextMenu.open(currentScope.findElements(By.cssSelector(SELECTOR_FOR_COMPONENT_IN_PARSYS)).get(n));
        return contextMenu;
    }

    /**
     * Inserts a component of the type indicated by the parameter.
     *
     * @param componentClass component class name
     * @param <T>            component class
     * @return Object of a component class injected with a proper scope
     */
    public <T> T insertComponent(Class<T> componentClass) {
        By componentLocator = getComponentLocator(componentClass);
        int componentTypeCount = currentScope.findElements(componentLocator).size();

        openInsertWindow().insertComponent(componentClass);
        wait.withTimeout(Timeouts.SMALL)
                .until(driver -> currentScope.findElements(componentLocator).size() > componentTypeCount);

        return pageObjectInjector.inject(componentClass, getComponentScope(componentClass, componentTypeCount),
                currentFrame);
    }

    /**
     * Inserts a component of the type indicated by the parameter to empty parsys.
     *
     * @param componentClass component class name
     * @param <T>            component class
     * @return Object of a component class injected with a proper scope
     */
    public <T> T insertFirstComponentType(Class<T> componentClass) {
        By componentLocator = getComponentLocator(componentClass);
        openInsertWindow().insertComponent(componentClass);
        wait.withTimeout(Timeouts.SMALL).until(driver -> !currentScope.findElements(componentLocator).isEmpty());

        return pageObjectInjector.inject(componentClass, getComponentScope(componentClass, 0), currentFrame);
    }

    /**
     * Inserts a component identified by the group and name.
     *
     * @param componentGroup component group name
     * @param componentName  component name
     * @return This AemParsys.
     */
    public AemParsys insertComponent(String componentGroup, String componentName) {
        openInsertWindow().insertComponent(componentGroup, componentName);
        return this;
    }

    /**
     * Simply check if any component with a specific type is in parsys. Do not use it with negation,
     * because of performance issues. In such cases use {#link isComponentNotPresent}.
     *
     * @param componentClass component class
     * @return true if component is present
     */
    public boolean isComponentPresent(Class<?> componentClass) {
        final String cssClassName = new AemComponentHandler(componentClass).getCssClassName();
        return isComponentPresent(cssClassName);
    }

    /**
     * Simply check if any component with a specific css class name is in parsys. Do not use it with
     * negation, because of performance issues. In such cases use {#link isComponentNotPresent}.
     *
     * @param cssClassName component css class
     * @return true if component is present
     */
    public boolean isComponentPresent(String cssClassName) {
        try {
            wait.withTimeout(Timeouts.SMALL).until(
                    ExpectedConditions.visibilityOf(currentScope.findElement(getComponentLocator(cssClassName))));
            return true;
        } catch (NoSuchElementException e) {
            LOG.debug(String.format("component located by: %s is not present", cssClassName), e);
            return false;
        }
    }

    /**
     * Simply check if any component with a specific type is NOT in parsys. Do not use it with
     * negation, because of performance issues. In such cases use {#link isComponentPresent}.
     *
     * @param componentClass component class
     * @return true if component is NOT present
     */
    public boolean isComponentNotPresent(Class<?> componentClass) {
        final String cssClassName = new AemComponentHandler(componentClass).getCssClassName();
        return isComponentNotPresent(cssClassName);
    }

    /**
     * Simply check if any component with a specific css class name is NOT in parsys. Do not use it
     * with negation, because of performance issues. In such cases use {#link isComponentPresent}.
     *
     * @param cssClassName component css class
     * @return true if component is NOT present
     */
    public boolean isComponentNotPresent(final String cssClassName) {
        boolean missing = false;
        try {
            wait.withTimeout(Timeouts.SMALL)
                    .until(driver -> currentScope.findElements(getComponentLocator(cssClassName)).isEmpty());
            missing = true;
        } catch (TimeoutException e) {
            LOG.debug(String.format("component located by: %s is not present", cssClassName), e);
        }
        return missing;
    }

    /**
     * Get the first element of type componentClass. This is alias for getNthComponentOfType(Class
     * componentClass, 0).
     *
     * @param componentClass component class name
     * @param <T>            component class
     * @return object of a componentClass injected with a proper scope
     */
    public <T> T getFirstComponentOfType(Class<T> componentClass) {
        return getNthComponentOfType(componentClass, 0);
    }

    /**
     * Get the n-th element of type componentClass. Indexing starts at 0.
     *
     * @param componentClass component class name
     * @param <T>            component class
     * @param n              component index
     * @return object of a componentClass injected with a proper scope
     */
    public <T> T getNthComponentOfType(Class<T> componentClass, int n) {
        return pageObjectInjector.inject(componentClass, getComponentScope(componentClass, n), currentFrame);
    }

    /**
     * Get the n-th component. Indexing starts at 0.
     *
     * @param componentClass component class name
     * @param <T>            component class
     * @param globalIndex    component global index
     * @return object of a componentClass injected with a proper scope
     */
    public <T> T getComponent(Class<T> componentClass, int globalIndex) {
        return pageObjectInjector.inject(componentClass,
                currentScope.findElements(By.cssSelector(SELECTOR_FOR_COMPONENT_IN_PARSYS)).get(globalIndex),
                currentFrame);
    }

    /**
     * Remove the n-th occurrence of componentClass. Indexing starts at 0.
     *
     * @param componentClass component class
     * @param n              component index
     * @return This AemParsys.
     */
    public AemParsys removeNthComponentOfType(Class<?> componentClass, int n) {
        WebElement webElement = getComponentScope(componentClass, n);
        removeComponentByContextMenu(webElement);
        waitForComponentToBeRemoved(webElement);
        return this;
    }

    /**
     * Remove the n-th occurrence of componentClass using custom clickable area. Indexing starts at 0.
     *
     * @param componentClass component class
     * @param n              component index
     * @return This AemParsys.
     */
    public AemParsys removeNthComponentOfType(Class<?> componentClass, int n, By clickableArea) {
        WebElement webElement = getComponentScope(componentClass, n).findElement(clickableArea);
        removeComponentByContextMenu(webElement);
        waitForComponentToBeRemoved(webElement);
        return this;
    }

    /**
     * Remove the first occurrence of componentClass.
     *
     * @param componentClass component class
     * @return This AemParsys.
     */
    public AemParsys removeFirstComponentOfType(Class<?> componentClass) {
        return removeNthComponentOfType(componentClass, 0);
    }

    /**
     * Remove the first occurrence of componentClass using custom clickable area.
     *
     * @param componentClass component class
     * @return This AemParsys.
     */
    public AemParsys removeFirstComponentOfType(Class<?> componentClass, By clickableArea) {
        return removeNthComponentOfType(componentClass, 0, clickableArea);
    }

    /**
     * Remove the n-th(index) component, not matter what type it is. Indexing starts at 0.
     *
     * @param index component index
     * @return This AemParsys.
     */
    public AemParsys removeComponent(int index) {
        try {
            WebElement webElement = currentScope.findElements(By.cssSelector(SELECTOR_FOR_COMPONENT_IN_PARSYS))
                    .get(index);
            removeComponentByContextMenu(webElement);
            waitForComponentToBeRemoved(webElement);
        } catch (ArrayIndexOutOfBoundsException e) {
            throw new NoSuchComponentException(e);
        }
        return this;
    }

    /**
     * Remove all components in parsys.
     *
     * @return This AemParsys.
     */
    public AemParsys clear() {
        List<WebElement> list = currentScope.findElements(By.cssSelector(SELECTOR_FOR_COMPONENT_IN_PARSYS));
        list.forEach(this::removeComponentByContextMenu);
        return this;
    }

    /**
     * @return Number of components inserted into parsys.
     */
    public int componentsCount() {
        return currentScope.findElements(By.cssSelector(SELECTOR_FOR_COMPONENT_IN_PARSYS)).size();
    }

    /**
     * @return droppable area that accepts components dragged from the sidekick
     */
    public Droppable getDroppable() {
        return factory.createDroppable(getClickableParsys(), currentFrame);
    }

    private WebElement getComponentScope(Class<?> componentClass, int index) {
        final String componentCssClass = new AemComponentHandler(componentClass).getCssClassName();
        try {
            return currentScope.findElements(getComponentLocator(componentCssClass)).get(index);
        } catch (ArrayIndexOutOfBoundsException e) {
            throw new NoSuchComponentException(e);
        }
    }

    private <T> By getComponentLocator(Class<T> componentClass) {
        final String componentCssClass = new AemComponentHandler(componentClass).getCssClassName();
        return getComponentLocator(componentCssClass);
    }

    private By getComponentLocator(String componentCssClass) {
        return By.cssSelector(String.format(componentLocatorFormat, componentCssClass));
    }

    private void removeComponentByContextMenu(WebElement webElement) {
        contextMenu.open(webElement).clickOption(MenuOption.DELETE);
        confirmationWindow.acceptConfirmationWindow();
    }

    private WebElement getClickableParsys() {
        wait.withTimeout(Timeouts.SMALL).until(webDriver -> getParsysStream().count() >= 1, Timeouts.MINIMAL);
        return getParsysStream().findFirst().get();
    }

    private Stream<WebElement> getParsysStream() {
        return clickableParsyses.stream().filter(parsysByLocation(insertComponentArea.getLocation()));
    }

    private Predicate<WebElement> parsysByLocation(final Point location) {
        return input -> {
            int xPositionDiff = Math.abs(location.getX() - input.getLocation().getX());
            int yPositionDiff = Math.abs(location.getY() - input.getLocation().getY());

            return xPositionDiff + yPositionDiff <= parsysOffset;
        };
    }

    private void waitForComponentToBeRemoved(WebElement webElement) {
        wait.withTimeout(Timeouts.SMALL).until(ExpectedConditions.stalenessOf(webElement));
    }

}