Merge branch 'develop' of https://github.com/bigbluebutton/bigbluebutton into issue-8544

This commit is contained in:
KDSBrowne 2020-03-30 20:40:01 +00:00
commit 81d0d5b0dc
26 changed files with 356 additions and 128 deletions

View File

@ -26,6 +26,12 @@ A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**BBB version (optional):**
BigBlueButton continually evolves. Providing the version/build helps us to pinpoint when an issue was introduced.
Example:
$ sudo bbb-conf --check | grep BigBlueButton
BigBlueButton Server 2.2.2 (1816)
**Desktop (please complete the following information):**
- OS: [e.g. Windows, Mac]
- Browser [e.g. Chrome, Safari]

View File

@ -128,6 +128,7 @@ class ActionsDropdown extends PureComponent {
? (
<DropdownListItem
icon="polling"
data-test="polling"
label={formatMessage(pollBtnLabel)}
description={formatMessage(pollBtnDesc)}
key={this.pollId}

View File

@ -174,6 +174,7 @@ class Poll extends Component {
label={label}
color="default"
className={styles.pollBtn}
data-test="pollBtn"
key={_.uniqueId('quick-poll-')}
onClick={() => {
Session.set('pollInitiated', true);
@ -338,6 +339,7 @@ class Poll extends Component {
<header className={styles.header}>
<Button
ref={(node) => { this.hideBtn = node; }}
data-test="hidePollDesc"
tabIndex={0}
label={intl.formatMessage(intlMessages.pollPaneTitle)}
icon="left_arrow"

View File

@ -206,6 +206,7 @@ class LiveResult extends PureComponent {
stopPoll();
}}
label={intl.formatMessage(intlMessages.publishLabel)}
data-test="publishLabel"
color="primary"
className={styles.btn}
/>

View File

@ -683,7 +683,7 @@ class PresentationUploader extends Component {
disablepreview="true"
onDrop={this.handleFiledrop}
>
<Icon className={styles.dropzoneIcon} iconName="upload" />
<Icon className={styles.dropzoneIcon} data-test="fileUploadDropZone" iconName="upload" />
<p className={styles.dropzoneMessage}>
{intl.formatMessage(intlMessages.dropzoneLabel)}
&nbsp;

View File

@ -114,6 +114,7 @@
.presenter {
&:before {
content: "\00a0\e90b\00a0";
padding: var(--md-padding-y);
}
@include presenterIndicator();
}

View File

@ -159,7 +159,7 @@ class VideoListItem extends Component {
>
{
!videoIsReady
&& <div className={styles.connecting} />
&& <div data-test="webcamConnecting" className={styles.connecting} />
}
<div
className={styles.videoContainer}

View File

@ -1,2 +1,18 @@
# meeting credentials
BBB_SERVER_URL=""
BBB_SHARED_SECRET=""
BBB_SHARED_SECRET=""
# collecting metrics
BBB_COLLECT_METRICS= # (true/false): true to collect metrics
METRICS_FOLDER= # full path of your audio.wav file
# files paths for audio and webcams tests
AUDIO_FILE= # full path of your audio.wav file
VIDEO_FILE= # full path of your video.y4m file
# webcams test
LOOP_INTERVAL= # time to loop in the webcams test in milliseconds
CAMERA_SHARE_FAILED_WAIT_TIME=15000 # this is set by default in the BBB server
# audio test
IS_AUDIO_TEST= # (true/false): true if the test will require enabling audio

View File

@ -1,7 +1,7 @@
const util = require('./util');
const utilNotification = require('../notifications/util');
const Page = require('../core/page');
const params = require('../params');
const pe = require('../core/elements');
const util = require('./util');
class Audio {
constructor() {
@ -17,6 +17,11 @@ class Audio {
await this.page2.joinMicrophone();
}
async initOneUser(page,meetingId) {
await page.init(Page.getArgsWithAudio(), meetingId, {...params, fullName: 'User1'});
await page.joinMicrophone();
}
async test() {
// User1 is checking if User2 is talking
const isTalkingIndicatorUser1 = await util.checkUserIsTalkingIndicator(this.page1);
@ -43,6 +48,11 @@ class Audio {
return response;
}
async audioNotification(page) {
const resp = await utilNotification.getLastToastValue(page);
return resp;
}
async close() {
this.page1.close();
this.page2.close();

View File

@ -6,7 +6,6 @@ exports.echoYes = 'button[aria-label="Echo is audible"]';
exports.title = '._imports_ui_components_nav_bar__styles__presentationTitle';
exports.alerts = '.toastify-content';
exports.isTalking = '[data-test="isTalking"]';
exports.wasTalking = '[data-test="wasTalking"]';
exports.joinAudio = 'button[aria-label="Join Audio"]';

View File

@ -0,0 +1,3 @@
exports.sharedNotes = 'div[data-test="sharedNotes"]';
exports.hideNoteLabel = 'button[data-test="hideNoteLabel"]';
exports.etherpad = 'iframe[title="etherpad"]';

View File

@ -0,0 +1,19 @@
const Create = require('../breakout/create');
const util = require('./util');
class SharedNotes extends Create {
constructor() {
super('shared-notes');
}
async test() {
const response = await util.startSharedNotes(this.page1);
return response;
}
async close() {
await this.page1.close();
await this.page2.close();
}
}
module.exports = exports = SharedNotes;

View File

@ -0,0 +1,18 @@
const se = require('./elements');
async function startSharedNotes(test) {
await test.waitForSelector(se.sharedNotes);
await test.click(se.sharedNotes, true);
await test.waitForSelector(se.hideNoteLabel);
const resp = await test.page.evaluate(getTestElement, se.etherpad);
await test.waitForSelector(se.etherpad);
return resp;
}
async function getTestElement(element) {
const response = document.querySelectorAll(element).length >= 1;
return response;
}
exports.getTestElement = getTestElement;
exports.startSharedNotes = startSharedNotes;

View File

@ -1,6 +1,6 @@
const Page = require('./core/page');
const MultiUsers = require('./user/multiusers');
const Notifications = require('./notifications/notifications');
const ShareScreen = require('./screenshare/screenshare');
const Audio = require('./audio/audio');
describe('Notifications', () => {
test('Save settings notification', async () => {
@ -8,7 +8,7 @@ describe('Notifications', () => {
let response;
try {
await test.init();
response = await test.saveSttingsNotification();
response = await test.saveSettingsNotification();
} catch (e) {
console.log(e);
} finally {
@ -44,4 +44,79 @@ describe('Notifications', () => {
}
expect(response).toBe(true);
});
test('User join notification', async () => {
const test = new Notifications();
let response;
try {
await test.initUser3();
await test.userJoinNotification();
await test.initUser4();
response = await test.getUserJoinPopupResponse();
} catch (e) {
console.log(e);
} finally {
await test.closePages();
}
expect(response).toBe('User4 joined the session');
});
test('Presentation upload notification', async () => {
const test = new Notifications();
let response;
try {
await test.initUser3();
response = await test.fileUploaderNotification();
} catch (e) {
console.log(e);
} finally {
await test.closePage(test.page3);
}
expect(response).toContain('Current presentation');
});
test('Poll results notification', async () => {
const test = new Notifications();
let response;
try {
await test.initUser3();
response = await test.publishPollResults();
} catch (e) {
console.log(e);
} finally {
await test.closePage(test.page3);
}
expect(response).toContain('Poll results were published to Public Chat and Whiteboard');
});
test('Screenshare notification', async () => {
const test = new ShareScreen();
const page = new Notifications()
let response;
try {
await page.initUser3();
response = await test.toast(page.page3);
} catch (e) {
console.log(e);
} finally {
await page.closePage(page.page3);
}
expect(response).toBe('Screenshare has started');
});
test('Audio notifications', async () => {
const test = new Audio();
const page = new Notifications();
let response;
try {
process.env.IS_AUDIO_TEST = true;
await test.initOneUser(page.page3);
response = await test.audioNotification(page.page3);
} catch (e) {
console.log(e);
} finally {
await page.closePage(page.page3);
}
expect(response).toBe('You have joined the audio conference');
})
});

View File

@ -9,3 +9,13 @@ exports.publicChatToast = 'New Public Chat message';
exports.privateChatToast = 'New Private Chat message';
exports.userListNotifiedIcon = '[class^=btnWithNotificationDot]';
exports.hasUnreadMessages = 'button[data-test="hasUnreadMessages"]';
exports.modalConfirmButton = 'button[data-test="modalConfirmButton"]';
exports.userJoinPushAlerts = '[data-test="userJoinPushAlerts"]';
exports.uploadPresentation = '[data-test="uploadPresentation"]';
exports.dropdownContent = '[data-test="dropdownContent"]';
exports.fileUploadDropZone = '[data-test="fileUploadDropZone"]';
exports.polling = '[data-test="polling"]';
exports.hidePollDesc = '[data-test="hidePollDesc"]';
exports.pollBtn = '[data-test="pollBtn"]';
exports.publishLabel = '[data-test="publishLabel"]';

View File

@ -1,16 +1,17 @@
const Page = require('../core/page');
const util = require('./util');
const utilMultiUsers = require('../chat/util');
const MultiUsers = require('../user/multiusers');
const Page = require('../core/page');
const params = require('../params');
const util = require('./util');
const ne = require('./elements');
const e = require('../chat/elements');
const we = require('../whiteboard/elements');
class Notifications extends MultiUsers {
constructor() {
super('notifications');
this.page1 = new Page();
this.page2 = new Page();
this.page3 = new Page();
this.page4 = new Page();
}
async init(meetingId) {
@ -18,13 +19,23 @@ class Notifications extends MultiUsers {
await this.page2.init(Page.getArgs(), this.page1.meetingId, { ...params, fullName: 'User2' });
}
async saveSttingsNotification() {
async initUser3(meetingId) {
await this.page3.init(Page.getArgs(), meetingId, { ...params, fullName: 'User3' });
}
async initUser4() {
await this.page4.init(Page.getArgs(), this.page3.meetingId, { ...params, fullName: 'User4' });
}
// Save Settings toast notification
async saveSettingsNotification() {
await util.popupMenu(this.page1);
await util.saveSettings(this.page1);
const resp = await util.getLastToastValue(this.page1) === ne.savedSettingsToast;
return resp;
}
// Public chat toast notification
async publicChatNotification() {
await util.popupMenu(this.page1);
await util.enableChatPopup(this.page1);
@ -36,7 +47,7 @@ class Notifications extends MultiUsers {
return expectedToastValue === lastToast;
}
// Private chat toast notification
async privateChatNotification() {
await util.popupMenu(this.page1);
await util.enableChatPopup(this.page1);
@ -47,6 +58,44 @@ class Notifications extends MultiUsers {
const lastToast = await util.getOtherToastValue(this.page1);
return expectedToastValue === lastToast;
}
// User join toast notification
async userJoinNotification() {
await util.popupMenu(this.page3);
await util.enableUserJoinPopup(this.page3);
await util.saveSettings(this.page3);
}
async getUserJoinPopupResponse() {
await this.page3.waitForSelector(ne.smallToastMsg);
const response = await util.getOtherToastValue(this.page3);
return response;
}
// File upload notification
async fileUploaderNotification() {
await util.uploadFileMenu(this.page3);
await this.page3.waitForSelector(ne.fileUploadDropZone);
const inputUploadHandle = await this.page3.page.$('input[type=file]');
await inputUploadHandle.uploadFile(process.env.PDF_FILE);
await this.page3.page.evaluate(util.clickTestElement, ne.modalConfirmButton);
const resp = await util.getLastToastValue(this.page3);
await this.page3.waitForSelector(we.whiteboard);
return resp;
}
async publishPollResults() {
await this.page3.waitForSelector(we.whiteboard);
await util.startPoll(this.page3);
await this.page3.waitForSelector(ne.smallToastMsg);
const resp = await util.getLastToastValue(this.page3);
return resp;
}
async closePages() {
await this.page3.close();
await this.page4.close();
}
}
module.exports = exports = Notifications;

View File

@ -1,13 +1,15 @@
const e = require('../core/elements');
const ne = require('../notifications/elements');
const ce = require('../chat/elements');
const ule = require('../user/elements');
const ce = require('../chat/elements');
const e = require('../core/elements');
async function popupMenu(page1) {
await page1.waitForSelector(e.options);
await page1.click(e.options, true);
await page1.waitForSelector(ne.settings);
await page1.click(ne.settings, true);
async function clickTestElement(element) {
await document.querySelectorAll(element)[0].click();
}
async function popupMenu(page) {
await page.page.evaluate(clickTestElement, e.options);
await page.page.evaluate(clickTestElement, ne.settings);
}
async function enableChatPopup(test) {
@ -15,9 +17,14 @@ async function enableChatPopup(test) {
await test.page.evaluate(() => document.querySelector('[data-test="chatPushAlerts"]').children[0].click());
}
async function saveSettings(page1) {
await page1.waitForSelector(ne.saveSettings);
await page1.click(ne.saveSettings, true);
async function enableUserJoinPopup(test) {
await test.waitForSelector(ne.userJoinPushAlerts);
await test.page.evaluate(() => document.querySelector('[data-test="userJoinPushAlerts"]').children[0].click());
}
async function saveSettings(page) {
await page.waitForSelector(ne.saveSettings);
await page.click(ne.saveSettings, true);
}
async function waitForToast(test) {
@ -28,8 +35,8 @@ async function waitForToast(test) {
async function getLastToastValue(test) {
await test.waitForSelector(ne.smallToastMsg);
const toast = test.page.evaluate(async () => {
const lastToast = await document.querySelectorAll('[data-test="toastSmallMsg"]')[0].innerText;
const toast = test.page.evaluate(() => {
const lastToast = document.querySelectorAll('[data-test="toastSmallMsg"]')[0].innerText;
return lastToast;
});
return toast;
@ -37,9 +44,9 @@ async function getLastToastValue(test) {
async function getOtherToastValue(test) {
await test.waitForSelector(ne.smallToastMsg);
const toast = test.page.evaluate(async () => {
const lastToast = await document.querySelectorAll('[data-test="toastSmallMsg"]')[1];
return lastToast.innerText;
const toast = test.page.evaluate(() => {
const lastToast = document.querySelectorAll('[data-test="toastSmallMsg"]')[1].innerText;
return lastToast;
});
return toast;
}
@ -48,8 +55,7 @@ async function getTestElement(element) {
await document.querySelectorAll(element)[1];
}
async function clickOnTheOtherUser(element) {
async function clickOnElement(element) {
await document.querySelectorAll(element)[0].click();
}
@ -59,7 +65,7 @@ async function clickThePrivateChatButton(element) {
async function publicChatMessageToast(page1, page2) {
// Open private Chat with the other User
await page1.page.evaluate(clickOnTheOtherUser, ule.userListItem);
await page1.page.evaluate(clickOnElement, ule.userListItem);
await page1.page.evaluate(clickThePrivateChatButton, ce.activeChat);
// send a public message
await page2.page.type(ce.publicChat, ce.publicMessage1);
@ -69,7 +75,7 @@ async function publicChatMessageToast(page1, page2) {
async function privateChatMessageToast(page2) {
// Open private Chat with the other User
await page2.page.evaluate(clickOnTheOtherUser, ule.userListItem);
await page2.page.evaluate(clickOnElement, ule.userListItem);
await page2.page.evaluate(clickThePrivateChatButton, ce.activeChat);
// send a private message
await page2.page.type(ce.privateChat, ce.message1);
@ -77,12 +83,43 @@ async function privateChatMessageToast(page2) {
return ne.privateChatToast;
}
// File upload notification
async function uploadFileMenu(test) {
await test.page.evaluate(clickOnElement, ne.dropdownContent);
await test.page.evaluate(clickOnElement, ne.uploadPresentation);
}
async function getFileItemStatus(element, value) {
document.querySelectorAll(element)[1].innerText.includes(value);
}
async function clickRandomPollOption(element) {
document.querySelector(element).click();
}
async function startPoll(test) {
await test.page.evaluate(clickOnElement, ne.dropdownContent);
await test.page.evaluate(clickOnElement, ne.polling);
await test.waitForSelector(ne.hidePollDesc);
await test.waitForSelector(ne.pollBtn);
await test.page.evaluate(clickRandomPollOption, ne.pollBtn);
await test.waitForSelector(ne.publishLabel);
await test.page.evaluate(clickOnElement, ne.publishLabel);
await test.waitForSelector(ne.smallToastMsg);
}
exports.getFileItemStatus = getFileItemStatus;
exports.privateChatMessageToast = privateChatMessageToast;
exports.publicChatMessageToast = publicChatMessageToast;
exports.enableUserJoinPopup = enableUserJoinPopup;
exports.getOtherToastValue = getOtherToastValue;
exports.getLastToastValue = getLastToastValue;
exports.enableChatPopup = enableChatPopup;
exports.uploadFileMenu = uploadFileMenu;
exports.getTestElement = getTestElement;
exports.saveSettings = saveSettings;
exports.waitForToast = waitForToast;
exports.popupMenu = popupMenu;
exports.clickTestElement = clickTestElement;
exports.startPoll = startPoll;
exports.clickOnElement = clickOnElement;

View File

@ -1,4 +1,5 @@
const Page = require('../core/page');
const utilNotifications = require('../notifications/util');
const util = require('./util');
const e = require('../core/elements');
@ -9,13 +10,17 @@ class ShareScreen extends Page {
async test() {
await util.startScreenshare(this.page);
this.page.on('dialog', async (dialog) => {
await dialog.accept();
});
await this.page.waitForSelector(e.screenShareVideo);
const response = await util.getScreenShareContainer(this.page);
return response;
}
async toast(page) {
await util.startScreenshare(page);
const response = await utilNotifications.getLastToastValue(page);
return response;
}
}
module.exports = exports = ShareScreen;

View File

@ -0,0 +1,17 @@
const SharedNotes = require('./notes/sharednotes');
describe('Shared notes', () => {
test('Open Shared notes', async () => {
const test = new SharedNotes();
let response;
try {
await test.init();
response = await test.test();
} catch (e) {
console.log(e);
} finally {
await test.close();
}
expect(response).toBe(true);
});
});

View File

@ -50,6 +50,10 @@ class MultiUsers {
await this.page1.close();
await this.page2.close();
}
async closePage(page) {
await page.close();
}
}
module.exports = exports = MultiUsers;

View File

@ -1,13 +1,13 @@
const Share = require('./webcam/share');
const Check = require('./webcam/check');
const LoadingTime = require('./webcam/loadtime');
const Page = require('./core/page');
describe('Webcam', () => {
test('Shares webcam', async () => {
const test = new Share();
let response;
try {
await test.init();
await test.init(Page.getArgsWithVideo());
response = await test.test();
} catch (e) {
console.log(e);
@ -17,25 +17,11 @@ describe('Webcam', () => {
expect(response).toBe(true);
});
test('Check Webcam loading time', async () => {
const test = new LoadingTime();
let response;
try {
await test.init();
response = await test.test();
} catch (e) {
console.log(e);
} finally {
await test.close();
}
expect(response).toBeLessThan(parseInt(process.env.CAMERA_SHARE_FAILED_WAIT_TIME));
});
test('Checks content of webcam', async () => {
const test = new Check();
let response;
try {
await test.init();
await test.init(Page.getArgsWithVideo());
response = await test.test();
} catch (e) {
console.log(e);

View File

@ -7,11 +7,9 @@ class Check extends Share {
}
async test() {
await util.startAndCheckForWebcams(this.page1);
await util.startAndCheckForWebcams(this.page2);
const responseUser1 = await util.webcamContentCheck(this.page1);
const responseUser2 = await util.webcamContentCheck(this.page2);
return responseUser1 && responseUser2;
await util.enableWebcam(this.page);
const respUser = await util.webcamContentCheck(this.page);
return respUser === true;
}
}
module.exports = exports = Check;

View File

@ -2,5 +2,4 @@ exports.joinVideo = 'button[data-test="joinVideo"]';
exports.videoPreview = 'video[data-test="videoPreview"]';
exports.startSharingWebcam = 'button[data-test="startSharingWebcam"]';
exports.videoContainer = 'video[data-test="videoContainer"]';
exports.webcamConnectingStatus = '[data-test="webcamConnectingStatus"]';
exports.presentationFullscreenButton = '[data-test="presentationFullscreenButton"]';
exports.webcamConnecting = '[data-test="webcamConnecting"]';

View File

@ -1,19 +0,0 @@
const Share = require('./share');
const util = require('./util');
class LoadingTime extends Share {
constructor() {
super('check-webcam-loading-time');
}
async test() {
await util.enableWebcam(this.page1);
await util.enableWebcam(this.page2);
const now = new Date().getMilliseconds();
await util.waitForWebcamsLoading(this.page1);
await util.waitForWebcamsLoading(this.page2);
const end = new Date().getMilliseconds();
return end - now;
}
}
module.exports = exports = LoadingTime;

View File

@ -1,29 +1,15 @@
const Page = require('../core/page');
const util = require('./util');
const params = require('../params');
class Share {
class Share extends Page{
constructor() {
this.page1 = new Page();
this.page2 = new Page();
}
async init(meetingId) {
await this.page1.init(Page.getArgsWithVideo(), meetingId, { ...params, fullName: 'Streamer1' });
await this.page2.init(Page.getArgsWithVideo(), this.page1.meetingId, { ...params, fullName: 'Streamer2' });
super('webcam-test');
}
async test() {
await util.enableWebcam(this.page1);
await util.enableWebcam(this.page2);
const responseUser1 = await util.evaluateCheck(this.page1);
const responseUser2 = await util.evaluateCheck(this.page2);
return responseUser1 && responseUser2;
}
async close() {
await this.page1.close();
await this.page2.close();
await util.enableWebcam(this.page);
const response = await util.evaluateCheck(this.page);
return response;
}
}

View File

@ -3,10 +3,10 @@ const we = require('./elements');
async function enableWebcam(test) {
// Enabling webcam
await test.waitForSelector(we.joinVideo);
await test.click(we.joinVideo, true);
await test.click(we.joinVideo);
await test.waitForSelector(we.videoPreview);
await test.waitForSelector(we.startSharingWebcam);
await test.click(we.startSharingWebcam, true);
await test.click(we.startSharingWebcam);
}
async function getTestElement(element) {
@ -15,8 +15,7 @@ async function getTestElement(element) {
async function evaluateCheck(test) {
await test.waitForSelector(we.videoContainer);
await test.waitForSelector(we.webcamConnectingStatus);
const videoContainer = await test.page.evaluate(getTestElement, we.presentationFullscreenButton);
const videoContainer = await test.evaluate(getTestElement, we.presentationFullscreenButton);
const response = videoContainer !== null;
return response;
}
@ -27,35 +26,41 @@ async function startAndCheckForWebcams(test) {
return response;
}
async function checkCameras() {
const videos = document.querySelectorAll('video');
const lastVideoColor = document.lastVideoColor || {};
document.lastVideoColor = lastVideoColor;
for (let v = 0; v < videos.length; v++) {
const video = videos[v];
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
const pixel = context.getImageData(50, 50, 1, 1).data;
const pixelString = new Array(pixel).join(' ').toString();
if (lastVideoColor[v]) {
if (lastVideoColor[v] == pixelString) {
return false;
}
}
return lastVideoColor[v] !== pixelString === true;
}
}
async function webcamContentCheck(test) {
await test.waitForSelector(we.videoContainer);
await test.waitForFunction(() => !document.querySelector('[data-test="webcamConnecting"]'));
const repeats = 5;
let check;
for (let i = repeats; i >= 0; i--) {
await test.page.waitFor(parseInt(process.env.LOOP_INTERVAL));
return check = await test.page.evaluate(checkCameras);
for (let i = repeats; i >= 1; i--) {
console.log(`loop ${i}`);
const checkCameras = function (i) {
const videos = document.querySelectorAll('video');
const lastVideoColor = document.lastVideoColor || {};
document.lastVideoColor = lastVideoColor;
for (let v = 0; v < videos.length; v++) {
const video = videos[v];
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
const pixel = context.getImageData(50, 50, 1, 1).data;
const pixelString = new Array(pixel).join(' ').toString();
if (lastVideoColor[v]) {
if (lastVideoColor[v] == pixelString) {
return false;
}
}
lastVideoColor[v] = pixelString;
return true;
}
};
check = await test.evaluate(checkCameras, i);
await test.waitFor(parseInt(process.env.LOOP_INTERVAL));
}
return check === true;
}
exports.startAndCheckForWebcams = startAndCheckForWebcams;