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;