action.js

import R from 'ramda';
import KeyCodes from 'keycode-js';
import {code as dragAndDrop} from 'xl-html-dnd';
import {Expectation} from './expectation';
import {JQueryAction} from './jquery-action';
import {ActionUtil} from './action-util';
import {ElementUtil} from './element-util';
import {WaitCondition} from './wait-condition';

const bulkOfClicks = (action, selectors) => {
    for (const selector of selectors) {
        action(selector);
    }
};

/**
 * Base DSL actions
 */
export class Action {
    /**
     * Performs a bulk of Ctrl clicks.
     *
     * @param selectors
     */
    static bulkCtrlKey(selectors) {
        return bulkOfClicks(Action.ctrlClick, selectors);
    }

    /**
     * Performs a bulk of Shift clicks.
     *
     * @param selectors
     */
    static bulkShiftKey(selectors) {
        return bulkOfClicks(Action.shiftClick, selectors);
    }

    /**
     * Clear text in found element.
     *
     * @param selector
     */
    static clearText(selector) {
        const action = () => {
            ElementUtil.elementFinder(selector).isSelected().then((selected) => {
                if (!selected) {
                    Action.click(ElementUtil.elementFinder(selector));
                }

                ElementUtil.elementFinder(selector).getAttribute('value').then((value) => {
                    if (value) {
                        R.times(() => ElementUtil.elementFinder(selector)
                            .sendKeys(protractor.Key.BACK_SPACE), value.length);
                    }
                });
            });
        };
        const condition = () => WaitCondition.textEquals(selector, '');
        ActionUtil.repeatAction(action, condition);
    }

    /**
     * Clicks on element if it's clickable.
     * For example button can be disabled and click won't occur, you need to fetch that unaccepted behavior earlier.
     *
     * @param {Object} selector CSS Selector or Protractor Element
     */
    static click(selector) {
        Expectation.clickable(selector);

        browser.executeScript('window.scrollTo(0,0);').then(() => {
            ActionUtil.expectExecutedAction(() => ElementUtil.elementFinder(selector).click());
        });

        browser.sleep(500);
    }

    /**
     * Clicks on element nevertheless if it's clickable or not. You can use it when
     * element is appeared only for some period of time and then disappears.
     * As e2e especially for IE is slow it can happen that Protractor can miss to click on that element during
     * that period of time. For example it can be used to close timed notification messages to proceed further,
     * as toastr might hide some elements which you want to click.
     *
     * @param {Object} selector CSS Selector or Protractor Element
     */
    static clickIfClickable(selector) {
        const finder = ElementUtil.elementFinder(selector);
        return expect(ActionUtil.execute(() => finder.click().then(R.F, R.F)));
    }

    /**
     * Performs an enter on a certain element.
     *
     * @param selector
     */
    static clickEnter(selector) {
        return Action.keyPress(selector, KeyCodes.KEY_RETURN);
    }

    /**
     * Performs an escape on a certain element.
     *
     * @param selector
     */
    static clickEscape(selector) {
        return Action.keyPress(selector, KeyCodes.KEY_ESCAPE);
    }

    /**
     * Performs double click on a certain element.
     *
     * @param selector
     */
    static doubleClick(selector) {
        Expectation.displayed(selector);
        const doubleClick = (element) => bean.fire(element, 'dblclick');
        return Action.executeVoidScript(doubleClick, ElementUtil.elementFinder(selector));
    }

    /**
     * Performs Ctrl click on a certain element.
     *
     * @param selector
     */
    static ctrlClick(selector) {
        return JQueryAction.click(selector, {ctrlKey: true});
    }

    /**
     * Executes native JavaScript function.
     *
     * @param {function} scriptFunction
     * @param {array} scriptArguments
     */
    static executeVoidScript(scriptFunction, ...scriptArguments) {
        const script = `(${scriptFunction}).apply(null, arguments);`;
        return ActionUtil.expectExecutedAction(() => browser.executeScript(script, ...scriptArguments));
    }

    /**
     * Focuses on a certain element.
     * Mainly has to be used for input fields.
     *
     * @param selector
     */
    static focus(selector) {
        Expectation.clickable(selector);
        return ActionUtil.expectExecutedAction(() => ElementUtil.elementFinder(selector).focus());
    }

    /**
     * Hovers on a certain element.
     *
     * @param selector
     */
    static hover(selector) {
        Expectation.displayed(selector);
        const hover = (element) => bean.fire(element, 'mouseover');
        Action.executeVoidScript(hover, ElementUtil.elementFinder(selector));
    }

    /**
     * Clicks on element by using native JavaScript execution.
     *
     * @param {Object} selector CSS Selector or Protractor Element
     */
    static jsClick(selector) {
        Expectation.displayed(selector);
        Expectation.clickable(selector);

        function clickIt() {
            arguments[0].click(); // eslint-disable-line prefer-rest-params
        }

        return Action.executeVoidScript(clickIt, ElementUtil.elementFinder(selector));
    }

    /**
     * Drag the element and drops it to a certain area.
     *
     * @param fromElement
     * @param toElement
     * @param waitBeforeDropping time to wait between drag and dropping the element
     */
    static jsDragAndDrop(fromElement, toElement, waitBeforeDropping = 500) {
        Expectation.displayed(fromElement);
        Expectation.displayed(toElement);

        const draggedItem = ElementUtil.elementFinder(fromElement);
        const droppable = ElementUtil.elementFinder(toElement);

        const script = () =>
            browser.executeScript(dragAndDrop, draggedItem, droppable, waitBeforeDropping);

        ActionUtil.expectExecutedAction(script);
        browser.sleep(1500);
    }

    /**
     * Performs a custom keydown event on a certain element.
     *
     * @param selector
     * @param keyCode
     */
    static keyDown = (selector, keyCode) => {
        const sendKey = (element, code) =>
            ReactTestUtils.Simulate.keyDown(element, {charCode: code, keyCode: code, which: code});
        return Action.executeVoidScript(sendKey, ElementUtil.elementFinder(selector), keyCode);
    };

    /**
     * Performs a custom keyPress event on a certain element.
     *
     * @param selector
     * @param keyCode
     */
    static keyPress = (selector, keyCode) => {
        const sendKey = (element, code) =>
            ReactTestUtils.Simulate.keyPress(element, {charCode: code, keyCode: code, which: code});
        return Action.executeVoidScript(sendKey, ElementUtil.elementFinder(selector), keyCode);
    };

    /**
     * Performs Shift click on a certain element.
     *
     * @param selector
     */
    static shiftClick(selector) {
        return JQueryAction.click(selector, {shiftKey: true});
    }

    /**
     * Types a text into a specified element.
     *
     * @param selector
     * @param text
     */
    static typeText(selector, text) {
        if (text) {
            Action.click(ElementUtil.elementFinder(selector));
            for (const chars of text.split('')) {
                ElementUtil.elementFinder(selector).sendKeys(chars);
            }
        }
    }

    /**
     * Cleans previously typed text and fill in with a new value.
     * @param selector
     * @param text
     */
    static typeNewText = (selector, text) => {
        Action.clearText(selector);
        Action.typeText(selector, text);
    };
}