# all files / sockets/ protractor-runner.js

97.6% Statements 122/125
97.5% Branches 39/40
100% Functions 17/17
96.15% Lines 75/78
20 statements, 1 function, 14 branches Ignored

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166

    1
    1

    1
    12
    1
    1

    11

    11

    11
    11

    3

    3

    11
    11

    11
    11

    1
    11
    11
    11
    11
    11
    11
    11

    11
    11

    11
    1
    1

    10
    10

    10
    1

    9
    9

    10

    10
    10
    10
    10

    10
    1
    1
    1

    10

    10

    10
    10
    10
    10

    10
    10
    9
    1

    8

    10

    1
    4
    4
    4
    4
    4
    2

    1

    4
    1

    3

    3

    3

    3
    2

    1

    1
    1

     'use strict';

    // Constants:
    import { config } from '../config';
    const PROTRACTOR_PATH = join('node_modules', 'protractor', 'bin', 'protractor');
    const E2E_PATH = join(config.testDirectory, 'protractor.conf.js');

    // Utilities:
    import _ from 'lodash';
    import Promise from 'bluebird';
    import { spawn } from 'child_process';
    import log from 'npmlog';
    import { join } from 'path';

    // Dependencies:
    import stripcolorcodes from 'stripcolorcodes';

    // Errors:
    import TractorError from '../errors/TractorError';

    export default { run };

    function run (socket, runOptions) {
        if (module.exports.running) {
            log.error('Protractor already running.');
            return Promise.reject(new TractorError('Protractor already running.'));
        } else {
            module.exports.running = true;

            return Promise.resolve(config.beforeProtractor())
            .then(() => {
                log.info('Starting Protractor...\n');
                return startProtractor(socket, runOptions);
            })
            .catch((error) => {
                socket.lastMessage = socket.lastMessage || '';
                let [lastMessage] = socket.lastMessage.split(/\r\n|\n/);
                log.error(`${error.message}${lastMessage}`);
            })
            .finally(() => {
                socket.disconnect();
                return Promise.resolve(config.afterProtractor())
                .then(() => {
                    module.exports.running = false;
                    log.info('Protractor finished.');
                });
            });
        }
    }

    function startProtractor (socket, runOptions) {
        let featureToRun;
        let resolve;
        let reject;
        let protractorArgs = [];
        let deferred = new Promise((...args) => {
            resolve = args[0];
            reject = args[1];
        });

        protractorArgs.push(PROTRACTOR_PATH);
        protractorArgs.push(E2E_PATH);

        // baseUrl
        if (_.isUndefined(runOptions.baseUrl)) {
            reject(new TractorError('`baseUrl` must be defined.'));
            return deferred;
        } else {
            protractorArgs.push('--baseUrl');
            protractorArgs.push(runOptions.baseUrl);
        }

        // specs && params
        if (runOptions.hasOwnProperty("feature")) {
            featureToRun = join('/features', '/**/', `${runOptions.feature}.feature`);
        } else {
            featureToRun = '/features/**/*.feature';
            runOptions.debug = false;
        }

        let specs = join(config.testDirectory, featureToRun);

        protractorArgs.push('--specs');
        protractorArgs.push(specs);
        protractorArgs.push('--params.debug');
        protractorArgs.push(runOptions.debug);

        // cucumber tag
        if (runOptions.tag) {
            protractorArgs.push('--cucumberOpts.tags');
            protractorArgs.push(runOptions.tag);
            console.log(`Running cucumber with tag : ${runOptions.tag}`);
        }

        // run at mobile size
        Iif (runOptions.tag === '@Mobile') {
            protractorArgs.push('--params.runAtMobileSize');
            protractorArgs.push(true);
            console.log(`Running at mobile size.`);
        }

        let protractor = spawn('node', protractorArgs);

        protractor.stdout.on('data', sendDataToClient.bind(socket));
        protractor.stderr.on('data', sendErrorToClient.bind(socket));
        protractor.stdout.pipe(process.stdout);
        protractor.stderr.pipe(process.stderr);

        protractor.on('error', error => reject(new TractorError(error.message)));
        protractor.on('exit', (code) => {
            if (code) {
                reject(new TractorError('Protractor Exit Error - '));
            } else {
                resolve();
            }
        });
        return deferred;
    }

    function sendDataToClient (data) {
        this.lastMessage = data.toString();
        let messages = data.toString().split(/\r\n|\n/);
        messages.forEach((message) => {
            data = formatMessage(stripcolorcodes(message).trim());
            if (data && data.message.length) {
                this.emit('protractor-out', data);
            }
        });
    }

    function formatMessage (message) {
        // If it looks like an error:
        if (message.match(/Error\:/)) {
            return {
                message,
                type: 'error'
            };
        }

        // Remove line numbers from step definitions:
        message = message.replace(/\s*?# .*/g, '...');
        // Trim leading whitespace from scenario outlines:
        message = message.replace(/^\s*/gm, '');
        // Remove server URL:
        message = message.replace(/ at http.*/g, '.');
        // Really shitty match for some Error messages.
        // I'm not actually sure why these come back on stdout...?

        // If it's not something we care about, ignore it:
        if (!message.match(/^(Feature|Scenario|Given|Then|When)/)) {
            return null;
        }

        return {
            message,
            type: 'info'
        };
    }

    function sendErrorToClient () {
        this.emit('protractor-err', {
            message: 'Something went really wrong - check the console for details.',
            type: 'error'
        });
    }