Source: steps.js

"use strict";
/**
 * Steps for video recording.
 *
 * @mixin VideoSteps
 * @prop {VideoRecorder} video - Video recorder instance.
 */

var fs = require("fs");
var path = require("path");

var _ = require("lodash");
var expect = require("chai").expect;
var resolution = require("screen-resolution");
var uuid = require("uuid/v4");
var U = require("glace-utils");
var LOG = U.logger;

require("./fixtures");
var VideoRecorder = require("./video");

var VideoSteps = {
    /* modules */
    __fs: fs,
    __resolution: resolution,
    __VideoRecorder: VideoRecorder,

    startVideo: async function (opts) {
        /**
         * Step to start video recording. Step recall will be skipped if video
         *  recording wasn't stopped before.
         *
         * @async
         * @memberOf VideoSteps
         * @method startVideo
         * @instance
         * @arg {object} [opts] - Step options.
         * @arg {string} [opts.name] - File name. Extension `.avi` will be
         *  added automatically. Default name will be generated with `uuid`
         *  algorithm.
         * @arg {string} [opts.dir] - Folder to save video.
         * @arg {boolean} [opts.check=true] - Flag to check that video recording
         *  was launched.
         * @return {Promise<boolean>} `true` if step was executed, `false` if
         *  was skipped.
         * @throws {AssertionError} If video recording wasn't launched.
         */

        if (this._isVideoStarted) {
            LOG.warn("Step to start video recording was passed already");
            return false;
        };

        opts = U.defVal(opts, {});
        var check = U.defVal(opts.check, true);

        LOG.info("Starting video recording...");

        this.video = this.video || new this.__VideoRecorder();

        var videoOpts = await this._videoLocation();
        videoOpts.path = this._makeVideoPath(opts);
        allure.step(`Record video to ${videoOpts.path}`);

        this.video.configure(videoOpts);
        this.video.start();
        await this.pause(1, "it needs a time to start recording");

        if (check) {
            expect(this.video.isRunning,
                "Video recording wasn't launched")
                .to.be.true;
        };

        this._isVideoStarted = true;

        LOG.info("Video recording is started");
        allure.pass();

        return true;
    },

    stopVideo: async function (opts) {
        /**
         * Step to stop video recording. Step call will be skipped if video
         *  recording wasn't launched before.
         *
         * @async
         * @memberOf VideoSteps
         * @method stopVideo
         * @instance
         * @arg {object} [opts] - Step options.
         * @arg {boolean} [opts.check=true] - Flag to check that video recording
         *  was stopped.
         * @return {Promise<string>} Path to recorded video.
         * @return {Promise<boolean>} `false` if step was skipped.
         * @throws {AssertionError} If video recording wasn't stopped.
         */

        if (!this._isVideoStarted) {
            LOG.warn("Step to start video recording wasn't passed yet");
            return false;
        };

        opts = U.defVal(opts, {});
        var check = U.defVal(opts.check, true);

        allure.step("Stop video recording");
        LOG.info("Stopping video recording...");

        await this.pause(1, "it needs a time to gather latest frames");
        await this.video.stop();

        if (check) {
            expect(this.video.isRunning,
                "Video recording was still running")
                .to.be.false;
        };

        this._isVideoStarted = false;

        LOG.info("Video recording is stopped");
        allure.pass();

        return this.video.filePath;
    },

    getVideo: function (opts) {
        /**
         * Step to get path to recorded video.
         *
         * @memberOf VideoSteps
         * @method getVideo
         * @instance
         * @arg {object} [opts] - Step options.
         * @arg {boolean} [opts.check=true] - Flag to check that video is recorded
         *  and path exists.
         * @return {string} Path to recorded video.
         */

        allure.step("Get video file");

        expect(this.video,
            "Video recorder isn't initialized").to.exist;

        opts = U.defVal(opts, {});
        var check = U.defVal(opts.check, true);

        LOG.info("Getting recorded video path...");

        if (check) {
            expect(this.video.isRunning,
                "Can't get recorded video file path, " +
                   "because video is still recording").to.be.false;
            expect(this.video.filePath,
                "Can't get recorded video file path, " +
                   "because it's empty").to.not.be.empty;
        };

        LOG.info("Recorded video path is got");
        allure.pass();

        return this.video.filePath;
    },

    removeVideo: function (opts) {
        /**
         * Step to remove recorded video.
         *
         * @memberOf VideoSteps
         * @method removeVideo
         * @instance
         * @arg {object} [opts] - Step options.
         * @arg {boolean} [opts.check=true] - Flag to check step result.
         * @return {boolean} `true` if step was executed, `false` if was skipped.
         * @throws {AssertionError} If video file wasn't removed.
         */

        if (!this.video.filePath || !this.__fs.existsSync(this.video.filePath)) {
            LOG.warn("Video file doesn't exist");
            return false;
        };

        opts = U.defVal(opts, {});
        var check = U.defVal(opts.check, true);

        allure.step("Remove recorded video");
        LOG.info("Removing recorded video file...");

        this.__fs.unlinkSync(this.video.filePath);

        if (check) {
            expect(this.__fs.existsSync(this.video.filePath),
                `Video file '${this.video.filePath}' wasn't removed`)
                .to.be.false;
        };

        LOG.info("Recorded video file is removed");
        allure.pass();

        return true;
    },
    /**
     * Helper to get video location.
     * 
     * @async
     * @method
     * @instance
     * @protected
     * @return {Promise<object>} Dict of location.
     */
    _videoLocation: async function () {
        var loc = {};
        var screen = await this.__resolution.get();
        var screenLoc = { x: 0, y: 0,
            width: screen.width,
            height: screen.height };

        if (this.webdriver && await this.webdriver.session()) {

            _.assign(loc,
                (await this.webdriver.windowHandlePosition()).value);
            _.assign(loc,
                (await this.webdriver.windowHandleSize()).value);

            expect(U.isInScreen(loc, screenLoc),
                "Browser is outside of screen").to.be.true;

            if (loc.x < 0) loc.x = 0;
            if (loc.y < 0) loc.y = 0;
            if (loc.x + loc.width > screen.width) {
                loc.width = screen.width - loc.x;
            };
            if (loc.y + loc.height > screen.height) {
                loc.height = screen.height - loc.y;
            };

        } else {
            loc = screenLoc;
        };

        expect(loc.width, "Invalid video width").to.be.above(0);
        expect(loc.height, "Invalid video height").to.be.above(0);

        return loc;
    },
    /**
     * Helper to make video path.
     *
     * @method
     * @instance
     * @protected
     * @arg {object} [opts] - Step options.
     * @arg {string} [opts.name] - File name. Extension `.avi` will be
     *  added automatically. Default name will be generated with `uuid`
     *  algorithm.
     * @arg {string} [opts.dir] - Folder to save video.
     * @return {string} Full video path.
     */
    _makeVideoPath: function (opts) {
        opts = U.defVal(opts, {});

        var fileName = U.toKebab(U.defVal(opts.name, uuid()));
        if (!fileName.endsWith(".avi")) fileName += ".avi";

        return U.mkpath(
            U.defVal(opts.dir,
                path.resolve(CONF.report.testDir || CONF.report.dir, "videos")),
            fileName);
    },
};

module.exports = VideoSteps;