Home Manual Reference Source

src/controllers/web/call.controller.js

/* eslint-disable max-lines */
import * as WebPageObjects from "../../page_objects/web/webPageObjects.js";
import BaseController from "./base.controller.js";
import { isImageAboveBlackThreshold } from "../../utils/pngUtils.js";

/**
 * Controller for making and handling calls
 * @extends BaseController
 */
class CallController extends BaseController {
  /* eslint-disable max-statements */
  /**
   * @param {args} args Args from client
   */
  constructor(args) {
    super(args);
    this.headerPanel = new WebPageObjects.HeaderPanel(args);
    this.homePage = new WebPageObjects.HomePage(args);
    this.startCallModal = new WebPageObjects.StartCallModal(args);
    this.startingCallModal = new WebPageObjects.StartingCallModal(args);
    this.inCallModal = new WebPageObjects.InCallModal(args);
    this.incomingCallPage = new WebPageObjects.IncomingCallPage(args);
    this.joinModal = new WebPageObjects.JoinModal(args);
    this.callDetailsPanel = new WebPageObjects.CallDetailsPanel(args);
    this.participantListPanel = new WebPageObjects.ParticipantListPanel(args);
    this.addParticipantPanel = new WebPageObjects.AddParticipantPanel(args);
  }

  /**
   * When closing browser or navigating away, we are presented with an alert.
   * This run js on the page that tells the app to skip this alert.
   * Hard coded to 300,000ms (5 minutes)
   * Copy/Pasted from login controller
   * @todo Abstract this out into something else
   */
  async setSkipAlertOnClose() {
    // checking for lsConfig so we can skip the alert upon closing browser
    await this.browser.waitUntil(
      async () => await this.browser.execute("!!window.lsConfig"),
      10000,
      this.testName + " - lsConfig is not found"
    );

    // window.desktop = true
    await this.browser.execute(
      "window.lsConfig.skipTimeOnBeforeUnload(900000)"
    );
  }

  /**
   * Join call by visiting the /call URL
   * @param {String} extension Extension to call
   * @param {Boolean} wait If true, will wait for call to be connected.
   */
  async joinCall(extension, wait = true) {
    const url = `https://call.lifesizecloudbeta.com/call/${extension}`;
    await this.browser.url(url);
    await this.joinModal.nextButton().waitForVisible(60000);
    await this.setSkipAlertOnClose();
    await this.joinModal.nextButton().click();
    await this.joinModal.useComputerAudioButton().click();
    await this.joinModal.joinButton().click();
    wait ? await this.waitUntilCallConnected() : null;
    await this.browser.pause(5000);
  }

  /**
   * Join call by visiting the /call URL, and cancel call
   * @param {String} extension Extension to call
   */
  async joinCallAndCancel(extension) {
    this.joinCall(extension, false);
    await this.startingCallModal.cancelCall();
  }

  /**
   * End the current call
   */
  async endCall() {
    await this.inCallModal.endCall();
    await this.headerPanel.waitForHeader();
    this.browser.pause(5000);
  }

  /**
   * Determines whether the callee is 'Unreachable' when the caller calls
   * @returns {Boolean} Returns true if call error message is Unreachable
   */
  async calleeIsUnreachable() {
    await this.startCallModal.callErrorMessage().waitForVisible(10000);
    const status = await this.startCallModal.callStatusMessage().getText();
    return status === "Unreachable";
  }

  /**
   * Determines whether the callee is 'Busy' when the caller calls
   * @returns {Boolean} Returns true if call error message is Busy
   */
  async calleeIsBusy() {
    await this.startCallModal.callErrorMessage().waitForVisible(5000);
    const status = await this.startCallModal.callStatusMessage().getText();
    return status === "Busy";
  }

  /**
   * Starts a call from the Welcome page by entering an extension
   * @param {String} extension Extension of contact to cal
   * @param {Boolean} wait Wait for call to be connected before continuing
   */
  async callExtensionWithVideo(extension, wait = true) {
    await this.homePage.startCall();
    await this.startCallModal.enterExtension(extension);
    await this.startCallModal.startVideoCall();
    wait ? await this.waitUntilCallConnected() : null;
  }

  /**
   * Will call extension and immediately cancel the call
   * @param {String} extension Extension to call
   */
  async callExtensionWithVideoAndCancel(extension) {
    await this.callExtensionWithVideo(extension, false);
    await this.startingCallModal.cancelCall();
    await this.browser.pause(3000);
  }

  /**
   * Answer an incoming call
   * @param {Boolean} wait Wait for call to be connected before continuing
   */
  async answerCall(wait = true) {
    await this.incomingCallPage.answerCall();
    wait ? await this.waitUntilCallConnected() : null;
  }

  /**
   * Waits for incoming call, then will click ignore
   */
  async ignoreCall() {
    await this.incomingCallPage.ignoreCall();
    await this.incomingCallPage.ignoreButton().waitForNotVisible(10000);
    await this.browser.pause(3000);
  }

  /**
   * Waits for incoming call while in a call, then will click add
   */
  async addCall() {
    await this.incomingCallPage.addCall();
    await this.browser.pause(3000);
  }

  /**
   * Waits for incoming call modal to be displayed
   */
  async waitForIncomingCall() {
    await this.incomingCallPage.waitForModalToBeDisplayed();
  }

  /**
   * Determines if the call is considered good
   * @returns {Boolean} true if call is considered good, false if not
   */
  async isCallGood() {
    // * Check whether the call got connected on all clients
    // * Check for primary video displayed
    // * Check RTCPeerConnection is established on all clients
    // * Check call duration gets updated on all clients
    // * Check for blank video on all clients
    // * Check for basic call stats on all clients
    // let isBlank = await this.isPrimaryVideoBlank()
    const isBlank = false;
    const hasGoodRtcStats = await this.hasGoodRtcStats();
    return !isBlank && hasGoodRtcStats;
  }

  /**
   * Determines if RTC stats are good
   * @returns {Boolean} True if stats are considered good, false if not
   * @todo Determine what good stats are
   */
  async hasGoodRtcStats() {
    await this.analyzeRtc(10000);
    return true; // change this eventually
  }

  /**
   * Determines if the primary video is blank. This takes a screen shot of
   * the primary video element at the time this is called.
   * @returns {Boolean} True if primary video is considered blank, false if not.
   */
  async isPrimaryVideoBlank() {
    await this.browser.pause(5000);
    try {
      await this.browser.saveElementScreenshot(
        "./temp_shots/test.png",
        this.inCallModal.primaryVideo().selector
      );
      // if over 95% black, returns true
      return isImageAboveBlackThreshold("./temp_shots/test.png", 95);
    } catch (error) {
      console.log("SCREENSHOT ERROR", error);
      throw new Error(
        `[${this.testName}]:` +
          `Unable to take screenshot of element. Cannot determine if blank.`
      );
    }
  }

  /**
   * Checks to see if we have a peer connection.
   * @returns {Boolean} True if connected, false if not.
   */
  async isConnected() {
    const connection = await this.browser.execute(() =>
      // eslint-disable-next-line
      window.getNucleusPeerConnection()
    );
    return connection.value === null ? false : true;
  }

  /**
   * Waits up to 30 seconds to become connected to a call
   */
  async waitUntilCallConnected() {
    await this.inCallModal.loadingHeader().waitForNotVisible();
    await this.browser.waitUntil(
      () => this.isConnected(),
      30000,
      `[${this.testName}]: Call not connected`
    );
  }

  /**
   * Starts analyzing RTC data during a call for client for ms provided.
   * @param {String} ms Amount of milliseconds to analyze call
   * @return {Object} Blob of rtc data
   * @todo Determine what RTC data is relevant to present here.
   */
  async analyzeRtc(ms) {
    try {
      await this.browser.startAnalyzing(
        () => window.getNucleusPeerConnection() //eslint-disable-line
      );
    } catch (error) {
      if (error.message !== "analyzing already started") {
        throw error;
      }
    }

    const connectionType = await this.browser.getConnectionInformation();
    await this.browser.pause(ms);
    const rtcData = await this.browser.getStats(ms);
    return { connectionType: connectionType, rtcData: rtcData };
  }

  /**
   * Clicks the start presentation button. Auto-selects "Entire Screen"
   * based on our capabilities
   */
  async startPresentation() {
    await this.inCallModal.startPresentationButton().click();
    await this.browser.pause(1000);
    await this.browser.waitUntil(
      async () => await this.isSendingPresentation(),
      60000
    );
    await this.browser.pause(1000);
  }

  /**
   * Click the stop presentation button
   */
  async stopPresentation() {
    await this.inCallModal.stopPresentationButton().click();
  }

  /**
   * Checks if sending presentation
   * @return {Boolean} true if sending, false if not
   */
  async isSendingPresentation() {
    return await this.inCallModal.isSendingPresentation();
  }

  /**
   * Checks if receiving presentation
   * @return {Boolean} true if receiving, false if not
   */
  async isReceivingPresentation() {
    return await this.inCallModal.isReceivingPresentation();
  }

  /**
   * Get text of presentation button tooltip
   * @return {String} Text of presentation tooltip
   */
  async getPresentationTooltipText() {
    return await this.inCallModal.getPresentationTooltipText();
  }

  /**
   * Get details from call details panel
   * @returns {Object} Object of call details
   */
  async getCallDetails() {
    await this.inCallModal.viewCallDetails();
    const details = await this.callDetailsPanel.parseCallDetails();
    await this.inCallModal.closeRightHandPanel();
    return details;
  }

  /**
   * Views incall participants list and returns the info
   * @return {Object} participants info
   */
  async getCallParticipantsInfo() {
    await this.inCallModal.viewParticipantsList();
    const info = await this.participantListPanel.getParticipantsInfo();
    await this.inCallModal.closeRightHandPanel();
    return info;
  }

  /**
   * Views add participant panel and dials to a participant by extension
   * @param  {String}  extension extension of the participant
   */
  async addParticipantByExtension(extension) {
    await this.inCallModal.viewAddParticipantPanel();
    await this.addParticipantPanel.dialByExtension(extension);
  }

  /**
   * Clicks on start recording button from call controls
   * @param  {Boolean} [wait=true] If true waits for the recording to be started
   */
  async startRecording(wait = true) {
    await this.inCallModal.startRecordingButton().click();
    if (wait) {
      await this.browser.waitUntil(
        async () => await this.isCallRecorded(),
        50000
      );
    }
  }

  /**
   * Clicks on stop recording button from call controle
   * @param  {Boolean} [wait=true] If true waits until recording stops
   */
  async stopRecording(wait = true) {
    await this.incallModal.stopRecordingButton().click();
    if (wait) {
      await this.browser.waitUntil(
        async () => !await this.isCallRecorded(),
        50000
      );
    }
  }

  /**
   * Returns the call recording state
   * @return {Boolean} call recording state
   */
  async isCallRecorded() {
    return await this.incallModal.isCallRecorded();
  }

  /**
   * Returns recording button tool tip text
   * @return {String} Recording button tool tip text
   */
  async getRecordingTooltipText() {
    return await this.InCallModal.getRecordingTooltipText();
  }
}

export default CallController;