Source: actions.js

'use strict';

module.exports = {
    clearInput,
    closeLastOpenedTab,
    dragAndDrop,
    executeInAlertAndDismiss,
    executeInAngular,
    executeInIframe,
    executeInProtractor,
    mouseMoveTo,
    performClicks,
    performScrolls,
    pressGlobalKey,
    selectOption,
    setWindowSize,
};

/**
 * Set browser's window size
 * @param {number} width - The desired width for the browser
 * @param {number} height - The desired height for the browser
 */
function setWindowSize(width, height) {
    browser
        .driver
        .manage()
        .window()
        .setSize(width, height);
}

/**
 * Execute a script in the context of a chosen iframe.
 * @param {string} iframeName - The name of the iframe (id)
 * @param {Function} fn - The function to execute
 */
function executeInIframe(iframeName, fn) {
    browser.switchTo().frame(iframeName);
    fn();
    browser.switchTo().defaultContent();
    browser.waitForAngular();
}

/**
 * Execute a script in an alert.
 * @param {Function} fn - The function to execute
 */
function executeInAlertAndDismiss(fn) {
    browser.wait(() => browser.switchTo().alert().then(() => true, () => false));
    browser.switchTo().alert().then(alert => {
        fn(alert);
        alert.dismiss();
    });
}

/**
 * Execute a function with an injected Angular service.
 * @param {string} serviceName - Name of the service to retrieve
 * @param {Function} fn - The function to execute
 * @example
 * <caption>Destroy Angular cache</caption>
 * toolbox.actions.executeInAngular('$cacheFactory', function ($cacheFactory) {
 *     $cacheFactory.destroy();
 * });
 */
function executeInAngular(serviceName, fn) {
    browser.executeScript((serviceName, fn) => {
        const service = angular.element('[ng-app]').injector().get(serviceName);
        let func;

        // See the doc: functions in arguments are resolved in their string representation
        eval(`var func = ${ fn }`); // eslint-disable-line no-eval
        func(service);

        angular.element('[ng-app]').injector().get('$rootScope').$apply();
    }, serviceName, fn);
}

/**
 * Close the last opened tab.
 */
function closeLastOpenedTab() {
    browser.getAllWindowHandles().then(handles => {
        browser.switchTo().window(handles[handles.length - 1]); // switch driver execution to the last tab
        browser.close();
        browser.switchTo().window(handles[0]); // switch driver execution to the first tab
    });
}

/**
 * Select a particular option of a select by its value.
 * @param {Element} select - A Protractor-wrapped element
 * @param {*} value - The value you want to select
 * @returns {Element} - The option wrapped by Protractor
 */
function selectOption(select, value) {
    return select.element(by.cssContainingText('option', value)).click();
}

/**
 * Perform a drag and drop by providing two Protractor elements
 * @param {Element} from - The Protractor-wrapped element to be moved
 * @param {Element} to - The Protractor-wrapped element receiving `from`
 */
function dragAndDrop(from, to) {
    browser
        .actions()
        .mouseDown(from)
        .mouseMove(to)
        .mouseUp()
        .perform();
}

/**
 * Move the mouse to hover the specified target.
 * @param {Element} target - The Protractor-wrapped element to hover
 */
function mouseMoveTo(target) {
    browser
        .actions()
        .mouseMove(target)
        .perform();
}

/**
 * Performs a provided number of clicks on an element.
 * @param {string} selector - A CSS selector
 * @param {number} nbClicks - The number of clicks
 */
function performClicks(selector, nbClicks) {
    browser.executeScript((selector, nbClicks) => {
        let i = 0;
        while (i < nbClicks) {
            $(selector).click();
            i++;
        }
    }, selector, nbClicks);
}

/**
 * Performs a provided number of scrolls on the page.
 * @param {number} nbScrolls - The number of scrolls to perform
 * @param {number} pxToScroll - The number of pixel to scroll
 */
function performScrolls(nbScrolls, pxToScroll) {
    nbScrolls.forEach(() => {
        if (!pxToScroll) {
            browser.executeScript('$("html, body").scrollTop(Math.random() * 10000)');
        } else {
            browser.executeScript(`$("html, body").scrollTop(${ nbScrolls % 2 === 0 ? 0 : pxToScroll })`);
        }
    });
}

/**
 * Circumvent Protractor/Selenium's bug when clearing an input doesn't update the model.
 * It seems to be linked to this issue: https://github.com/angular/protractor/issues/301
 * @param {Element} input - The Protractor-wrapped input to clear.
 */
function clearInput(input) {
    input.clear().then(() => {
        input.sendKeys(' ');
        input.sendKeys(protractor.Key.BACK_SPACE);
    });
}

/**
 * Press a key without focusing an input/textarea
 * @param {number} key - Keycode of the key you want to press.
 */
function pressGlobalKey(key) {
    protractor
        .getInstance()
        .actions()
        .sendKeys(key)
        .perform();
}

/**
 * Execute a command in the WebDriver control flow.
 * The command must call `fulfill` or `reject` to allow protractor to continue test execution.
 * It is useful when you have external modules based on promises:
 * Protractor will wait the fulfilling/rejecting of the promise before continuing test execution.
 * @param {function} command - The function to execute. It must call either fulfill or reject.
 * @return {Promise.<*>}
 * @example
 * <caption>Make an external request to some API</caption> *
 * toolbox.actions.executeInProtractor(function (fulfill, reject) {
 *     let req = new XMLHttpRequest();
 *     req.open('GET', 'http://contentsquare.github.io');
 *     req.send();
 *
 *     req.onreadystatechange = (res) => {
 *         if (req.readyState === 4 && req.status >= 200 && req.status < 300) {
 *             fulfill();
 *         } else {
 *             reject();
 *         }
 *     };
 * });
 */
function executeInProtractor(command) {
    return protractor.promise.controlFlow().execute(() => {
        let defer = protractor.promise.defer();
        command(defer.fulfill, defer.reject);
        return defer.promise;
    });
}