Merge remote-tracking branch 'upstream/develop' into use-context-selector

This commit is contained in:
Ramon Souza 2021-09-13 15:41:52 -03:00
commit 9300b9b382
76 changed files with 14210 additions and 577 deletions

View File

@ -4,6 +4,7 @@ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.models.{ Users2x, Roles }
trait RequestBreakoutJoinURLReqMsgHdlr extends RightsManagementTrait {
this: MeetingActor =>
@ -19,7 +20,9 @@ trait RequestBreakoutJoinURLReqMsgHdlr extends RightsManagementTrait {
for {
model <- state.breakout
room <- model.find(msg.body.breakoutId)
requesterUser <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield {
if (requesterUser.role == Roles.MODERATOR_ROLE || room.freeJoin) {
BreakoutHdlrHelpers.sendJoinURL(
liveMeeting,
outGW,
@ -28,6 +31,11 @@ trait RequestBreakoutJoinURLReqMsgHdlr extends RightsManagementTrait {
room.sequence.toString(),
room.id
)
} else {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to request breakout room URL for meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
}
}
}

View File

@ -45,7 +45,6 @@ class App extends React.Component {
.then((response) => response.json())
.then((json) => {
this.setState({ activitiesJson: json });
document.title = `Learning Dashboard - ${json.name}`;
});
}
}
@ -54,6 +53,8 @@ class App extends React.Component {
const { activitiesJson, tab } = this.state;
const { intl } = this.props;
document.title = `${intl.formatMessage({ id: 'app.learningDashboard.dashboardTitle', defaultMessage: 'Learning Dashboard' })} - ${activitiesJson.name}`;
function totalOfRaiseHand() {
if (activitiesJson && activitiesJson.users) {
return Object.values(activitiesJson.users)

View File

@ -1 +1 @@
BIGBLUEBUTTON_RELEASE=2.4-beta-4
BIGBLUEBUTTON_RELEASE=2.4-rc-1

View File

@ -74,6 +74,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
[hidden]:not([hidden="false"]) {
display: none !important;
}
textarea::-webkit-input-placeholder,
input::-webkit-input-placeholder {
color: var(--palette-placeholder-text);
opacity: 1;
}
</style>
<script>
document.addEventListener('gesturestart', function (e) {

View File

@ -253,7 +253,6 @@ class BreakoutRoom extends PureComponent {
roomList.removeEventListener('keydown', this.handleMoveEvent, true);
}
}
this.handleDismiss();
}
handleShiftUser(activeListSibling) {
@ -373,7 +372,7 @@ class BreakoutRoom extends PureComponent {
return;
}
this.setState({ preventClosing: false });
this.handleDismiss();
const rooms = _.range(1, numberOfRooms + 1).map((seq) => ({
users: this.getUserByRoom(seq).map((u) => u.userId),
@ -403,7 +402,7 @@ class BreakoutRoom extends PureComponent {
breakoutUsers.forEach((user) => sendInvitation(breakoutId, user.userId));
});
this.setState({ preventClosing: false });
this.handleDismiss();
}
onAssignRandomly() {

View File

@ -47,6 +47,7 @@ import ConnectionStatusService from '/imports/ui/components/connection-status/se
import { NAVBAR_HEIGHT, LARGE_NAVBAR_HEIGHT } from '/imports/ui/components/layout/defaultValues';
import Settings from '/imports/ui/services/settings';
import LayoutService from '/imports/ui/components/layout/service';
import { registerTitleView } from '/imports/utils/dom-utils';
const MOBILE_MEDIA = 'only screen and (max-width: 40em)';
const APP_CONFIG = Meteor.settings.public.app;
@ -99,6 +100,10 @@ const intlMessages = defineMessages({
id: 'app.whiteboard.annotations.poll',
description: 'message displayed when a poll is published',
},
defaultViewLabel: {
id: 'app.title.defaultViewLabel',
description: 'view name apended to document title',
},
});
const propTypes = {
@ -153,12 +158,14 @@ class App extends Component {
const { browserName } = browserInfo;
const { osName } = deviceInfo;
registerTitleView(intl.formatMessage(intlMessages.defaultViewLabel));
layoutContextDispatch({
type: ACTIONS.SET_IS_RTL,
value: isRTL,
});
MediaService.setSwapLayout();
MediaService.setSwapLayout(layoutContextDispatch);
Modal.setAppElement('#app');
const fontSize = isMobile() ? MOBILE_FONT_SIZE : DESKTOP_FONT_SIZE;

View File

@ -62,6 +62,7 @@ const AppContainer = (props) => {
settingsLayout,
pushLayoutToEveryone,
currentUserId,
shouldShowPresentation: propsShouldShowPresentation,
...otherProps
} = props;
@ -69,15 +70,15 @@ const AppContainer = (props) => {
const sidebarNavigation = layoutSelectInput((i) => i.sidebarNavigation);
const actionsBarStyle = layoutSelectOutput((i) => i.actionBar);
const captionsStyle = layoutSelectOutput((i) => i.captions);
const presentation = layoutSelectInput((i) => i.presentation);
const layoutType = layoutSelect((i) => i.layoutType);
const deviceType = layoutSelect((i) => i.deviceType);
const layoutContextDispatch = layoutDispatch();
const { sidebarContentPanel } = sidebarContent;
const { sidebarNavPanel } = sidebarNavigation;
const sidebarNavigationIsOpen = sidebarNavigation.isOpen;
const sidebarContentIsOpen = sidebarContent.isOpen;
const { sidebarContentPanel, isOpen: sidebarContentIsOpen } = sidebarContent;
const { sidebarNavPanel, isOpen: sidebarNavigationIsOpen } = sidebarNavigation;
const { isOpen: presentationIsOpen } = presentation;
const shouldShowPresentation = propsShouldShowPresentation && presentationIsOpen;
return currentUserId
? (
@ -97,6 +98,7 @@ const AppContainer = (props) => {
sidebarNavigationIsOpen,
sidebarContentPanel,
sidebarContentIsOpen,
shouldShowPresentation,
}}
{...otherProps}
/>

View File

@ -79,6 +79,7 @@ class AudioControls extends PureComponent {
hideLabel
aria-label={intl.formatMessage(intlMessages.joinAudio)}
label={intl.formatMessage(intlMessages.joinAudio)}
data-test="joinAudio"
color="default"
ghost
icon="audio_off"

View File

@ -290,6 +290,7 @@ class InputStreamLiveSelector extends Component {
aria-label={intl.formatMessage(intlMessages.leaveAudio)}
label={intl.formatMessage(intlMessages.leaveAudio)}
accessKey={shortcuts.leaveaudio}
data-test="leaveAudio"
hideLabel
color="primary"
icon={isListenOnly ? 'listen' : 'audio_on'}

View File

@ -187,7 +187,7 @@ class AudioModal extends Component {
if (autoplayBlocked !== prevProps.autoplayBlocked) {
if (autoplayBlocked) {
this.setContent('autoplayBlocked');
this.setContent({ content: 'autoplayBlocked' });
} else {
closeModal();
}

View File

@ -5,6 +5,8 @@ import _ from 'lodash';
import BBBMenu from "/imports/ui/components/menu/component";
import Button from '/imports/ui/components/button/component';
import { alertScreenReader } from '/imports/utils/dom-utils';
import ChatService from '../service';
const intlMessages = defineMessages({
@ -20,6 +22,14 @@ const intlMessages = defineMessages({
id: 'app.chat.dropdown.copy',
description: 'Copy button label',
},
copySuccess: {
id: 'app.chat.copySuccess',
description: 'aria success alert',
},
copyErr: {
id: 'app.chat.copyErr',
description: 'aria error alert',
},
options: {
id: 'app.chat.dropdown.options',
description: 'Chat Options',
@ -92,7 +102,11 @@ class ChatDropdown extends PureComponent {
label: intl.formatMessage(intlMessages.copy),
onClick: () => {
let chatHistory = ChatService.exportChat(timeWindowsValues, users, intl);
navigator.clipboard.writeText(chatHistory);
navigator.clipboard.writeText(chatHistory).then(() => {
alertScreenReader(intl.formatMessage(intlMessages.copySuccess));
}).catch(() => {
alertScreenReader(intl.formatMessage(intlMessages.copyErr));
});
}
}
)
@ -121,6 +135,7 @@ class ChatDropdown extends PureComponent {
if (!amIModerator && !ENABLE_SAVE_AND_COPY_PUBLIC_CHAT) return null;
return (
<>
<BBBMenu
trigger={
<Button
@ -148,6 +163,7 @@ class ChatDropdown extends PureComponent {
}}
actions={this.getAvailableActions()}
/>
</>
);
}
}

View File

@ -125,7 +125,7 @@
flex-shrink: 0;
flex-grow: 0;
flex-basis: 3.5rem;
color: var(--color-gray-light);
color: var(--palette-placeholder-text);
text-transform: uppercase;
font-size: 75%;
margin: 0 0 0 calc(var(--line-height-computed) / 2);

View File

@ -321,7 +321,7 @@ class ConnectionStatusComponent extends PureComponent {
{conn.offline ? ` (${intl.formatMessage(intlMessages.offline)})` : null}
</div>
</div>
<div className={styles.status}>
<div aria-label={`${intl.formatMessage(intlMessages.title)} ${conn.level}`} className={styles.status}>
<div className={styles.icon}>
<Icon level={conn.level} />
</div>

View File

@ -153,6 +153,7 @@ const CustomLayout = () => {
isOpen: false,
},
presentation: {
isOpen: presentationInput.isOpen,
slidesLength: presentationInput.slidesLength,
currentSlide: {
...presentationInput.currentSlide,
@ -180,6 +181,7 @@ const CustomLayout = () => {
isOpen: false,
},
presentation: {
isOpen: presentationInput.isOpen,
slidesLength: presentationInput.slidesLength,
currentSlide: {
...presentationInput.currentSlide,
@ -434,7 +436,7 @@ const CustomLayout = () => {
);
}
cameraDockBounds.top = DEFAULT_VALUES.navBarHeight;
cameraDockBounds.top = DEFAULT_VALUES.navBarHeight + bannerAreaHeight();
cameraDockBounds.left = cameraDockLeft;
cameraDockBounds.right = isRTL ? sidebarSize : null;
cameraDockBounds.minWidth = mediaAreaBounds.width;
@ -462,7 +464,7 @@ const CustomLayout = () => {
);
}
cameraDockBounds.top = DEFAULT_VALUES.navBarHeight;
cameraDockBounds.top = DEFAULT_VALUES.navBarHeight + bannerAreaHeight();
const sizeValue = presentationInput.isOpen
? (mediaAreaBounds.left + mediaAreaBounds.width) - cameraDockWidth
: mediaAreaBounds.left;
@ -499,7 +501,7 @@ const CustomLayout = () => {
}
cameraDockBounds.top = DEFAULT_VALUES.navBarHeight
+ mediaAreaBounds.height - cameraDockHeight;
+ mediaAreaBounds.height - cameraDockHeight + bannerAreaHeight();
cameraDockBounds.left = cameraDockLeft;
cameraDockBounds.right = isRTL ? sidebarSize : null;
cameraDockBounds.minWidth = mediaAreaBounds.width;
@ -527,7 +529,7 @@ const CustomLayout = () => {
);
}
cameraDockBounds.top = DEFAULT_VALUES.navBarHeight;
cameraDockBounds.top = DEFAULT_VALUES.navBarHeight + bannerAreaHeight();
cameraDockBounds.left = mediaAreaBounds.left + camerasMargin;
cameraDockBounds.right = isRTL ? sidebarSize + (camerasMargin * 2) : null;
cameraDockBounds.minWidth = DEFAULT_VALUES.cameraDockMinWidth;
@ -599,7 +601,7 @@ const CustomLayout = () => {
const { isOpen } = presentationInput;
const { height: actionBarHeight } = calculatesActionbarHeight();
const mediaAreaHeight = windowHeight()
- (DEFAULT_VALUES.navBarHeight + actionBarHeight);
- (DEFAULT_VALUES.navBarHeight + actionBarHeight + bannerAreaHeight());
const mediaAreaWidth = windowWidth() - (sidebarNavWidth + sidebarContentWidth);
const mediaBounds = {};
const { element: fullscreenElement } = fullscreen;
@ -632,7 +634,7 @@ const CustomLayout = () => {
case CAMERADOCK_POSITION.CONTENT_TOP: {
mediaBounds.width = mediaAreaWidth;
mediaBounds.height = mediaAreaHeight - cameraDockBounds.height - camerasMargin;
mediaBounds.top = navBarHeight + cameraDockBounds.height + camerasMargin;
mediaBounds.top = navBarHeight + cameraDockBounds.height + camerasMargin + bannerAreaHeight();
mediaBounds.left = !isRTL ? sidebarSize : null;
mediaBounds.right = isRTL ? sidebarSize : null;
break;
@ -640,7 +642,7 @@ const CustomLayout = () => {
case CAMERADOCK_POSITION.CONTENT_RIGHT: {
mediaBounds.width = mediaAreaWidth - cameraDockBounds.width - camerasMargin;
mediaBounds.height = mediaAreaHeight;
mediaBounds.top = navBarHeight;
mediaBounds.top = navBarHeight + bannerAreaHeight();
mediaBounds.left = !isRTL ? sidebarSize : null;
mediaBounds.right = isRTL ? sidebarSize - (camerasMargin * 2) : null;
break;
@ -648,7 +650,7 @@ const CustomLayout = () => {
case CAMERADOCK_POSITION.CONTENT_BOTTOM: {
mediaBounds.width = mediaAreaWidth;
mediaBounds.height = mediaAreaHeight - cameraDockBounds.height - camerasMargin;
mediaBounds.top = navBarHeight - camerasMargin;
mediaBounds.top = navBarHeight - camerasMargin + bannerAreaHeight();
mediaBounds.left = !isRTL ? sidebarSize : null;
mediaBounds.right = isRTL ? sidebarSize : null;
break;
@ -656,7 +658,7 @@ const CustomLayout = () => {
case CAMERADOCK_POSITION.CONTENT_LEFT: {
mediaBounds.width = mediaAreaWidth - cameraDockBounds.width - camerasMargin;
mediaBounds.height = mediaAreaHeight;
mediaBounds.top = navBarHeight;
mediaBounds.top = navBarHeight + bannerAreaHeight();
const sizeValue = sidebarNavWidth
+ sidebarContentWidth + mediaAreaWidth - mediaBounds.width;
mediaBounds.left = !isRTL ? sizeValue : null;
@ -666,7 +668,7 @@ const CustomLayout = () => {
case CAMERADOCK_POSITION.SIDEBAR_CONTENT_BOTTOM: {
mediaBounds.width = mediaAreaWidth;
mediaBounds.height = mediaAreaHeight;
mediaBounds.top = navBarHeight;
mediaBounds.top = navBarHeight + bannerAreaHeight();
mediaBounds.left = !isRTL ? sidebarSize : null;
mediaBounds.right = isRTL ? sidebarSize : null;
break;
@ -719,7 +721,7 @@ const CustomLayout = () => {
horizontalCameraDiff = camerasMargin * 2;
}
layoutDispatch({
layoutContextDispatch({
type: ACTIONS.SET_NAVBAR_OUTPUT,
value: {
display: navbarInput.hasNavBar,
@ -731,7 +733,7 @@ const CustomLayout = () => {
},
});
layoutDispatch({
layoutContextDispatch({
type: ACTIONS.SET_ACTIONBAR_OUTPUT,
value: {
display: actionbarInput.hasActionBar,
@ -746,7 +748,7 @@ const CustomLayout = () => {
},
});
layoutDispatch({
layoutContextDispatch({
type: ACTIONS.SET_CAPTIONS_OUTPUT,
value: {
left: !isRTL ? (mediaBounds.left + captionsMargin) : null,
@ -755,7 +757,7 @@ const CustomLayout = () => {
},
});
layoutDispatch({
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_NAVIGATION_OUTPUT,
value: {
display: sidebarNavigationInput.isOpen,
@ -773,7 +775,7 @@ const CustomLayout = () => {
},
});
layoutDispatch({
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_NAVIGATION_RESIZABLE_EDGE,
value: {
top: false,
@ -783,7 +785,7 @@ const CustomLayout = () => {
},
});
layoutDispatch({
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_OUTPUT,
value: {
display: sidebarContentInput.isOpen,
@ -802,7 +804,7 @@ const CustomLayout = () => {
},
});
layoutDispatch({
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_RESIZABLE_EDGE,
value: {
top: false,
@ -812,7 +814,7 @@ const CustomLayout = () => {
},
});
layoutDispatch({
layoutContextDispatch({
type: ACTIONS.SET_MEDIA_AREA_SIZE,
value: {
width: windowWidth() - sidebarNavWidth.width - sidebarContentWidth.width,
@ -820,7 +822,7 @@ const CustomLayout = () => {
},
});
layoutDispatch({
layoutContextDispatch({
type: ACTIONS.SET_CAMERA_DOCK_OUTPUT,
value: {
display: cameraDockInput.numCameras > 0,
@ -851,12 +853,12 @@ const CustomLayout = () => {
},
});
layoutDispatch({
layoutContextDispatch({
type: ACTIONS.SET_DROP_AREAS,
value: dropZoneAreas,
});
layoutDispatch({
layoutContextDispatch({
type: ACTIONS.SET_PRESENTATION_OUTPUT,
value: {
display: presentationInput.isOpen,
@ -871,7 +873,7 @@ const CustomLayout = () => {
},
});
layoutDispatch({
layoutContextDispatch({
type: ACTIONS.SET_SCREEN_SHARE_OUTPUT,
value: {
width: mediaBounds.width,
@ -883,7 +885,7 @@ const CustomLayout = () => {
},
});
layoutDispatch({
layoutContextDispatch({
type: ACTIONS.SET_EXTERNAL_VIDEO_OUTPUT,
value: {
width: mediaBounds.width,

View File

@ -91,6 +91,7 @@ const PresentationFocusLayout = () => {
isOpen: false,
},
presentation: {
isOpen: presentationInput.isOpen,
slidesLength: presentationInput.slidesLength,
currentSlide: {
...presentationInput.currentSlide,
@ -118,6 +119,7 @@ const PresentationFocusLayout = () => {
isOpen: false,
},
presentation: {
isOpen: presentationInput.isOpen,
slidesLength: presentationInput.slidesLength,
currentSlide: {
...presentationInput.currentSlide,
@ -263,6 +265,7 @@ const PresentationFocusLayout = () => {
};
const calculatesSidebarContentHeight = () => {
const { isOpen } = presentationInput;
const {
navBarHeight,
sidebarContentMinHeight,
@ -275,7 +278,7 @@ const PresentationFocusLayout = () => {
height = windowHeight() - navBarHeight - bannerAreaHeight();
minHeight = height;
maxHeight = height;
} else if (cameraDockInput.numCameras > 0) {
} else if (cameraDockInput.numCameras > 0 && isOpen) {
if (sidebarContentInput.height === 0) {
height = (windowHeight() * 0.75) - bannerAreaHeight();
} else {
@ -347,9 +350,20 @@ const PresentationFocusLayout = () => {
sidebarContentWidth,
sidebarContentHeight,
) => {
const { isOpen } = presentationInput;
const cameraDockBounds = {};
const sidebarSize = sidebarNavWidth + sidebarContentWidth;
if (cameraDockInput.numCameras > 0) {
if (!isOpen) {
cameraDockBounds.width = mediaAreaBounds.width;
cameraDockBounds.maxWidth = mediaAreaBounds.width;
cameraDockBounds.height = mediaAreaBounds.height;
cameraDockBounds.maxHeight = mediaAreaBounds.height;
cameraDockBounds.top = DEFAULT_VALUES.navBarHeight;
cameraDockBounds.left = !isRTL ? mediaAreaBounds.left : 0;
cameraDockBounds.right = isRTL ? sidebarSize : null;
} else {
let cameraDockHeight = 0;
if (fullscreen.group === 'webcams') {
@ -399,6 +413,7 @@ const PresentationFocusLayout = () => {
cameraDockBounds.maxHeight = windowHeight() - sidebarContentHeight;
cameraDockBounds.zIndex = 1;
}
}
} else {
cameraDockBounds.width = 0;
cameraDockBounds.height = 0;

View File

@ -91,6 +91,7 @@ const SmartLayout = () => {
isOpen: false,
},
presentation: {
isOpen: presentationInput.isOpen,
slidesLength: presentationInput.slidesLength,
currentSlide: {
...presentationInput.currentSlide,
@ -118,6 +119,7 @@ const SmartLayout = () => {
isOpen: false,
},
presentation: {
isOpen: presentationInput.isOpen,
slidesLength: presentationInput.slidesLength,
currentSlide: {
...presentationInput.currentSlide,

View File

@ -99,6 +99,7 @@ const VideoFocusLayout = () => {
isOpen: false,
},
presentation: {
isOpen: presentationInput.isOpen,
slidesLength: presentationInput.slidesLength,
currentSlide: {
...presentationInput.currentSlide,
@ -129,6 +130,7 @@ const VideoFocusLayout = () => {
isOpen: false,
},
presentation: {
isOpen: presentationInput.isOpen,
slidesLength: presentationInput.slidesLength,
currentSlide: {
...presentationInput.currentSlide,
@ -358,8 +360,19 @@ const VideoFocusLayout = () => {
const calculatesCameraDockBounds = (mediaAreaBounds, sidebarSize) => {
const cameraDockBounds = {};
const { isOpen } = presentationInput;
const { numCameras } = cameraDockInput;
if (cameraDockInput.numCameras > 0) {
if (numCameras > 0) {
if (!isOpen) {
cameraDockBounds.width = mediaAreaBounds.width;
cameraDockBounds.maxWidth = mediaAreaBounds.width;
cameraDockBounds.height = mediaAreaBounds.height;
cameraDockBounds.maxHeight = mediaAreaBounds.height;
cameraDockBounds.top = DEFAULT_VALUES.navBarHeight;
cameraDockBounds.left = !isRTL ? mediaAreaBounds.left : 0;
cameraDockBounds.right = isRTL ? sidebarSize : null;
} else {
if (deviceType === DEVICE_TYPE.MOBILE) {
cameraDockBounds.minHeight = mediaAreaBounds.height * 0.7;
cameraDockBounds.height = mediaAreaBounds.height * 0.7;
@ -389,7 +402,7 @@ const VideoFocusLayout = () => {
cameraDockBounds.left = 0;
cameraDockBounds.zIndex = 99;
}
}
return cameraDockBounds;
}

View File

@ -47,9 +47,14 @@ const swapLayout = {
tracker: new Tracker.Dependency(),
};
const setSwapLayout = () => {
const setSwapLayout = (layoutContextDispatch) => {
swapLayout.value = getFromUserSettings('bbb_auto_swap_layout', LAYOUT_CONFIG.autoSwapLayout);
swapLayout.tracker.changed();
layoutContextDispatch({
type: ACTIONS.SET_PRESENTATION_IS_OPEN,
value: !swapLayout.value,
});
};
const toggleSwapLayout = (layoutContextDispatch) => {

View File

@ -117,6 +117,7 @@ class BBBMenu extends React.Component {
open={Boolean(anchorEl)}
onClose={this.handleClose}
className={menuClasses.join(' ')}
style={{ zIndex: 9999 }}
>
{actionsItems}
{anchorEl && window.innerWidth < MAX_WIDTH &&

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ReactModal from 'react-modal';
import { styles } from './styles.scss';
import { registerTitleView, unregisterTitleView } from '/imports/utils/dom-utils';
const propTypes = {
overlayClassName: PropTypes.string.isRequired,
@ -19,6 +20,15 @@ const defaultProps = {
};
export default class ModalBase extends Component {
componentDidMount() {
registerTitleView(this.props.contentLabel);
}
componentWillUnmount() {
unregisterTitleView();
}
render() {
if (!this.props.isOpen) return null;
@ -55,12 +65,12 @@ export const withModalState = ComponentToWrap =>
this.show = this.show.bind(this);
}
hide(cb = () => {}) {
hide(cb = () => { }) {
Promise.resolve(cb())
.then(() => this.setState({ isOpen: false }));
}
show(cb = () => {}) {
show(cb = () => { }) {
Promise.resolve(cb())
.then(() => this.setState({ isOpen: true }));
}

View File

@ -11,6 +11,7 @@ import LiveResult from './live-result/component';
import { styles } from './styles.scss';
import { PANELS, ACTIONS } from '../layout/enums';
import DragAndDrop from './dragAndDrop/component';
import { alertScreenReader } from '/imports/utils/dom-utils';
const intlMessages = defineMessages({
pollPaneTitle: {
@ -185,6 +186,14 @@ const intlMessages = defineMessages({
id: 'app.switch.offLabel',
description: 'label for toggle switch off state',
},
removePollOpt: {
id: 'app.poll.removePollOpt',
description: 'screen reader alert for removed poll option',
},
emptyPollOpt: {
id: 'app.poll.emptyPollOpt',
description: 'screen reader for blank poll option',
},
});
const POLL_SETTINGS = Meteor.settings.public.poll;
@ -295,10 +304,15 @@ class Poll extends Component {
}
handleRemoveOption(index) {
const { intl } = this.props;
const { optList } = this.state;
const list = [...optList];
const removed = list[index];
list.splice(index, 1);
this.setState({ optList: list });
this.setState({ optList: list }, () => {
alertScreenReader(`${intl.formatMessage(intlMessages.removePollOpt,
{ 0: removed.val || intl.formatMessage(intlMessages.emptyPollOpt) })}`);
});
}
handleAddOption() {
@ -395,7 +409,10 @@ class Poll extends Component {
this.handleRemoveOption(i);
}}
/>
<span className="sr-only" id={`option-${i}`}>{intl.formatMessage(intlMessages.deleteRespDesc, { 0: o.val })}</span>
<span className="sr-only" id={`option-${i}`}>
{intl.formatMessage(intlMessages.deleteRespDesc,
{ 0: (o.val || intl.formatMessage(intlMessages.emptyPollOpt)) })}
</span>
</>
)
: <div style={{ width: '40px' }} />}

View File

@ -13,6 +13,7 @@ import logger from '/imports/startup/client/logger';
import { notify } from '/imports/ui/services/notification';
import { toast } from 'react-toastify';
import _ from 'lodash';
import { registerTitleView, unregisterTitleView } from '/imports/utils/dom-utils';
import { styles } from './styles';
const { isMobile } = deviceInfo;
@ -214,6 +215,10 @@ const intlMessages = defineMessages({
id: 'app.presentationUploder.clearErrorsDesc',
description: 'aria description for button clearing upload error',
},
uploadViewTitle: {
id: 'app.presentationUploder.uploadViewTitle',
description: 'view name apended to document title',
}
});
class PresentationUploader extends Component {
@ -251,10 +256,16 @@ class PresentationUploader extends Component {
}
componentDidUpdate(prevProps) {
const { isOpen, presentations: propPresentations } = this.props;
const { isOpen, presentations: propPresentations, intl } = this.props;
const { presentations } = this.state;
if (!isOpen && prevProps.isOpen) {
unregisterTitleView();
}
// Updates presentation list when chat modal opens to avoid missing presentations
if (isOpen && !prevProps.isOpen) {
registerTitleView(intl.formatMessage(intlMessages.uploadViewTitle));
const focusableElements =
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
const modal = document.getElementById('upload-modal');

View File

@ -22,6 +22,10 @@ const intlMessages = defineMessages({
id: 'app.guest-policy.description',
description: 'Guest policy description',
},
policyBtnDesc: {
id: 'app.guest-policy.policyBtnDesc',
description: 'aria description for guest policy button',
},
askModerator: {
id: 'app.guest-policy.button.askModerator',
description: 'Ask moderator button label',
@ -84,9 +88,11 @@ class GuestPolicyComponent extends PureComponent {
<div className={styles.content}>
<Button
color="primary"
className={styles.button}
className={[styles.button, guestPolicy === ASK_MODERATOR && styles.active].join(' ')}
disabled={guestPolicy === ASK_MODERATOR}
label={intl.formatMessage(intlMessages.askModerator)}
aria-describedby={guestPolicy === ASK_MODERATOR ? 'selected-btn-desc' : 'policy-btn-desc'}
aria-pressed={guestPolicy === ASK_MODERATOR}
data-test="askModerator"
onClick={() => {
changeGuestPolicy(ASK_MODERATOR);
@ -95,9 +101,11 @@ class GuestPolicyComponent extends PureComponent {
/>
<Button
color="primary"
className={styles.button}
className={[styles.button, guestPolicy === ALWAYS_ACCEPT && styles.active].join(' ')}
disabled={guestPolicy === ALWAYS_ACCEPT}
label={intl.formatMessage(intlMessages.alwaysAccept)}
aria-describedby={guestPolicy === ALWAYS_ACCEPT ? 'selected-btn-desc' : 'policy-btn-desc'}
aria-pressed={guestPolicy === ALWAYS_ACCEPT}
data-test="alwaysAccept"
onClick={() => {
changeGuestPolicy(ALWAYS_ACCEPT);
@ -106,9 +114,11 @@ class GuestPolicyComponent extends PureComponent {
/>
<Button
color="primary"
className={styles.button}
className={[styles.button, guestPolicy === ALWAYS_DENY && styles.active].join(' ')}
disabled={guestPolicy === ALWAYS_DENY}
label={intl.formatMessage(intlMessages.alwaysDeny)}
aria-describedby={guestPolicy === ALWAYS_DENY ? 'selected-btn-desc' : 'policy-btn-desc'}
aria-pressed={guestPolicy === ALWAYS_DENY}
data-test="alwaysDeny"
onClick={() => {
changeGuestPolicy(ALWAYS_DENY);
@ -116,6 +126,9 @@ class GuestPolicyComponent extends PureComponent {
}}
/>
</div>
<div id="policy-btn-desc" aria-hidden className="sr-only">
{intl.formatMessage(intlMessages.policyBtnDesc)}
</div>
</div>
</Modal>
);

View File

@ -66,3 +66,9 @@
box-sizing: border-box;
margin: 5px;
}
.active {
span {
text-decoration: underline;
}
}

View File

@ -98,7 +98,7 @@ const WebcamComponent = ({
);
Storage.setItem('webcamSize', { width: newCameraMaxWidth, height: lastHeight });
}
}, [cameraDock.position, isPresenter, displayPresentation]);
}, [cameraDock.position, cameraDock.maxWidth, isPresenter, displayPresentation]);
const onResizeHandle = (deltaWidth, deltaHeight) => {
if (cameraDock.resizableEdge.top || cameraDock.resizableEdge.bottom) {

View File

@ -1,4 +1,6 @@
:root {
--palette-placeholder-text: #787675;
--color-white: #FFF;
--color-off-white: #F3F6F9;

View File

@ -0,0 +1,40 @@
const TITLE_WITH_VIEW = 3;
const ARIA_ALERT_TIMEOUT = 3000;
const getTitleData = () => {
const title = document.getElementsByTagName('title')[0];
return { title, data: title?.text?.split(' - ') };
}
export const registerTitleView = (v) => {
const { title, data } = getTitleData();
if (data.length < TITLE_WITH_VIEW) data.push(`${v}`);
else data.splice(TITLE_WITH_VIEW - 1, TITLE_WITH_VIEW, v);
title.text = data.join(' - ');
};
export const unregisterTitleView = () => {
const { title, data } = getTitleData();
if (data.length === TITLE_WITH_VIEW) {
data.splice(TITLE_WITH_VIEW - 1, TITLE_WITH_VIEW, 'Default');
}
title.text = data.join(' - ');
};
export const alertScreenReader = (s = '') => {
const app = document.getElementById('app');
const ariaAlert = document.createElement("div");
ariaAlert.setAttribute("id", "aria-alert");
ariaAlert.setAttribute("role", "alert");
ariaAlert.setAttribute("aria-hidden", false);
ariaAlert.setAttribute("className", "sr-only");
ariaAlert.textContent = s;
app.appendChild(ariaAlert);
setTimeout(() => {
document.getElementById('aria-alert').remove();
}, ARIA_ALERT_TIMEOUT);
};
export default { registerTitleView, unregisterTitleView, alertScreenReader };

View File

@ -25,6 +25,8 @@
"app.chat.multi.typing": "Multiple users are typing",
"app.chat.one.typing": "{0} is typing",
"app.chat.two.typing": "{0} and {1} are typing",
"app.chat.copySuccess": "Copied chat transcript",
"app.chat.copyErr": "Copy chat transcript failed",
"app.captions.label": "Captions",
"app.captions.menu.close": "Close",
"app.captions.menu.start": "Start",
@ -51,6 +53,7 @@
"app.captions.pad.dictationOffDesc": "Turns speech recognition off",
"app.captions.pad.speechRecognitionStop": "Speech recognition stopped due to the browser incompatibility or some time of silence",
"app.textInput.sendLabel": "Send",
"app.title.defaultViewLabel": "Default presentation view",
"app.note.title": "Shared Notes",
"app.note.label": "Note",
"app.note.hideNoteLabel": "Hide note",
@ -230,6 +233,7 @@
"app.presentationUploder.itemPlural" : "items",
"app.presentationUploder.clearErrors": "Clear errors",
"app.presentationUploder.clearErrorsDesc": "Clears failed presentation uploads",
"app.presentationUploder.uploadViewTitle": "Upload Presentation",
"app.poll.pollPaneTitle": "Polling",
"app.poll.quickPollTitle": "Quick Poll",
"app.poll.hidePollDesc": "Hides the poll menu pane",
@ -288,6 +292,8 @@
"app.poll.liveResult.usersTitle": "Users",
"app.poll.liveResult.responsesTitle": "Response",
"app.poll.liveResult.secretLabel": "This is an anonymous poll. Individual responses are not shown.",
"app.poll.removePollOpt": "Removed Poll option {0}",
"app.poll.emptyPollOpt": "Blank",
"app.polling.pollingTitle": "Polling options",
"app.polling.pollQuestionTitle": "Polling Question",
"app.polling.submitLabel": "Submit",
@ -662,6 +668,7 @@
"app.guest-policy.button.askModerator": "Ask moderator",
"app.guest-policy.button.alwaysAccept": "Always accept",
"app.guest-policy.button.alwaysDeny": "Always deny",
"app.guest-policy.policyBtnDesc": "Sets meeting guest policy",
"app.connection-status.ariaTitle": "Connection status modal",
"app.connection-status.title": "Connection status",
"app.connection-status.description": "View users' connection status",

View File

@ -7,11 +7,11 @@ class Audio extends Page {
}
async test() {
return await util.joinAudio(this);
return util.joinAudio(this);
}
async microphone() {
return await util.joinMicrophone(this);
return util.joinMicrophone(this);
}
}

View File

@ -1,26 +1,27 @@
const ae = require('./elements');
const { clickElement, getElementLength } = require('../core/util');
const { ELEMENT_WAIT_TIME, ELEMENT_WAIT_LONGER_TIME } = require('../core/constants');
async function joinAudio(test) {
await test.waitForSelector(ae.joinAudio, ELEMENT_WAIT_TIME);
await test.page.evaluate(clickTestElement, ae.joinAudio);
await test.page.evaluate(clickElement, ae.joinAudio);
await test.waitForSelector(ae.listen, ELEMENT_WAIT_TIME);
await test.page.evaluate(clickTestElement, ae.listen);
await test.page.evaluate(clickElement, ae.listen);
await test.waitForSelector(ae.connectingStatus, ELEMENT_WAIT_TIME);
await test.waitForElementHandleToBeRemoved(ae.connectingStatus, ELEMENT_WAIT_LONGER_TIME);
const parsedSettings = await test.getSettingsYaml();
const listenOnlyCallTimeout = parseInt(parsedSettings.public.media.listenOnlyCallTimeout);
await test.waitForSelector(ae.leaveAudio, listenOnlyCallTimeout);
await test.waitForSelector(ae.whiteboard, ELEMENT_WAIT_TIME);
const resp = await test.page.evaluate(getTestElement, ae.leaveAudio);
const resp = await test.page.evaluate(getElementLength, ae.leaveAudio) >= 1;
return resp;
}
async function joinMicrophone(test) {
await test.waitForSelector(ae.joinAudio, ELEMENT_WAIT_TIME);
await test.page.evaluate(clickTestElement, ae.joinAudio);
await test.page.evaluate(clickElement, ae.joinAudio);
await test.waitForSelector(ae.microphone, ELEMENT_WAIT_TIME);
await test.page.evaluate(clickTestElement, ae.microphone);
await test.page.evaluate(clickElement, ae.microphone);
await test.waitForSelector(ae.connectingStatus, ELEMENT_WAIT_TIME);
await test.waitForElementHandleToBeRemoved(ae.connectingStatus, ELEMENT_WAIT_LONGER_TIME);
const parsedSettings = await test.getSettingsYaml();
@ -28,17 +29,9 @@ async function joinMicrophone(test) {
await test.waitForSelector(ae.audioAudible, listenOnlyCallTimeout);
await test.click(ae.audioAudible, true);
await test.waitForSelector(ae.whiteboard, ELEMENT_WAIT_TIME);
const resp = await test.page.evaluate(getTestElement, ae.audioAudible);
const resp = await test.page.evaluate(getElementLength, ae.audioAudible) >= 1;
return resp;
}
async function clickTestElement(element) {
document.querySelectorAll(element)[0].click();
}
async function getTestElement(element) {
return document.querySelectorAll(element).length >= 1 === true;
}
exports.joinAudio = joinAudio;
exports.joinMicrophone = joinMicrophone;

View File

@ -2,14 +2,13 @@ const moment = require('moment');
const path = require('path');
const Page = require('../core/page');
const params = require('../params');
const util = require('./util');
const be = require('./elements'); // breakout elements
const we = require('../webcam/elements'); // webcam elements
const ae = require('../audio/elements'); // audio elements
const ue = require('../user/elements'); // user elements
const ce = require('../customparameters/elements'); // customparameters elements
const e = require('../core/elements'); // page base elements
// core constants (Timeouts vars imported)
const { checkElement, clickElement } = require('../core/util');
const { ELEMENT_WAIT_TIME, ELEMENT_WAIT_LONGER_TIME } = require('../core/constants');
const today = moment().format('DD-MM-YYYY');
@ -48,7 +47,7 @@ class Create {
await this.page1.click(ue.askModerator, true);
await this.page1.screenshot(`${testName}`, `05-clicked-askModerator-[${this.page1.meetingId}]`);
await this.initViewer(testName);
const responseLoggedIn = await this.page1.page.evaluate(util.getTestElement, ue.waitingUsersBtn);
const responseLoggedIn = await this.page1.page.evaluate(checkElement, ue.waitingUsersBtn);
await this.page1.screenshot(`${testName}`, `06-after-viewer-acceptance-[${this.page1.meetingId}]`);
return responseLoggedIn;
} catch (err) {
@ -74,7 +73,7 @@ class Create {
await this.page1.screenshot(`${testName}`, `05-clicked-alwaysAccept-[${this.page1.meetingId}]`);
await this.initViewer(testName);
await this.page3.closeAudioModal();
const responseLoggedIn = await this.page3.page.evaluate(util.getTestElement, e.whiteboard);
const responseLoggedIn = await this.page3.page.evaluate(checkElement, e.whiteboard);
await this.page3.screenshot(`${testName}`, `06-after-viewer-connection-[${this.page1.meetingId}]`);
return responseLoggedIn;
} catch (err) {
@ -99,7 +98,7 @@ class Create {
await this.page1.click(ue.alwaysAccept, true);
await this.page1.screenshot(`${testName}`, `05-clicked-alwaysAccept-[${this.page1.meetingId}]`);
await this.initViewer(testName);
const responseLoggedIn = await this.page3.page.evaluate(util.getTestElement, ue.joinMeetingDemoPage);
const responseLoggedIn = await this.page3.page.evaluate(checkElement, ue.joinMeetingDemoPage);
await this.page3.screenshot(`${testName}`, `06-after-viewer-gets-denied-[${this.page1.meetingId}]`);
return responseLoggedIn;
} catch (err) {
@ -116,20 +115,20 @@ class Create {
await this.page1.screenshot(`${testName}`, `01-page01-initialized-${testName}`);
await this.page2.screenshot(`${testName}`, `01-page02-initialized-${testName}`);
await this.page1.page.evaluate(util.clickTestElement, be.manageUsers);
await this.page1.page.evaluate(util.clickTestElement, be.createBreakoutRooms);
await this.page1.page.evaluate(clickElement, be.manageUsers);
await this.page1.page.evaluate(clickElement, be.createBreakoutRooms);
await this.page1.screenshot(`${testName}`, `02-page01-creating-breakoutrooms-${testName}`);
await this.page1.waitForSelector(be.randomlyAssign, ELEMENT_WAIT_TIME);
await this.page1.page.evaluate(util.clickTestElement, be.randomlyAssign);
await this.page1.page.evaluate(clickElement, be.randomlyAssign);
await this.page1.screenshot(`${testName}`, `03-page01-randomly-assign-user-${testName}`);
await this.page1.waitForSelector(be.modalConfirmButton, ELEMENT_WAIT_LONGER_TIME);
await this.page1.page.evaluate(util.clickTestElement, be.modalConfirmButton);
await this.page1.page.evaluate(clickElement, be.modalConfirmButton);
await this.page1.screenshot(`${testName}`, `04-page01-confirm-breakoutrooms-creation-${testName}`);
await this.page2.waitForSelector(be.modalConfirmButton, ELEMENT_WAIT_LONGER_TIME);
await this.page2.page.evaluate(util.clickTestElement, be.modalConfirmButton);
await this.page2.page.evaluate(clickElement, be.modalConfirmButton);
await this.page2.screenshot(`${testName}`, `02-page02-accept-invite-breakoutrooms-${testName}`);
await this.page2.page.bringToFront();
@ -157,7 +156,7 @@ class Create {
// Check if Breakoutrooms have been created
async testCreatedBreakout(testName) {
try {
const resp = await this.page1.page.evaluate(() => document.querySelectorAll('div[data-test="breakoutRoomsItem"]').length !== 0);
const resp = await this.page1.page.evaluate(checkElement, be.breakoutRoomsItem);
if (resp === true) {
await this.page1.screenshot(`${testName}`, `05-page01-success-${testName}`);

View File

@ -1,13 +1,12 @@
const path = require('path');
const moment = require('moment');
const Page = require('../core/page');
const Create = require('./create');
const util = require('./util');
const utilScreenShare = require('../screenshare/util');
const e = require('./elements');
const pe = require('../core/elements');
const we = require('../webcam/elements');
const ae = require('../audio/elements');
const { checkElement } = require('../core/util');
const { ELEMENT_WAIT_TIME, VIDEO_LOADING_WAIT_TIME } = require('../core/constants'); // core constants (Timeouts vars imported)
const today = moment().format('DD-MM-YYYY');
@ -41,7 +40,7 @@ class Join extends Create {
}
await this.page3.logger('before pages check');
const resp = await page2[2].evaluate(util.getTestElement, pe.isTalking);
const resp = await page2[2].evaluate(checkElement, pe.isTalking);
if (process.env.GENERATE_EVIDENCES === 'true') {
await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/06-breakout-page02-user-joined-with-audio-after-check-${testName}.png`) });
@ -58,7 +57,7 @@ class Join extends Create {
}
await this.page3.logger('before pages check');
const resp = await page2[2].evaluate(util.getTestElement, we.videoContainer);
const resp = await page2[2].evaluate(checkElement, we.videoContainer);
if (process.env.GENERATE_EVIDENCES === 'true') {
await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/06-breakout-page02-user-joined-webcam-before-check-${testName}.png`) });
@ -80,7 +79,7 @@ class Join extends Create {
await page2[2].screenshot({ path: path.join(__dirname, `../${process.env.TEST_FOLDER}/test-${today}-${testName}/screenshots/06-breakout-page02-user-joined-screenshare-after-check-${testName}.png`) });
}
await this.page3.logger('after pages check');
this.page2.logger('after pages check');
return resp === true;
} else {
await this.page3.page.bringToFront();
@ -88,7 +87,8 @@ class Join extends Create {
await this.page3.waitForSelector(e.chatButton, ELEMENT_WAIT_TIME);
await this.page3.click(e.chatButton, true);
await this.page3.click(e.breakoutRoomsItem, true);
const resp = await this.page3.page.evaluate(async () => await document.querySelectorAll('span[class^="alreadyConnected--"]') !== null);
const resp = await this.page3.page.evaluate(checkElement, e.alreadyConnected);
return resp === true;
}
} catch (err) {

View File

@ -1,5 +1,4 @@
const e = require('./elements');
const pe = require('../core/elements');
const { ELEMENT_WAIT_TIME } = require('../core/constants'); // core constants (Timeouts vars imported)
async function createBreakoutRooms(page1, page2) {
@ -13,14 +12,4 @@ async function createBreakoutRooms(page1, page2) {
await page2.click(e.modalConfirmButton, true);
}
async function getTestElement(element) {
return document.querySelectorAll(element)[0] !== null;
}
async function clickTestElement(element) {
await document.querySelectorAll(element)[0].click();
}
exports.getTestElement = getTestElement;
exports.createBreakoutRooms = createBreakoutRooms;
exports.clickTestElement = clickTestElement;

View File

@ -3,7 +3,7 @@
const Page = require('../core/page');
const e = require('./elements');
const util = require('./util');
const { chatPushAlerts } = require('../notifications/elements');
const { checkElementLengthEqualTo } = require('../core/util');
const { ELEMENT_WAIT_TIME } = require('../core/constants');
class Clear extends Page {
@ -23,7 +23,7 @@ class Clear extends Page {
await this.screenshot(`${testName}`, `02-after-chat-message-send-[${this.meetingId}]`);
const chat0 = await this.page.evaluate(() => document.querySelectorAll('p[data-test="chatClearMessageText"]').length === 0);
const chat0 = await this.page.evaluate(checkElementLengthEqualTo, e.chatClearMessageText, 0);
// clear
await this.click(e.chatOptions, true);

View File

@ -1,8 +1,8 @@
// Test: Sending a chat message
const Notifications = require('../notifications/notifications');
const Page = require('../core/page');
const e = require('./elements');
const { checkElementLengthEqualTo } = require('../core/util');
const { ELEMENT_WAIT_TIME } = require('../core/constants');
class Poll extends Notifications {
@ -13,7 +13,7 @@ class Poll extends Notifications {
async test(testName) {
try {
// 0 messages
const chat0 = await this.page3.page.evaluate(() => document.querySelectorAll('p[data-test="chatPollMessageText"]').length === 0);
const chat0 = await this.page3.page.evaluate(checkElementLengthEqualTo, e.chatPollMessageText, 0);
await this.page3.screenshot(`${testName}`, `01-before-chat-message-send-[${this.page3.meetingId}]`);
await this.publishPollResults(testName);
@ -23,7 +23,7 @@ class Poll extends Notifications {
await this.page3.waitForSelector(e.chatPollMessageText, ELEMENT_WAIT_TIME);
// 1 message
const chat1 = await this.page3.page.evaluate(() => document.querySelectorAll('p[data-test="chatPollMessageText"]').length === 1);
const chat1 = await this.page3.page.evaluate(checkElementLengthEqualTo, e.chatPollMessageText, 1);
return chat0 === chat1;
} catch (err) {
await this.page3.logger(err);

View File

@ -3,6 +3,7 @@
const Page = require('../core/page');
const e = require('./elements');
const util = require('./util');
const { checkElementLengthEqualTo } = require('../core/util');
class Send extends Page {
constructor() {
@ -14,7 +15,7 @@ class Send extends Page {
await util.openChat(this);
// 0 messages
const chat0 = await this.page.evaluate((chatSelector) => document.querySelectorAll(chatSelector).length === 0, e.chatUserMessageText);
const chat0 = await this.page.evaluate(checkElementLengthEqualTo, e.chatUserMessageText, 0);
await this.screenshot(`${testName}`, `01-before-chat-message-send-[${this.meetingId}]`);
// send a message
@ -27,7 +28,7 @@ class Send extends Page {
await this.waitForSelector(e.chatUserMessageText);
// 1 message
const chat1 = await this.page.evaluate((chatSelector) => document.querySelectorAll(chatSelector).length === 1, e.chatUserMessageText);
const chat1 = await this.page.evaluate(checkElementLengthEqualTo, e.chatUserMessageText, 1);
return chat0 === chat1;
} catch (err) {
await this.logger(err);

View File

@ -1,5 +1,6 @@
const e = require('./elements');
const ule = require('../user/elements');
const { clickElement } = require('../core/util');
const { ELEMENT_WAIT_TIME } = require('../core/constants');
async function openChat(test) {
@ -20,12 +21,12 @@ async function sendPublicChatMessage(page1, page2) {
async function openPrivateChatMessage(page1, page2) {
// Open private Chat with the other User
Object.values(arguments).forEach(async argument => await argument.waitForSelector(ule.userListItem, ELEMENT_WAIT_TIME));
await page1.page.evaluate(clickOnTheOtherUser, ule.userListItem);
await page2.page.evaluate(clickOnTheOtherUser, ule.userListItem);
await page1.page.evaluate(clickElement, ule.userListItem);
await page2.page.evaluate(clickElement, ule.userListItem);
await page1.page.waitForSelector(e.activeChat, ELEMENT_WAIT_TIME);
await page1.page.evaluate(clickThePrivateChatButton, e.activeChat);
await page1.page.evaluate(clickElement, e.activeChat);
await page2.page.waitForSelector(e.activeChat, ELEMENT_WAIT_TIME);
await page2.page.evaluate(clickThePrivateChatButton, e.activeChat);
await page2.page.evaluate(clickElement, e.activeChat);
}
async function sendPrivateChatMessage(page1, page2) {
@ -41,14 +42,6 @@ async function sendPrivateChatMessage(page1, page2) {
await page2.page.screenshot(true);
}
async function clickOnTheOtherUser(element) {
await document.querySelectorAll(element)[0].click();
}
async function clickThePrivateChatButton(element) {
await document.querySelectorAll(element)[0].click();
}
async function checkForPublicMessageReception(page1, page2) {
const publicChat1 = await page1.page.$$(`${e.chatUserMessage} ${e.chatMessageText}`);
const publicChat2 = await page2.page.$$(`${e.chatUserMessage} ${e.chatMessageText}`);

View File

@ -6,18 +6,18 @@ exports.echoYes = 'button[aria-label="Echo is audible"]';
exports.title = '._imports_ui_components_nav_bar__styles__presentationTitle';
exports.alerts = '.toastify-content';
exports.presenterClassName = 'presenter--';
exports.zoomIn = 'button[aria-label="Zoom in"]';
exports.pdfFileName = '100PagesFile';
exports.isTalking = '[data-test="isTalking"]';
exports.wasTalking = '[data-test="wasTalking"]';
exports.joinAudio = 'button[data-test="joinAudio"]';
exports.leaveAudio = 'button[aria-label="Leave audio"]';
exports.leaveAudio = 'button[data-test="leaveAudio"]';
exports.disconnectAudio = 'li[data-test="disconnectAudio"]';
exports.actions = 'button[aria-label="Actions"]';
exports.options = 'button[aria-label="Options"]';
exports.userList = 'button[aria-label="Users and Messages Toggle"]';
exports.joinAudio = 'button[aria-label="Join Audio"]';
exports.connectingStatus = 'div[class^="connecting--"]';
exports.videoMenu = 'button[aria-label="Open video menu dropdown"]';
exports.screenShare = 'button[aria-label="Share your screen"]';
@ -28,5 +28,5 @@ exports.logout = 'li[data-test="logout"]';
exports.meetingEndedModal = 'div[data-test="meetingEndedModal"]';
exports.rating = 'div[data-test="rating"]';
exports.whiteboard = 'svg[data-test="whiteboard"]';
exports.pollMenuButton = 'button[data-test="pollMenuButton"]';
exports.pollMenuButton = 'div[data-test="pollMenuButton"]';
exports.unauthorized = 'h1[data-test="unauthorized"]';

View File

@ -9,6 +9,7 @@ const PuppeteerVideoRecorder = require('puppeteer-video-recorder');
const helper = require('./helper');
const params = require('../params');
const { ELEMENT_WAIT_TIME } = require('./constants');
const { getElementLength } = require('./util');
const e = require('./elements');
const ue = require('../user/elements');
const { NETWORK_PRESETS } = require('./profiles');
@ -169,7 +170,7 @@ class Page {
}
async returnElement(element) {
return await document.querySelectorAll(element)[0];
return document.querySelectorAll(element)[0];
}
async getUserAgent(test) {
@ -410,7 +411,7 @@ class Page {
const users = collection.default._collection.find({}, {}, {}, {}, {}, { loggedOut: 'false' }).count();
return users;
});
const totalNumberOfUsersDom = await this.page.evaluate(async () => await document.querySelectorAll('[data-test^="userListItem"]').length);
const totalNumberOfUsersDom = await this.page.evaluate(getElementLength, '[data-test^="userListItem"]');
await this.logger({ totalNumberOfUsersDom, totalNumberOfUsersMongo });
const metric = await this.page.metrics();
pageMetricsObj.totalNumberOfUsersMongoObj = totalNumberOfUsersMongo;

View File

@ -0,0 +1,44 @@
// Common
function checkElement(element, index = 0) {
return document.querySelectorAll(element)[index] !== undefined;
}
function clickElement(element, index = 0) {
document.querySelectorAll(element)[index].click();
}
// Text
function checkElementText(element, param, index = 0) {
return document.querySelectorAll(element)[index].innerText === param;
}
function checkElementTextIncludes(element, param, index = 0) {
return document.querySelectorAll(element)[index].innerText.includes(param);
}
function getElementText(element, index = 0) {
return document.querySelectorAll(element)[index].innerText;
}
// Length
function checkElementLengthEqualTo(element, param) {
return document.querySelectorAll(element).length === param;
}
function checkElementLengthDifferentTo(element, param) {
return document.querySelectorAll(element).length !== param;
}
// use this for other operations
function getElementLength(element) {
return document.querySelectorAll(element).length;
}
exports.checkElement = checkElement;
exports.clickElement = clickElement;
exports.checkElementText = checkElementText;
exports.checkElementTextIncludes = checkElementTextIncludes;
exports.getElementText = getElementText;
exports.checkElementLengthEqualTo = checkElementLengthEqualTo;
exports.checkElementLengthDifferentTo = checkElementLengthDifferentTo;
exports.getElementLength = getElementLength;

View File

@ -1,4 +1,3 @@
const path = require('path');
const Page = require('../core/page');
const params = require('../params');
const ne = require('../notifications/elements');
@ -6,9 +5,11 @@ const pe = require('../core/elements');
const cpe = require('./elements');
const we = require('../webcam/elements');
const ae = require('../audio/elements');
const ce = require('../chat/elements');
const util = require('./util');
const c = require('./constants');
const { ELEMENT_WAIT_TIME, VIDEO_LOADING_WAIT_TIME, ELEMENT_WAIT_LONGER_TIME } = require('../core/constants'); // core constants (Timeouts vars imported)
const { checkElementLengthEqualTo, checkElementLengthDifferentTo } = require('../core/util');
class CustomParameters {
constructor() {
@ -30,12 +31,13 @@ class CustomParameters {
await this.page1.startRecording(testName);
await this.page1.screenshot(`${testName}`, `01-${testName}`);
await this.page1.waitForSelector('div[data-test="chatMessages"]', ELEMENT_WAIT_TIME);
if (await this.page1.page.evaluate(util.getTestElement, cpe.audioModal) === false) {
await this.page1.waitForSelector(ce.chatMessages, ELEMENT_WAIT_TIME);
const resp = await this.page1.page.evaluate(checkElementLengthEqualTo, cpe.audioModal, 0);
if (!resp) {
await this.page1.screenshot(`${testName}`, `02-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page1.page.evaluate(util.getTestElement, cpe.audioModal) === true;
await this.page1.screenshot(`${testName}`, `02-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -46,26 +48,22 @@ class CustomParameters {
await this.page1.startRecording(testName);
await this.page1.waitForSelector(pe.audioDialog, ELEMENT_WAIT_TIME);
await this.page1.screenshot(`${testName}`, `01-page1-${testName}`);
const audioOptionsButton = await this.page1.page.evaluate(async () => {
const countFoundElements = await document.querySelectorAll('[class^="audioOptions"] > button').length;
return countFoundElements;
});
if (audioOptionsButton === 1) {
await this.page1.screenshot(`${testName}`, `04-success-${testName}`);
this.page1.logger(testName, ' passed');
return true;
} if (audioOptionsButton !== 1) {
const audioOptionsButton = await this.page1.page.evaluate(checkElementLengthEqualTo, cpe.audioOptionsButtons, 1);
if (!audioOptionsButton) {
await this.page1.screenshot(`${testName}`, `04-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
await this.page1.screenshot(`${testName}`, `04-success-${testName}`);
this.page1.logger(testName, ' passed');
return true;
}
async forceListenOnly(testName, args, meetingId, customParameter) {
await this.page2.init(args, meetingId, { ...params, fullName: 'Attendee', moderatorPW: '' }, customParameter, testName);
await this.page2.startRecording(testName);
await this.page2.screenshot(`${testName}`, `01-${testName}`);
if (await this.page2.page.$('[data-test="audioModalHeader"]')) {
if (await this.page2.page.$(cpe.audioModalHeader)) {
await this.page2.screenshot(`${testName}`, `02-fail-${testName}`);
this.page2.logger(testName, ' failed');
return false;
@ -87,32 +85,30 @@ class CustomParameters {
await this.page1.screenshot(`${testName}`, `02-${testName}`);
await this.page1.waitForElementHandleToBeRemoved(ae.connectingStatus, ELEMENT_WAIT_LONGER_TIME);
await this.page1.screenshot(`${testName}`, `03-${testName}`);
if (await this.page1.page.evaluate(util.countTestElements, cpe.echoTestYesButton) === true) {
const resp = await this.page1.page.evaluate(checkElementLengthEqualTo, cpe.echoTestYesButton, 0);
if (!resp) {
await this.page1.screenshot(`${testName}`, `04-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page1.page.evaluate(util.countTestElements, cpe.echoTestYesButton) === false;
await this.page1.screenshot(`${testName}`, `04-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
}
async skipCheckOnFirstJoin(testName, args, meetingId, customParameter) {
const parsedSettings = await this.page1.getSettingsYaml();
const listenOnlyCallTimeout = parseInt(parsedSettings.public.media.listenOnlyCallTimeout);
await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator' }, customParameter, testName);
await this.page1.startRecording(testName);
await this.page1.screenshot(`${testName}`, `01-${testName}`);
await this.page1.click(ae.microphone, true);
const firstCheck = await this.page1.page.evaluate(util.getTestElement, ae.connecting) === false;
const firstCheck = await this.page1.page.evaluate(checkElementLengthDifferentTo, ae.connecting, 0);
await this.page1.screenshot(`${testName}`, `02-${testName}`);
await this.page1.leaveAudio();
await this.page1.screenshot(`${testName}`, `03-${testName}`);
await this.page1.waitForSelector(pe.joinAudio, ELEMENT_WAIT_TIME);
await this.page1.click(pe.joinAudio, true);
await this.page1.click(ae.microphone, true);
const secondCheck = await this.page1.page.evaluate(util.getTestElement, ae.connectingToEchoTest) === false;
const secondCheck = await this.page1.page.evaluate(checkElementLengthDifferentTo, ae.connectingToEchoTest, 0);
if (firstCheck !== secondCheck) {
await this.page1.screenshot(`${testName}`, `04-fail-${testName}`);
@ -130,12 +126,12 @@ class CustomParameters {
await this.page1.screenshot(`${testName}`, `01-${testName}`);
await this.page1.waitForSelector(pe.whiteboard, ELEMENT_WAIT_TIME);
await this.page1.screenshot(`${testName}`, `02-${testName}`);
if (await !(await this.page1.page.title()).includes(c.docTitle)) {
const resp = await (await this.page1.page.title()).includes(c.docTitle);
if (!resp) {
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await (await this.page1.page.title()).includes(c.docTitle);
await this.page1.screenshot(`${testName}`, `03-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -152,12 +148,12 @@ class CustomParameters {
await this.page1.waitForSelector(cpe.meetingEndedModal, ELEMENT_WAIT_TIME);
await this.page1.screenshot(`${testName}`, `04-${testName}`);
this.page1.logger('audio modal closed');
if (await this.page1.page.evaluate(util.countTestElements, cpe.rating) === false) {
const resp = await this.page1.page.evaluate(checkElementLengthDifferentTo, cpe.rating, 0);
if (!resp) {
await this.page1.screenshot(`${testName}`, `05-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page1.page.evaluate(util.countTestElements, cpe.rating) === true;
await this.page1.screenshot(`${testName}`, `05-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -171,12 +167,12 @@ class CustomParameters {
await this.page1.screenshot(`${testName}`, `02-${testName}`);
this.page1.logger('audio modal closed');
await this.page1.waitForSelector(cpe.userListContent, ELEMENT_WAIT_TIME);
if (await this.page1.page.evaluate(util.countTestElements, cpe.brandingAreaLogo) === false) {
const resp = await this.page1.page.evaluate(checkElementLengthDifferentTo, cpe.brandingAreaLogo, 0);
if (!resp) {
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page1.page.evaluate(util.countTestElements, cpe.brandingAreaLogo) === true;
await this.page1.screenshot(`${testName}`, `03-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -192,12 +188,12 @@ class CustomParameters {
await this.page1.waitForSelector(pe.options, ELEMENT_WAIT_TIME);
await this.page1.page.keyboard.down('Alt');
await this.page1.page.keyboard.press('O');
if (await this.page1.page.evaluate(util.getTestElement, cpe.verticalListOptions) === false) {
const resp = await this.page1.page.evaluate(checkElementLengthEqualTo, cpe.verticalListOptions, 0);
if (!resp) {
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page1.page.evaluate(util.getTestElement, cpe.verticalListOptions) === true;
await this.page1.screenshot(`${testName}`, `03-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -208,12 +204,12 @@ class CustomParameters {
await this.page1.startRecording(testName);
await this.page1.closeAudioModal();
await this.page1.screenshot(`${testName}`, `01-${testName}`);
if (await this.page1.page.evaluate(util.getTestElement, cpe.screenShareButton) === false) {
const resp = await this.page1.page.evaluate(checkElementLengthEqualTo, cpe.screenShareButton, 0);
if (!resp) {
await this.page1.screenshot(`${testName}`, `02-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page1.page.evaluate(util.getTestElement, cpe.screenShareButton) === true;
await this.page1.screenshot(`${testName}`, `02-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -224,12 +220,12 @@ class CustomParameters {
await this.page1.startRecording(testName);
await this.page1.closeAudioModal();
await this.page1.screenshot(`${testName}`, `01-${testName}`);
if (await this.page1.page.evaluate(util.getTestElement, cpe.shareWebcamButton) === false) {
const resp = await this.page1.page.evaluate(checkElementLengthEqualTo, cpe.shareWebcamButton, 0);
if (!resp) {
await this.page1.screenshot(`${testName}`, `02-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page1.page.evaluate(util.getTestElement, cpe.shareWebcamButton) === true;
await this.page1.screenshot(`${testName}`, `02-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -241,12 +237,12 @@ class CustomParameters {
await this.page1.screenshot(`${testName}`, `01-${testName}`);
await this.page1.closeAudioModal();
await this.page1.screenshot(`${testName}`, `02-${testName}`);
if (await this.page1.page.evaluate(util.getTestElement, cpe.webcamSettingsModal) === true) {
const resp = await this.page1.page.evaluate(checkElementLengthEqualTo, cpe.webcamSettingsModal, 0);
if (!resp) {
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page1.page.evaluate(util.getTestElement, cpe.webcamSettingsModal) === false;
await this.page1.screenshot(`${testName}`, `03-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -269,12 +265,14 @@ class CustomParameters {
await this.page2.waitForSelector(cpe.tools, ELEMENT_WAIT_TIME);
await this.page2.click(cpe.tools, true);
await this.page2.screenshot(`${testName}`, `04-page2-${testName}`);
if (await this.page2.page.evaluate(async () => await document.querySelectorAll('[aria-label="Tools"]')[0].parentElement.childElementCount === 2)) {
const resp = await this.page2.page.evaluate((toolsElement) => {
return document.querySelectorAll(toolsElement)[0].parentElement.childElementCount === 1;
}, cpe.tools);
if (!resp) {
await this.page2.screenshot(`${testName}`, `05-page2-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page2.page.evaluate(async () => await document.querySelectorAll('[aria-label="Tools"]')[0].parentElement.childElementCount === 1);
await this.page2.screenshot(`${testName}`, `05-page2-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -289,12 +287,14 @@ class CustomParameters {
await this.page1.waitForSelector(cpe.tools, ELEMENT_WAIT_TIME);
await this.page1.click(cpe.tools, true);
await this.page1.screenshot(`${testName}`, `03-${testName}`);
if (await this.page1.page.evaluate(async () => await document.querySelectorAll('[aria-label="Tools"]')[0].parentElement.querySelector('[class^="toolbarList--"]').childElementCount === 7)) {
const resp = await this.page1.page.evaluate((toolsElement, toolbarListSelector) => {
return document.querySelectorAll(toolsElement)[0].parentElement.querySelector(toolbarListSelector).childElementCount === 2;
}, cpe.tools, cpe.toolbarListClass);
if (!resp) {
await this.page1.screenshot(`${testName}`, `04-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page1.page.evaluate(async () => await document.querySelectorAll('[aria-label="Tools"]')[0].parentElement.querySelector('[class^="toolbarList--"]').childElementCount === 2);
await this.page1.screenshot(`${testName}`, `04-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -317,12 +317,14 @@ class CustomParameters {
await this.page2.waitForSelector(cpe.tools, ELEMENT_WAIT_TIME);
await this.page2.click(cpe.tools, true);
await this.page2.screenshot(`${testName}`, `04-page2-${testName}`);
if (await this.page2.page.evaluate(async () => await document.querySelectorAll('[aria-label="Tools"]')[0].parentElement.querySelector('[class^="toolbarList--"]').childElementCount === 7)) {
const resp = await this.page2.page.evaluate((toolsElement, toolbarListSelector) => {
return document.querySelectorAll(toolsElement)[0].parentElement.querySelector(toolbarListSelector).childElementCount === 2;
}, cpe.tools, cpe.toolbarListClass);
if (!resp) {
await this.page2.screenshot(`${testName}`, `05-page2-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page2.page.evaluate(async () => await document.querySelectorAll('[aria-label="Tools"]')[0].parentElement.querySelector('[class^="toolbarList--"]').childElementCount === 2);
await this.page2.screenshot(`${testName}`, `05-page2-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -335,18 +337,17 @@ class CustomParameters {
await this.page1.closeAudioModal();
await this.page1.waitForSelector(cpe.whiteboard, ELEMENT_WAIT_TIME);
await this.page1.screenshot(`${testName}`, `02-${testName}`);
const isHidden = await this.page1.page.$eval('[class="presentationTitle--1LT79g"]', elem => elem.offsetHeight == 0);
if (isHidden === false) {
const isHidden = await this.page1.page.$eval(cpe.presentationTitle, elem => elem.offsetHeight == 0);
if (isHidden !== true) {
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
} if (isHidden === true) {
}
await this.page1.screenshot(`${testName}`, `03-success-${testName}`);
const resp = isHidden;
this.page1.logger(testName, ' passed');
return resp === true;
}
}
async customStyleUrl(testName, args, meetingId, customParameter) {
await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator1' }, customParameter, testName);
@ -355,38 +356,36 @@ class CustomParameters {
await this.page1.closeAudioModal();
await this.page1.waitForSelector(cpe.whiteboard, ELEMENT_WAIT_TIME);
await this.page1.screenshot(`${testName}`, `02-${testName}`);
const isHidden = await this.page1.page.$eval('[class="presentationTitle--1LT79g"]', elem => elem.offsetHeight == 0);
if (isHidden === false) {
const isHidden = await this.page1.page.$eval(cpe.presentationTitle, elem => elem.offsetHeight == 0);
if (isHidden !== true) {
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
} if (isHidden === true) {
}
await this.page1.screenshot(`${testName}`, `03-success-${testName}`);
const resp = isHidden;
this.page1.logger(testName, ' passed');
return resp === true;
}
}
async autoSwapLayout(testName, args, meetingId, customParameter) {
await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator1' }, customParameter, testName);
await this.page1.startRecording(testName);
await this.page1.screenshot(`${testName}`, `01-${testName}`);
await this.page1.closeAudioModal();
await this.page1.waitForSelector(cpe.container, ELEMENT_WAIT_TIME);
await this.page1.waitForSelector(pe.actions, ELEMENT_WAIT_TIME);
await this.page1.screenshot(`${testName}`, `02-${testName}`);
const isNotHidden = await this.page1.page.$eval(cpe.restorePresentation, elem => elem.offsetHeight !== 0);
if (isNotHidden === false) {
if (isNotHidden !== true) {
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
} if (isNotHidden === true) {
}
await this.page1.screenshot(`${testName}`, `03-success-${testName}`);
const resp = isNotHidden;
this.page1.logger(testName, ' passed');
return resp === true;
}
}
async hidePresentation(testName, args, meetingId, customParameter) {
await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator1' }, customParameter, testName);
@ -395,12 +394,12 @@ class CustomParameters {
await this.page1.closeAudioModal();
await this.page1.waitForSelector(cpe.actions, ELEMENT_WAIT_TIME);
await this.page1.screenshot(`${testName}`, `02-${testName}`);
if (await this.page1.page.evaluate(util.countTestElements, cpe.defaultContent) === false) {
const resp = await this.page1.page.evaluate(checkElementLengthDifferentTo, cpe.defaultContent, 0);
if (!resp) {
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page1.page.evaluate(util.countTestElements, cpe.defaultContent) === true;
await this.page1.screenshot(`${testName}`, `03-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -413,12 +412,12 @@ class CustomParameters {
await this.page1.closeAudioModal();
await this.page1.waitForSelector(cpe.actions, ELEMENT_WAIT_TIME);
await this.page1.screenshot(`${testName}`, `02-${testName}`);
if (await this.page1.page.evaluate(util.countTestElements, cpe.notificationBar) === false) {
const resp = await this.page1.page.evaluate(checkElementLengthDifferentTo, cpe.notificationBar, 0);
if (!resp) {
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page1.page.evaluate(util.countTestElements, cpe.notificationBar) === true;
await this.page1.screenshot(`${testName}`, `03-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -436,25 +435,24 @@ class CustomParameters {
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
} if (notificationBarColor === colorToRGB) {
}
await this.page1.screenshot(`${testName}`, `03-success-${testName}`);
this.page1.logger(testName, ' passed');
return true;
}
}
async hideAndSwapPresentation(testName, args, meetingId, customParameter) {
await this.page1.init(args, meetingId, { ...params, fullName: 'Moderator1' }, customParameter, testName);
await this.page1.startRecording(testName);
await this.page1.screenshot(`${testName}`, `01-${testName}`);
await this.page1.closeAudioModal();
await this.page1.waitForSelector(cpe.container, ELEMENT_WAIT_TIME);
if (await this.page1.page.evaluate(util.countTestElements, cpe.restorePresentation) === false && await this.page1.page.evaluate(util.countTestElements, cpe.defaultContent) === false) {
await this.page1.waitForSelector(pe.actions, ELEMENT_WAIT_TIME);
const resp = await this.page1.page.evaluate(checkElementLengthDifferentTo, cpe.restorePresentation, 0) && await this.page1.page.evaluate(checkElementLengthDifferentTo, cpe.defaultContent, 0);
if (!resp) {
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page1.page.evaluate(util.countTestElements, cpe.restorePresentation) === true && await this.page1.page.evaluate(util.countTestElements, cpe.defaultContent) === true;
await this.page1.screenshot(`${testName}`, `03-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -465,13 +463,13 @@ class CustomParameters {
await this.page1.startRecording(testName);
await this.page1.screenshot(`${testName}`, `01-${testName}`);
await this.page1.closeAudioModal();
await this.page1.waitForSelector(cpe.container, ELEMENT_WAIT_TIME);
if (await this.page1.page.evaluate(util.countTestElements, cpe.chat) === true) {
await this.page1.waitForSelector(pe.actions, ELEMENT_WAIT_TIME);
const resp = await this.page1.page.evaluate(checkElementLengthEqualTo, cpe.chat, 0);
if (!resp) {
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page1.page.evaluate(util.countTestElements, cpe.chat) === false;
await this.page1.screenshot(`${testName}`, `03-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -488,7 +486,6 @@ class CustomParameters {
await this.page1.screenshot(`${testName}`, `02-page1-${testName}`);
await this.page2.closeAudioModal();
await this.page2.screenshot(`${testName}`, `02-page2-${testName}`);
await this.page1.waitForSelector(cpe.container, ELEMENT_WAIT_TIME);
await this.page2.waitForSelector(cpe.hidePresentation, ELEMENT_WAIT_TIME);
await this.page2.click(cpe.hidePresentation, true);
await this.page2.screenshot(`${testName}`, `03-page2-${testName}`);
@ -510,13 +507,15 @@ class CustomParameters {
const annotationCase = await util.annotation(this.page1);
await this.page1.screenshot(`${testName}`, `06-page1-${testName}`);
await this.page2.screenshot(`${testName}`, `07-page2-${testName}`);
if (zoomInCase === true && zoomOutCase === true && pollCase === true && previousSlideCase === true && nextSlideCase === true && annotationCase === true
&& await this.page2.page.evaluate(util.countTestElements, cpe.restorePresentation) === true) {
const test = await this.page2.page.evaluate(checkElementLengthDifferentTo, cpe.restorePresentation, 0);
const resp = (zoomInCase && zoomOutCase && pollCase && previousSlideCase && nextSlideCase && annotationCase && test);
if (resp) {
await this.page2.screenshot(`${testName}`, `08-page2-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
await this.page2.page.evaluate(util.countTestElements, cpe.restorePresentation) === false;
await this.page2.page.evaluate(checkElementLengthEqualTo, cpe.restorePresentation, 0);
await this.page2.screenshot(`${testName}`, `08-page2-success-${testName}`);
this.page1.logger(testName, ' passed');
return true;
@ -534,17 +533,17 @@ class CustomParameters {
await this.page2.screenshot(`${testName}`, `02-page2-${testName}`);
await this.page2.click(cpe.hidePresentation, true);
await this.page2.screenshot(`${testName}`, `03-page2-${testName}`);
const pollCase = await util.poll(this.page1, this.page2);
const pollCase = await util.poll(this.page1, this.page2) === true;
await this.page2.waitForSelector(ne.smallToastMsg, ELEMENT_WAIT_TIME);
await this.page1.screenshot(`${testName}`, `03-page1-${testName}`);
await this.page2.screenshot(`${testName}`, `04-page2-${testName}`);
if (pollCase === true
&& await this.page2.page.evaluate(util.countTestElements, cpe.restorePresentation) === true) {
const test = await this.page2.page.evaluate(checkElementLengthDifferentTo, cpe.restorePresentation, 0);
if (pollCase && test) {
await this.page2.screenshot(`${testName}`, `05-page2-fail-${testName}`);
await this.page1.logger(testName, ' failed');
return false;
}
await this.page2.page.evaluate(util.countTestElements, cpe.restorePresentation) === false;
await this.page2.screenshot(`${testName}`, `05-page2-success-${testName}`);
await this.page1.logger(testName, ' passed');
return true;
@ -555,12 +554,12 @@ class CustomParameters {
await this.page1.startRecording(testName);
await this.page1.closeAudioModal();
await this.page1.screenshot(`${testName}`, `01-${testName}`);
if (await this.page1.page.evaluate(util.getTestElement, cpe.recordingIndicator) === false) {
const resp = await this.page1.page.evaluate(checkElementLengthEqualTo, cpe.recordingIndicator, 0);
if (!resp) {
await this.page1.screenshot(`${testName}`, `02-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page1.page.evaluate(util.getTestElement, cpe.recordingIndicator) === true;
await this.page1.screenshot(`${testName}`, `02-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -574,12 +573,12 @@ class CustomParameters {
await this.page1.screenshot(`${testName}`, `02-${testName}`);
await this.page1.waitForSelector(cpe.shareWebcamButton, ELEMENT_WAIT_TIME);
await this.page1.click(cpe.shareWebcamButton, true);
if (await this.page1.page.evaluate(util.getTestElement, cpe.webcamSettingsModal) === false) {
const resp = await this.page1.page.evaluate(checkElementLengthEqualTo, cpe.webcamSettingsModal, 0);
if (!resp) {
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page1.page.evaluate(util.getTestElement, cpe.webcamSettingsModal) === true;
await this.page1.screenshot(`${testName}`, `03-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -593,7 +592,7 @@ class CustomParameters {
await this.page1.screenshot(`${testName}`, `02-${testName}`);
await this.page1.waitForSelector(we.joinVideo, ELEMENT_WAIT_TIME);
await this.page1.click(we.joinVideo, true);
const firstCheck = await this.page1.page.evaluate(util.getTestElement, cpe.webcamSettingsModal) === true;
const firstCheck = await this.page1.page.evaluate(checkElementLengthEqualTo, cpe.webcamSettingsModal, 0);
await this.page1.waitForSelector(we.leaveVideo, VIDEO_LOADING_WAIT_TIME);
await this.page1.click(we.leaveVideo, true);
await this.page1.waitForElementHandleToBeRemoved(we.webcamVideo), ELEMENT_WAIT_LONGER_TIME;
@ -605,7 +604,7 @@ class CustomParameters {
const videoPreviewTimeout = parseInt(parsedSettings.public.kurento.gUMTimeout);
await this.page1.waitForSelector(cpe.webcamVideoPreview, videoPreviewTimeout);
await this.page1.waitForSelector(cpe.startSharingWebcamButton, ELEMENT_WAIT_TIME);
const secondCheck = await this.page1.page.evaluate(util.getTestElement, cpe.webcamSettingsModal) === false;
const secondCheck = await this.page1.page.evaluate(checkElementLengthDifferentTo, cpe.webcamSettingsModal, 0);
await this.page1.click(cpe.startSharingWebcamButton, true);
await this.page1.waitForSelector(we.webcamConnecting, ELEMENT_WAIT_TIME);
@ -630,12 +629,12 @@ class CustomParameters {
await this.page1.waitForSelector(cpe.webcamMirroredVideoPreview, ELEMENT_WAIT_TIME);
await this.page1.waitForSelector(cpe.startSharingWebcamButton, ELEMENT_WAIT_TIME);
await this.page1.click(cpe.startSharingWebcamButton, true);
if (await this.page1.page.evaluate(util.getTestElement, cpe.webcamMirroredVideoContainer) === true) {
const resp = await this.page1.page.evaluate(checkElementLengthDifferentTo, cpe.webcamMirroredVideoContainer, 0);
if (!resp) {
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page1.page.evaluate(util.getTestElement, cpe.webcamMirroredVideoContainer) === false;
await this.page1.screenshot(`${testName}`, `03-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;
@ -648,12 +647,12 @@ class CustomParameters {
await this.page1.closeAudioModal();
await this.page1.screenshot(`${testName}`, `02-${testName}`);
await this.page1.waitForSelector(cpe.whiteboard, ELEMENT_WAIT_TIME);
if (await this.page1.page.evaluate(util.getTestElement, cpe.userslistContainer) === false) {
const resp = await this.page1.page.evaluate(checkElementLengthEqualTo, cpe.userslistContainer, 0);
if (!resp) {
await this.page1.screenshot(`${testName}`, `03-fail-${testName}`);
this.page1.logger(testName, ' failed');
return false;
}
const resp = await this.page1.page.evaluate(util.getTestElement, cpe.userslistContainer) === true;
await this.page1.screenshot(`${testName}`, `03-success-${testName}`);
this.page1.logger(testName, ' passed');
return resp === true;

View File

@ -1,5 +1,7 @@
exports.audioModal = 'div[aria-label="Join audio modal"]';
exports.audioOverlay = 'div[class^="ReactModal__Overlay"]';
exports.audioModalHeader = '[data-test="audioModalHeader"]';
exports.audioOptionsButtons = '[class^="audioOptions"] > button';
exports.whiteboard = 'svg[data-test="whiteboard"]';
exports.echoTestYesButton = 'button[aria-label="Echo is audible"]';
exports.echoTestNoButton = 'button[aria-label="Echo is inaudible"]';
@ -17,11 +19,12 @@ exports.stopWebcamButton = 'button[data-test="leaveVideo"]';
exports.webcamSettingsModal = 'div[aria-label="Webcam settings"]';
exports.startWebcamSharingConfirm = 'button[aria-label="Start sharing"]';
exports.multiUsersWhiteboard = 'button[aria-label="Turn multi-user whiteboard on"]';
exports.toolbarListClass = '[class^="toolbarList--"]';
exports.tools = 'button[aria-label="Tools"]';
exports.actions = 'button[aria-label="Actions"]';
exports.hidePresentation = 'button[data-test="hidePresentationButton"]';
exports.restorePresentation = 'button[data-test="restorePresentationButton"]';
exports.container = 'div[id="container"]';
exports.presentationTitle = '[class^="presentationTitle--"]';
exports.defaultContent = 'div[class^="defaultContent--"]';
exports.notificationBar = 'div[class^="notificationsBar--"]';
exports.chat = 'section[aria-label="Chat"]';

View File

@ -1,28 +1,31 @@
const path = require('path');
const { ELEMENT_WAIT_TIME, ELEMENT_WAIT_LONGER_TIME } = require('../core/constants');
const ne = require('../notifications/elements');
const pe = require('../presentation/elements');
const ce = require('../customparameters/elements');
const we = require('../whiteboard/elements');
const poe = require('../polling/elemens');
const e = require('../core/elements');
const { ELEMENT_WAIT_TIME, ELEMENT_WAIT_LONGER_TIME } = require('../core/constants');
const { checkElementLengthEqualTo, checkElementLengthDifferentTo, checkElementText } = require('../core/util');
async function autoJoinTest(test) {
const resp = await test.page.evaluate(async () => {
const rep = await document.querySelectorAll('div[aria-label="Join audio modal"]').length === 0;
return rep !== false;
});
return resp;
try {
const resp = await test.page.evaluate(checkElementLengthEqualTo, e.audioDialog, 0);
return resp === true;
} catch (err) {
console.log(err);
return false;
}
}
async function listenOnlyMode(test) {
// maybe not used
try {
const resp = await test.page.evaluate(async () => {
await document.querySelectorAll('div[class^="connecting--"]')[0];
const audibleButton = await document.querySelectorAll('button[aria-label="Echo is audible"]').length !== 0;
return audibleButton !== false;
});
return resp;
const resp = await test.page.evaluate(async (connectionSelector, echoYes) => {
await document.querySelectorAll(connectionSelector)[0];
return document.querySelectorAll(echoYes).length !== 0;
}, e.connectingStatus, e.echoYes);
return resp === true;
} catch (err) {
console.log(err);
}
@ -30,40 +33,31 @@ async function listenOnlyMode(test) {
async function forceListenOnly(test) {
try {
const resp = await test.page.evaluate(async () => {
await document.querySelectorAll('div[class^="connecting--"]')[0];
if (await document.querySelectorAll('button[aria-label="Echo is audible"]').length > 0) {
return false;
}
const audibleNotification = await document.querySelectorAll('div[class^="toastContainer--"]')[0].innerText === 'You have joined the audio conference';
return audibleNotification !== false;
});
return resp;
const checkEchoYes = await test.page.evaluate(checkElementLengthEqualTo, e.echoYes, 0);
if (!checkEchoYes) return false;
const resp = await test.page.evaluate(checkElementText, ce.toastContainer, 'You have joined the audio conference');
return resp === true;
} catch (err) {
console.log(err);
return false
}
}
async function skipCheck(test) {
// maybe not used
try {
await test.waitForSelector(ce.toastContainer, ELEMENT_WAIT_TIME);
const resp1 = await test.page.evaluate(async () => await document.querySelectorAll('div[class^="toastContainer--"]').length !== 0);
const resp1 = await test.page.evaluate(checkElementLengthDifferentTo, e.toastContainer, 0);
await test.waitForSelector(ce.muteBtn, ELEMENT_WAIT_TIME);
const resp2 = await test.page.evaluate(async () => await document.querySelectorAll('button[aria-label="Mute"]').length !== 0);
const resp2 = await test.page.evaluate(checkElementLengthDifferentTo, ce.muteBtn, 0);
return resp1 === true && resp2 === true;
} catch (err) {
console.log(err);
return false;
}
}
async function countTestElements(element) {
return document.querySelectorAll(element).length !== 0;
}
async function getTestElement(element) {
return document.querySelectorAll(element).length === 0;
}
function hexToRgb(hex) {
const bigint = parseInt(hex, 16);
const r = (bigint >> 16) & 255;
@ -74,11 +68,11 @@ function hexToRgb(hex) {
async function zoomIn(test) {
try {
await test.page.evaluate(() => {
await test.page.evaluate((zoomIn) => {
setInterval(() => {
document.querySelector('button[aria-label="Zoom in"]').scrollBy(0, 10);
document.querySelector(zoomIn).scrollBy(0, 10);
}, 100);
});
}, e.zoomIn);
return true;
} catch (err) {
console.log(err);
@ -88,11 +82,12 @@ async function zoomIn(test) {
async function zoomOut(test) {
try {
await test.page.evaluate(() => {
await test.page.evaluate((zoomIn) => {
setInterval(() => {
document.querySelector('button[aria-label="Zoom in"]').scrollBy(10, 0);
document.querySelector(zoomIn).scrollBy(10, 0);
}, 100);
}); return true;
}, e.zoomIn);
return true;
} catch (err) {
console.log(err);
return false;
@ -102,7 +97,7 @@ async function zoomOut(test) {
async function poll(page1, page2) {
try {
await page1.page.waitForSelector(ce.whiteboard, { visible: true, timeout: ELEMENT_WAIT_LONGER_TIME });
await page1.page.evaluate(async () => await document.querySelectorAll('button[aria-label="Actions"]')[0].click());
await page1.click(e.actions);
await page1.waitForSelector(ne.polling, ELEMENT_WAIT_TIME);
await page1.click(ne.polling, true);
await page1.waitForSelector(ne.pollYesNoAbstentionBtn, ELEMENT_WAIT_TIME);
@ -149,8 +144,10 @@ async function annotation(test) {
await test.waitForSelector(we.pencil, ELEMENT_WAIT_TIME);
await test.click(we.pencil, true);
await test.click(ce.whiteboard, true);
const annoted = await test.page.evaluate(async () => await document.querySelectorAll('[data-test="whiteboard"] > g > g')[1].innerHTML !== '');
return annoted;
const annoted = await test.page.evaluate((whiteboard) => {
return document.querySelectorAll(`${whiteboard} > g > g`)[1].innerHTML !== '';
}, e.whiteboard);
return annoted === true;
}
async function presetationUpload(test) {
@ -159,7 +156,7 @@ async function presetationUpload(test) {
await test.click(ce.actions, true);
await test.waitForSelector(pe.uploadPresentation, ELEMENT_WAIT_TIME);
await test.click(pe.uploadPresentation, true);
const elementHandle = await test.page.$('input[type=file]');
const elementHandle = await test.page.$(pe.fileUpload);
await elementHandle.uploadFile(path.join(__dirname, `../media/${e.pdfFileName}.pdf`));
await test.click(ce.confirmBtn, true);
return true;
@ -187,8 +184,6 @@ exports.nextSlide = nextSlide;
exports.annotation = annotation;
exports.presetationUpload = presetationUpload;
exports.hexToRgb = hexToRgb;
exports.getTestElement = getTestElement;
exports.countTestElements = countTestElements;
exports.autoJoinTest = autoJoinTest;
exports.listenOnlyMode = listenOnlyMode;
exports.forceListenOnly = forceListenOnly;

View File

@ -1,9 +1,9 @@
const Page = require('../core/page');
const e = require('../core/elements');
const util = require('../customparameters/util');
const { exec } = require("child_process");
const { CLIENT_RECONNECTION_TIMEOUT } = require('../core/constants'); // core constants (Timeouts vars imported)
const { sleep } = require('../core/helper');
const e = require('../core/elements');
const { checkElementLengthDifferentTo } = require('../core/util');
class Trigger extends Page {
constructor() {
@ -33,7 +33,7 @@ class Trigger extends Page {
await sleep(3000);
await this.screenshot(`${testName}`, `04-after-meteor-reconnection-[${this.meetingId}]`);
const findUnauthorized = await this.page.evaluate(util.countTestElements, e.unauthorized) === true;
const findUnauthorized = await this.page.evaluate(checkElementLengthDifferentTo, e.unauthorized, 0) === true;
await this.logger('Check if Unauthorized message appears => ', findUnauthorized);
return meteorStatusConfirm && getAudioButton && findUnauthorized;
} catch (err) {
@ -85,12 +85,12 @@ class Trigger extends Page {
await this.page.reload();
await this.closeAudioModal();
const getAudioButton = await this.page.evaluate(() =>
document.querySelectorAll('button[aria-label="Join audio"]')[0]
.getAttribute('aria-disabled') === "true");
const getAudioButton = await this.page.evaluate((joinAudioSelector) => {
return document.querySelectorAll(joinAudioSelector)[0].getAttribute('aria-disabled') === "true";
}, e.joinAudio)
await this.logger('Check if Connections Buttons are disabled => ', getAudioButton);
await sleep(3000);
const findUnauthorized = await this.page.evaluate(util.countTestElements, e.unauthorized) === true;
const findUnauthorized = await this.page.evaluate(checkElementLengthDifferentTo, e.unauthorized, 0) === true;
await this.logger('Check if Unauthorized message appears => ', findUnauthorized);
return meteorStatusConfirm && getAudioButton && findUnauthorized;
} catch (err) {

View File

@ -12,8 +12,13 @@ class SharedNotes extends Create {
}
async close() {
try {
await this.page1.close();
await this.page2.close();
} catch (e) {
await this.page1.logger(e);
}
}
}
module.exports = exports = SharedNotes;

View File

@ -1,19 +1,19 @@
const { ELEMENT_WAIT_TIME, ELEMENT_WAIT_LONGER_TIME } = require('../core/constants');
const se = require('./elements');
const { getElementLength } = require('../core/util');
async function startSharedNotes(test) {
try {
await test.waitForSelector(se.sharedNotes, ELEMENT_WAIT_TIME);
await test.click(se.sharedNotes, true);
await test.waitForSelector(se.hideNoteLabel, ELEMENT_WAIT_LONGER_TIME);
const resp = await test.page.evaluate(getTestElement, se.etherpad);
const resp = await test.page.evaluate(getElementLength, se.etherpad) >= 1;
await test.waitForSelector(se.etherpad, ELEMENT_WAIT_TIME);
return resp;
return resp === true;
} catch (e) {
await test.logger(e);
return false;
}
}
async function getTestElement(element) {
const response = await document.querySelectorAll(element).length >= 1;
return response;
}
exports.getTestElement = getTestElement;
exports.startSharedNotes = startSharedNotes;

View File

@ -9,6 +9,7 @@ const ne = require('./elements');
const pe = require('../presentation/elements');
const we = require('../whiteboard/elements');
const { ELEMENT_WAIT_TIME, UPLOAD_PDF_WAIT_TIME } = require('../core/constants');
const { checkElementTextIncludes } = require('../core/util');
class Notifications extends MultiUsers {
constructor() {
@ -127,8 +128,8 @@ class Notifications extends MultiUsers {
await this.initUser4(testName);
await this.page4.closeAudioModal();
await this.page3.waitForSelector(ne.smallToastMsg, ELEMENT_WAIT_TIME);
await this.page3.page.waitForFunction(
'document.querySelector("body").innerText.includes("User joined the session")',
await this.page3.page.waitForFunction(checkElementTextIncludes, {},
'body', 'User joined the session'
);
await this.page3.screenshot(`${testName}`, `04-page03-user-join-toast-${testName}`);
return true;
@ -151,20 +152,20 @@ class Notifications extends MultiUsers {
await this.page3.waitForSelector(pe.fileUpload, ELEMENT_WAIT_TIME);
const fileUpload = await this.page3.page.$(pe.fileUpload);
await fileUpload.uploadFile(path.join(__dirname, `../media/${e.pdfFileName}.pdf`));
await this.page3.page.waitForFunction(
'document.querySelector("body").innerText.includes("To be uploaded ...")',
await this.page3.page.waitForFunction(checkElementTextIncludes, {},
'body', 'To be uploaded ...'
);
await this.page3.waitForSelector(pe.upload, ELEMENT_WAIT_TIME);
await this.page3.click(pe.upload, true);
await this.page3.page.waitForFunction(
'document.querySelector("body").innerText.includes("Converting file")',
await this.page3.page.waitForFunction(checkElementTextIncludes, {},
'body', 'Converting file'
);
await this.page3.screenshot(`${testName}`, `04-page03-file-uploaded-and-ready-${testName}`);
await this.page3.waitForSelector(ne.smallToastMsg, UPLOAD_PDF_WAIT_TIME);
await this.page3.waitForSelector(we.whiteboard, ELEMENT_WAIT_TIME);
await this.page3.screenshot(`${testName}`, `05-page03-presentation-changed-${testName}`);
await this.page3.page.waitForFunction(
'document.querySelector("body").innerText.includes("Current presentation")',
await this.page3.page.waitForFunction(checkElementTextIncludes, {},
'body', 'Current presentation'
);
await this.page3.screenshot(`${testName}`, `06-page03-presentation-change-toast-${testName}`);
return true;
@ -186,7 +187,7 @@ class Notifications extends MultiUsers {
await this.page3.waitForSelector(ne.smallToastMsg, ELEMENT_WAIT_TIME);
const resp = await util.getLastToastValue(this.page3);
await this.page3.screenshot(`${testName}`, `04-page03-poll-toast-${testName}`);
return resp === true;
return resp;
} catch (err) {
await this.page3.logger(err);
return false;
@ -202,7 +203,7 @@ class Notifications extends MultiUsers {
await this.page3.screenshot(`${testName}`, `02-page03-joined-microphone-${testName}`);
const resp = await util.getLastToastValue(this.page3) === ne.joinAudioToast;
await this.page3.screenshot(`${testName}`, `03-page03-audio-toast-${testName}`);
return resp === true;
return resp;
} catch (err) {
await this.page3.logger(err);
return false;
@ -220,7 +221,7 @@ class Notifications extends MultiUsers {
await this.page3.screenshot(`${testName}`, `03-page03-screenshare-started-${testName}`);
const response = await util.getLastToastValue(this.page3);
await this.page3.screenshot(`${testName}`, `04-page03-screenshare-toast-${testName}`);
return response === true;
return response;
} catch (err) {
await this.page3.logger(err);
return false;

View File

@ -3,28 +3,25 @@ const ule = require('../user/elements');
const ce = require('../chat/elements');
const e = require('../core/elements');
const { ELEMENT_WAIT_TIME } = require('../core/constants');
async function clickTestElement(element) {
await document.querySelectorAll(element)[0].click();
}
const { clickElement, getElementText, checkElement, checkElementLengthEqualTo } = require('../core/util');
async function popupMenu(test) {
await test.page.evaluate(clickTestElement, e.options);
await test.page.evaluate(clickTestElement, ne.settings);
await test.page.evaluate(clickElement, e.options);
await test.page.evaluate(clickElement, ne.settings);
}
async function enableChatPopup(test) {
await test.waitForSelector(ne.notificationsTab, ELEMENT_WAIT_TIME);
await test.page.evaluate(clickTestElement, ne.notificationsTab);
await test.page.evaluate(clickElement, ne.notificationsTab);
await test.waitForSelector(ne.chatPushAlerts, ELEMENT_WAIT_TIME);
await test.page.evaluate(clickTestElement, ne.chatPushAlerts);
await test.page.evaluate(clickElement, ne.chatPushAlerts);
}
async function enableUserJoinPopup(test) {
await test.waitForSelector(ne.notificationsTab, ELEMENT_WAIT_TIME);
await test.page.evaluate(clickTestElement, ne.notificationsTab);
await test.page.evaluate(clickElement, ne.notificationsTab);
await test.waitForSelector(ne.userJoinPushAlerts, ELEMENT_WAIT_TIME);
await test.page.evaluate(clickTestElement, ne.userJoinPushAlerts);
await test.page.evaluate(clickElement, ne.userJoinPushAlerts);
}
async function saveSettings(page) {
@ -34,44 +31,26 @@ async function saveSettings(page) {
async function waitForToast(test) {
await test.waitForSelector(ne.smallToastMsg, ELEMENT_WAIT_TIME);
const resp = await test.page.evaluate(getTestElement, ne.smallToastMsg) !== null;
const resp = await test.page.evaluate(checkElement, ne.smallToastMsg, 1);
return resp;
}
async function getLastToastValue(test) {
await test.waitForSelector(ne.smallToastMsg, ELEMENT_WAIT_TIME);
const toast = test.page.evaluate(async (toastMsgSelector) => {
const lastToast = await document.querySelectorAll(toastMsgSelector)[0].innerText;
return lastToast;
}, ne.smallToastMsg);
const toast = test.page.evaluate(getElementText, ne.smallToastMsg);
return toast;
}
async function getOtherToastValue(test) {
await test.waitForSelector(ne.smallToastMsg, ELEMENT_WAIT_TIME);
const toast = test.page.evaluate(async (toastMsgSelector) => {
const lastToast = await document.querySelectorAll(toastMsgSelector)[1].innerText;
return lastToast;
}, ne.smallToastMsg);
const toast = test.page.evaluate(getElementText, ne.smallToastMsg, 1);
return toast;
}
async function getTestElement(element) {
await document.querySelectorAll(element)[1];
}
async function clickOnElement(element) {
await document.querySelectorAll(element)[0].click();
}
async function clickThePrivateChatButton(element) {
await document.querySelectorAll(element)[0].click();
}
async function publicChatMessageToast(page1, page2) {
// Open private Chat with the other User
await page1.page.evaluate(clickOnElement, ule.userListItem);
await page1.page.evaluate(clickThePrivateChatButton, ce.activeChat);
await page1.page.evaluate(clickElement, ule.userListItem);
await page1.page.evaluate(clickElement, ce.activeChat);
// send a public message
await page2.page.type(ce.publicChat, ce.publicMessage1);
await page2.click(ce.sendButton, true);
@ -80,13 +59,13 @@ async function publicChatMessageToast(page1, page2) {
async function privateChatMessageToast(page2) {
// Open private Chat with the other User
await page2.page.evaluate(clickOnElement, ule.userListItem);
await page2.page.evaluate(clickThePrivateChatButton, ce.activeChat);
await page2.page.evaluate(clickElement, ule.userListItem);
await page2.page.evaluate(clickElement, ce.activeChat);
// wait for the private chat to be ready
await page2.page.waitForFunction(
(chatSelector) => document.querySelectorAll(chatSelector).length == 2,
checkElementLengthEqualTo,
{ timeout: ELEMENT_WAIT_TIME },
ce.chatButton
ce.chatButton, 2
);
// send a private message
await page2.page.type(ce.privateChat, ce.message1);
@ -100,27 +79,20 @@ async function uploadFileMenu(test) {
await test.click(ne.uploadPresentation);
}
async function getFileItemStatus(element, value) {
document.querySelectorAll(element)[1].innerText.includes(value);
}
async function startPoll(test) {
await test.waitForSelector(e.actions);
await test.click(e.actions);
await test.waitForSelector(ne.polling);
await test.click(ne.polling);
await test.waitForSelector(ne.hidePollDesc, ELEMENT_WAIT_TIME);
await test.waitForSelector(ne.polling, ELEMENT_WAIT_TIME);
await test.page.evaluate(clickOnElement, ne.polling);
await test.page.evaluate(clickElement, ne.polling);
await test.waitForSelector(ne.pollYesNoAbstentionBtn, ELEMENT_WAIT_TIME);
await test.click(ne.pollYesNoAbstentionBtn, true);
await test.waitForSelector(ne.startPoll, ELEMENT_WAIT_TIME);
await test.click(ne.startPoll, true);
await test.waitForSelector(ne.publishLabel, ELEMENT_WAIT_TIME);
await test.page.evaluate(clickOnElement, ne.publishLabel);
await test.page.evaluate(clickElement, ne.publishLabel);
}
exports.getFileItemStatus = getFileItemStatus;
exports.privateChatMessageToast = privateChatMessageToast;
exports.publicChatMessageToast = publicChatMessageToast;
exports.enableUserJoinPopup = enableUserJoinPopup;
@ -128,10 +100,7 @@ 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

@ -5289,9 +5289,9 @@
}
},
"ws": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA=="
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.4.tgz",
"integrity": "sha512-zP9z6GXm6zC27YtspwH99T3qTG7bBFv2VIkeHstMLrLlDJuzA7tQ5ls3OJ1hOGGCzTQPniNJoHXIAOS0Jljohg=="
},
"xml-name-validator": {
"version": "3.0.0",

View File

@ -1,7 +1,8 @@
exports.pollingContainer = 'div[data-test="pollingContainer"]';
exports.pollQuestionArea = 'textarea[data-test="pollQuestionArea"]';
exports.pollQuestion = 'Are we good ?';
exports.responseTypes = 'div[data-test="responseTypesLabel"]';
exports.responseTypes = 'div[data-test="responseTypes"]';
exports.responseTypesLabel = 'div[data-test="responseTypesLabel"]';
exports.responseChoices = 'div[data-test="responseChoices"]';
exports.addItem = 'button[data-test="addItem"]';
exports.pollOptionItem = 'input[data-test="pollOptionItem"]';

View File

@ -1,6 +1,7 @@
const Page = require('../core/page');
const e = require('../core/elements');
const utilNotification = require('../notifications/util');
const { ELEMENT_WAIT_TIME, VIDEO_LOADING_WAIT_TIME } = require('../core/constants'); // core constants (Timeouts vars imported)
const { checkElementLengthEqualTo } = require('../core/util');
class Polling extends Page {
constructor() {
@ -12,7 +13,7 @@ class Polling extends Page {
await utilNotification.startPoll(this);
await this.screenshot(`${testName}`, `01-before-chat-message-send-[${this.meetingId}]`);
const resp = this.page.evaluate(() => document.querySelectorAll('[data-test="pollMenuButton"]').length === 1);
const resp = this.page.evaluate(checkElementLengthEqualTo, e.pollMenuButton, 1);
return resp;
} catch (err) {
await this.logger(err);

View File

@ -2,6 +2,7 @@ const Page = require('../core/page');
const e = require('./elements');
const we = require('../whiteboard/elements');
const { ELEMENT_WAIT_LONGER_TIME, ELEMENT_WAIT_TIME } = require('../core/constants');
const util = require('./util');
class Slide extends Page {
constructor() {
@ -13,21 +14,21 @@ class Slide extends Page {
await this.waitForSelector(we.whiteboard, ELEMENT_WAIT_LONGER_TIME);
await this.waitForSelector(e.presentationToolbarWrapper, ELEMENT_WAIT_TIME);
const svg0 = await this.page.evaluate(async () => await document.querySelector('svg g g g').outerHTML.indexOf('/svg/1') !== -1);
const svg0 = await this.page.evaluate(util.checkSvgIndex, '/svg/1');
await this.waitForSelector(e.nextSlide, ELEMENT_WAIT_TIME);
await this.click(e.nextSlide, true);
await this.waitForSelector(we.whiteboard, ELEMENT_WAIT_TIME);
await this.page.waitFor(1000);
await this.page.waitForTimeout(1000);
const svg1 = await this.page.evaluate(async () => await document.querySelector('svg g g g').outerHTML.indexOf('/svg/2') !== -1);
const svg1 = await this.page.evaluate(util.checkSvgIndex, '/svg/2');
await this.waitForSelector(e.prevSlide, ELEMENT_WAIT_TIME);
await this.click(e.prevSlide, true);
await this.waitForSelector(we.whiteboard, ELEMENT_WAIT_TIME);
await this.page.waitFor(1000);
await this.page.waitForTimeout(1000);
const svg2 = await this.page.evaluate(async () => await document.querySelector('svg g g g').outerHTML.indexOf('/svg/1') !== -1);
const svg2 = await this.page.evaluate(util.checkSvgIndex, '/svg/1');
return svg0 === true && svg1 === true && svg2 === true;
} catch (err) {

View File

@ -3,6 +3,8 @@ const e = require('./elements');
const we = require('../whiteboard/elements');
const ce = require('../core/elements');
const { ELEMENT_WAIT_TIME, ELEMENT_WAIT_LONGER_TIME } = require('../core/constants');
const { checkElementTextIncludes } = require('../core/util');
const util = require('./util');
class Upload extends Page {
constructor() {
@ -14,7 +16,7 @@ class Upload extends Page {
await this.waitForSelector(we.whiteboard, ELEMENT_WAIT_LONGER_TIME);
await this.waitForSelector(e.skipSlide, ELEMENT_WAIT_TIME);
const slides0 = await this.page.evaluate(async () => await document.querySelector('svg g g g').outerHTML);
const slides0 = await this.page.evaluate(util.getSvgOuterHtml);
await this.click(ce.actions, true);
await this.click(e.uploadPresentation, true);
@ -24,19 +26,19 @@ class Upload extends Page {
await this.waitForSelector(e.fileUpload, ELEMENT_WAIT_TIME);
const fileUpload = await this.page.$(e.fileUpload);
await fileUpload.uploadFile(`${__dirname}/upload-test.png`);
await this.page.waitForFunction(
'document.querySelector("body").innerText.includes("To be uploaded ...")',
await this.page.waitForFunction(checkElementTextIncludes, {},
'body', 'To be uploaded ...'
);
await this.page.waitForSelector(e.upload, ELEMENT_WAIT_TIME);
await this.page.click(e.upload, true);
await this.logger('\nWaiting for the new presentation to upload...');
await this.page.waitForFunction(
'document.querySelector("body").innerText.includes("Converting file")',
await this.page.waitForFunction(checkElementTextIncludes, {},
'body', 'Converting file'
);
await this.logger('\nPresentation uploaded!');
await this.page.waitForFunction(
'document.querySelector("body").innerText.includes("Current presentation")',
await this.page.waitForFunction(checkElementTextIncludes, {},
'body', 'Current presentation'
);
await this.screenshot(`${testName}`, `02-after-presentation-upload-[${testName}]`);
@ -53,12 +55,6 @@ class Upload extends Page {
return false;
}
}
async getTestElements() {
const svg = await this.page.evaluate(async () => await document.querySelector('svg g g g').outerHTML);
await this.logger(svg);
return svg;
}
}
module.exports = exports = Upload;

View File

@ -0,0 +1,10 @@
function checkSvgIndex (element) {
return document.querySelector('svg g g g').outerHTML.indexOf(element) !== -1;
}
function getSvgOuterHtml() {
return document.querySelector('svg g g g').outerHTML;
}
exports.checkSvgIndex = checkSvgIndex;
exports.getSvgOuterHtml = getSvgOuterHtml;

View File

@ -3,6 +3,7 @@ const util = require('./util');
const e = require('../core/elements');
const { ELEMENT_WAIT_TIME, VIDEO_LOADING_WAIT_TIME } = require('../core/constants');
const { sleep } = require('../core/helper');
const { checkElementLengthEqualTo } = require('../core/util');
class ShareScreen extends Page {
constructor() {
@ -28,7 +29,7 @@ class ShareScreen extends Page {
await this.init(args, undefined, undefined, undefined, testName, undefined, deviceX);
await this.startRecording(testName);
await this.closeAudioModal();
const screenshareBtn = await this.page.evaluate(() => document.querySelectorAll('button[aria-label="Share your screen"]').length === 0) === true;
const screenshareBtn = await this.page.evaluate(checkElementLengthEqualTo, e.screenShare, 1);
return screenshareBtn;
} catch (err) {
await this.logger(err);

View File

@ -1,15 +1,12 @@
const { ELEMENT_WAIT_TIME, VIDEO_LOADING_WAIT_TIME } = require('../core/constants');
const e = require('../core/elements');
const { checkElement } = require('../core/util');
async function startScreenshare(test) {
await test.waitForSelector(e.screenShare, ELEMENT_WAIT_TIME);
await test.click(e.screenShare, true);
}
async function getTestElement(element) {
(await document.querySelectorAll(element)[0]) !== null;
}
async function waitForScreenshareContainer(test) {
await test.waitForSelector(e.screenshareConnecting, ELEMENT_WAIT_TIME);
await test.waitForSelector(e.screenShareVideo, VIDEO_LOADING_WAIT_TIME);
@ -17,21 +14,16 @@ async function waitForScreenshareContainer(test) {
async function getScreenShareContainer(test) {
await test.waitForSelector(e.screenShareVideo, VIDEO_LOADING_WAIT_TIME);
const screenShareContainer = await test.page.evaluate(getTestElement, e.screenShareVideo);
const response = screenShareContainer !== null;
return response;
return test.page.evaluate(checkElement, e.screenShareVideo);
}
async function getScreenShareBreakoutContainer(test) {
await test.waitForSelector(e.screenshareConnecting, { timeout: VIDEO_LOADING_WAIT_TIME });
await test.waitForSelector(e.screenShareVideo, { timeout: VIDEO_LOADING_WAIT_TIME });
const screenShareContainer = await test.evaluate(getTestElement, e.screenShareVideo);
const response = screenShareContainer !== null;
return response;
return test.evaluate(checkElement, e.screenShareVideo);
}
exports.getScreenShareBreakoutContainer = getScreenShareBreakoutContainer;
exports.getScreenShareContainer = getScreenShareContainer;
exports.getTestElement = getTestElement;
exports.startScreenshare = startScreenshare;
exports.waitForScreenshareContainer = waitForScreenshareContainer;

View File

@ -1,10 +1,11 @@
const Page = require('../core/page');
const { checkIncludeClass, checkUniqueElement } = require('./util');
const c = require('../core/constants');
const params = require('../params');
const c = require('../core/constants');
const ne = require('../notifications/elements');
const ue = require('../user/elements');
const e = require('../core/elements');
const util = require('./util');
const { checkElementLengthEqualTo } = require('../core/util');
class Stress extends Page {
constructor() {
@ -19,9 +20,9 @@ class Stress extends Page {
await this.init(Page.getArgs(), undefined, { ...params, fullName: `Moderator-${i}` }, undefined, testName);
await this.closeAudioModal();
await this.page.waitForSelector(ue.statusIcon, { timeout: c.ELEMENT_WAIT_TIME });
const hasPresenterClass = await this.page.evaluate(checkIncludeClass, ue.statusIcon, e.presenterClassName);
const hasPresenterClass = await this.page.evaluate(util.checkIncludeClass, ue.statusIcon, e.presenterClassName);
await this.click(e.actions);
const canStartPoll = await this.page.evaluate(checkUniqueElement, ne.polling);
const canStartPoll = await this.page.evaluate(checkElementLengthEqualTo, ne.polling, 1);
if (!hasPresenterClass || !canStartPoll) {
failureCount++;
await this.screenshot(`${testName}`, `loop-${i}-failure-${testName}`);

View File

@ -1,9 +1,5 @@
function checkIncludeClass(selector, className) {
return document.querySelectorAll(selector)[0]?.className.includes(className);
}
function checkUniqueElement(selector) {
return document.querySelectorAll(selector).length === 1;
}
exports.checkIncludeClass = checkIncludeClass;
exports.checkUniqueElement = checkUniqueElement;

View File

@ -32,3 +32,5 @@ exports.chatButton = '[accesskey="P"]';
exports.chatPanel = 'section[data-test="chatPanel"]';
exports.userListButton = '[accesskey="U"]';
exports.userListPanel = 'div[data-test="userListPanel"]';
exports.multiWhiteboardTool = 'span[data-test="multiWhiteboardTool"]'
exports.connectionStatusBtn = 'button[data-test="connectionStatusButton"]';

View File

@ -2,15 +2,16 @@ const Page = require('../core/page');
const params = require('../params');
const util = require('../chat/util');
const utilUser = require('./util');
const utilCustomParams = require('../customparameters/util');
const pe = require('../core/elements');
const ne = require('../notifications/elements');
const ple = require('../polling/elemens');
const we = require('../whiteboard/elements');
const ue = require('./elements');
const ce = require('../chat/elements');
const cu = require('../customparameters/elements');
const { ELEMENT_WAIT_TIME } = require('../core/constants');
const { sleep } = require('../core/helper');
const { getElementLength, checkElementLengthEqualTo, checkElementLengthDifferentTo } = require('../core/util');
class MultiUsers {
constructor() {
@ -32,8 +33,8 @@ class MultiUsers {
// Run the test for the page
async checkForOtherUser() {
const firstCheck = await this.page1.page.evaluate(() => document.querySelectorAll('[data-test="userListItem"]').length > 0);
const secondCheck = await this.page2.page.evaluate(() => document.querySelectorAll('[data-test="userListItem"]').length > 0);
const firstCheck = await this.page1.page.evaluate(getElementLength, ue.userListItem) > 0;
const secondCheck = await this.page1.page.evaluate(getElementLength, ue.userListItem) > 0;
return {
firstCheck,
secondCheck,
@ -42,9 +43,9 @@ class MultiUsers {
async multiUsersPublicChat() {
try {
const chat0 = await this.page1.page.evaluate(() => document.querySelectorAll('p[data-test="chatUserMessageText"]').length);
const chat0 = await this.page1.page.evaluate(getElementLength, ce.chatUserMessageText);
await util.sendPublicChatMessage(this.page1, this.page2);
const chat1 = await this.page1.page.evaluate(() => document.querySelectorAll('p[data-test="chatUserMessageText"]').length);
const chat1 = await this.page1.page.evaluate(getElementLength, ce.chatUserMessageText);
return chat0 !== chat1;
} catch (err) {
await this.page1.logger(err);
@ -55,10 +56,10 @@ class MultiUsers {
async multiUsersPrivateChat() {
try {
await util.openPrivateChatMessage(this.page1, this.page2);
const chat0 = await this.page1.page.evaluate(() => document.querySelectorAll('p[data-test="chatUserMessageText"]').length);
const chat0 = await this.page1.page.evaluate(getElementLength, ce.chatUserMessageText);
await util.sendPrivateChatMessage(this.page1, this.page2);
await sleep(2000);
const chat1 = await this.page1.page.evaluate(() => document.querySelectorAll('p[data-test="chatUserMessageText"]').length);
const chat1 = await this.page1.page.evaluate(getElementLength, ce.chatUserMessageText);
return chat0 !== chat1;
} catch (err) {
await this.page1.logger(err);
@ -90,15 +91,15 @@ class MultiUsers {
await this.page1.page.focus(ple.pollQuestionArea);
await this.page1.page.keyboard.type(ple.pollQuestion);
const chosenRandomNb = await this.page1.page.evaluate(() => {
const responseTypesDiv = document.querySelector('div[data-test="responseTypes"]');
const chosenRandomNb = await this.page1.page.evaluate((responseTypes) => {
const responseTypesDiv = document.querySelector(responseTypes);
const buttons = responseTypesDiv.querySelectorAll('button');
const countButtons = buttons.length;
const randomNb = Math.floor(Math.random() * countButtons) + 1;
const chosenRandomNb = randomNb - 1;
responseTypesDiv.querySelectorAll('button')[chosenRandomNb].click();
return chosenRandomNb;
});
}, ple.responseTypes);
const customs = {
0: ple.uncertain,
@ -158,7 +159,7 @@ class MultiUsers {
await this.page1.waitForSelector(ple.publishLabel, ELEMENT_WAIT_TIME);
await this.page1.click(ple.publishLabel, true);
await this.page1.waitForSelector(ple.restartPoll, ELEMENT_WAIT_TIME);
const receivedAnswerFound = await this.page1.page.evaluate(utilCustomParams.countTestElements, ple.receivedAnswer);
const receivedAnswerFound = await this.page1.page.evaluate(checkElementLengthDifferentTo, ple.receivedAnswer, 0);
return receivedAnswerFound;
} catch (err) {
await this.page1.logger(err);
@ -175,7 +176,9 @@ class MultiUsers {
await this.page1.clickNItem(we.userListItem, true, 1);
await this.page1.clickNItem(we.changeWhiteboardAccess, true, 1);
await sleep(2000);
const resp = await this.page1.page.evaluate(async () => await document.querySelector('[data-test="multiWhiteboardTool"]').children[0].innerText === '1');
const resp = await this.page1.page.evaluate((multiWhiteboardTool) => {
return document.querySelector(multiWhiteboardTool).children[0].innerText === '1';
}, ue.multiWhiteboardTool);
return resp === true;
} catch (err) {
await this.page1.logger(err);
@ -191,7 +194,7 @@ class MultiUsers {
await this.page2.waitForSelector(we.raiseHandLabel, ELEMENT_WAIT_TIME);
await this.page2.click(we.raiseHandLabel, true);
await sleep(2000);
const resp = await this.page2.page.evaluate(utilCustomParams.countTestElements, we.lowerHandLabel);
const resp = await this.page2.page.evaluate(checkElementLengthDifferentTo, we.lowerHandLabel, 0);
return resp === true;
} catch (err) {
await this.page1.logger(err);
@ -205,7 +208,7 @@ class MultiUsers {
await this.page2.waitForSelector(we.lowerHandLabel, ELEMENT_WAIT_TIME);
await this.page2.click(we.lowerHandLabel, true);
await sleep(2000);
const resp = await this.page2.page.evaluate(utilCustomParams.countTestElements, we.raiseHandLabel);
const resp = await this.page2.page.evaluate(checkElementLengthDifferentTo, we.raiseHandLabel, 0);
return resp === true;
} catch (err) {
await this.page2.logger(err);
@ -217,7 +220,7 @@ class MultiUsers {
async getAvatarColorAndCompareWithUserListItem() {
try {
const avatarInToastElementColor = await this.page1.page.$eval(we.avatarsWrapperAvatar, (elem) => getComputedStyle(elem).backgroundColor);
const avatarInUserListColor = await this.page1.page.$eval('[data-test="userListItem"] > div [data-test="userAvatar"]', (elem) => getComputedStyle(elem).backgroundColor);
const avatarInUserListColor = await this.page1.page.$eval(`${ue.userListItem} > div ${ue.statusIcon}`, (elem) => getComputedStyle(elem).backgroundColor);
return avatarInToastElementColor === avatarInUserListColor;
} catch (err) {
await this.page1.logger(err);
@ -236,8 +239,8 @@ class MultiUsers {
await sleep(5000);
await utilUser.connectionStatus(this.page1);
await sleep(5000);
const connectionStatusItemEmpty = await this.page1.page.evaluate(utilUser.countTestElements, ue.connectionStatusItemEmpty) === false;
const connectionStatusOfflineUser = await this.page1.page.evaluate(utilUser.countTestElements, ue.connectionStatusOfflineUser) === true;
const connectionStatusItemEmpty = await this.page1.page.evaluate(checkElementLengthEqualTo, ue.connectionStatusItemEmpty, 0);
const connectionStatusOfflineUser = await this.page1.page.evaluate(checkElementLengthDifferentTo, ue.connectionStatusOfflineUser, 0) === true;
return connectionStatusOfflineUser && connectionStatusItemEmpty;
} catch (err) {
await this.page1.logger(err);
@ -249,8 +252,8 @@ class MultiUsers {
try {
await this.page1.closeAudioModal();
await this.page2.closeAudioModal();
const userlistPanel = await this.page1.page.evaluate(utilUser.countTestElements, ue.chatButton) === false;
const chatPanel = await this.page2.page.evaluate(utilUser.countTestElements, ue.chatButton) === false;
const userlistPanel = await this.page1.page.evaluate(checkElementLengthEqualTo, ue.chatButton, 0);
const chatPanel = await this.page2.page.evaluate(checkElementLengthEqualTo, ue.chatButton, 0);
return userlistPanel && chatPanel;
} catch (err) {
await this.page1.logger(err);
@ -266,7 +269,7 @@ class MultiUsers {
await this.page2.click(ue.userListButton, true);
await this.page2.click(ue.chatButton, true);
const onUserListPanel = await this.page1.isNotVisible(cu.hidePresentation, ELEMENT_WAIT_TIME) === true;
const onChatPanel = await this.page2.page.evaluate(utilUser.countTestElements, cu.hidePresentation) === false;
const onChatPanel = await this.page2.page.evaluate(checkElementLengthEqualTo, cu.hidePresentation, 0);
await sleep(2000);
return onUserListPanel && onChatPanel;
} catch (err) {
@ -281,7 +284,7 @@ class MultiUsers {
await this.page2.closeAudioModal();
await this.page2.click(ue.userListButton, true);
await this.page2.click(ue.chatButton, true);
const whiteboard = await this.page1.page.evaluate(utilUser.countTestElements, ue.chatButton) === false;
const whiteboard = await this.page1.page.evaluate(checkElementLengthEqualTo, ue.chatButton, 0);
const onChatPanel = await this.page2.isNotVisible(ue.chatButton, ELEMENT_WAIT_TIME) === true;
await sleep(2000);
return whiteboard && onChatPanel;

View File

@ -4,8 +4,8 @@ const e = require('./elements');
const util = require('./util');
const utilWebcam = require('../webcam/util');
const utilScreenshare = require('../screenshare/util');
const utilB = require('../breakout/util');
const { sleep } = require('../core/helper');
const { clickElement, checkElementLengthEqualTo, checkElementLengthDifferentTo } = require('../core/util');
class Status extends Page {
constructor() {
@ -15,9 +15,9 @@ class Status extends Page {
async test() {
try {
await util.setStatus(this, e.applaud);
const resp1 = await this.page.evaluate(util.countTestElements, e.applauseIcon);
const resp1 = await this.page.evaluate(checkElementLengthDifferentTo, e.applauseIcon, 0);
await util.setStatus(this, e.away);
const resp2 = await this.page.evaluate(util.countTestElements, e.awayIcon);
const resp2 = await this.page.evaluate(checkElementLengthDifferentTo, e.awayIcon, 0);
await this.click(e.firstUser, true);
await this.waitForSelector(e.clearStatus, ELEMENT_WAIT_TIME);
@ -35,7 +35,7 @@ class Status extends Page {
await this.page.click(e.userList, true);
await this.page.waitForSelector(e.firstUser, ELEMENT_WAIT_TIME);
const response = await this.page.evaluate(util.countTestElements, e.mobileUser) === true;
const response = await this.page.evaluate(checkElementLengthDifferentTo, e.mobileUser, 0);
return response === true;
} catch (err) {
await this.logger(err);
@ -46,7 +46,7 @@ class Status extends Page {
async findConnectionStatusModal() {
try {
await util.connectionStatus(this.page);
const resp = await this.page.evaluate(util.countTestElements, e.connectionStatusModal) === true;
const resp = await this.page.evaluate(checkElementLengthDifferentTo, e.connectionStatusModal, 0);
return resp === true;
} catch (err) {
await this.logger(err);
@ -60,11 +60,11 @@ class Status extends Page {
await utilWebcam.enableWebcam(this, ELEMENT_WAIT_LONGER_TIME);
await util.connectionStatus(this);
await this.waitForSelector(e.dataSavingWebcams, ELEMENT_WAIT_TIME);
await this.page.evaluate(utilB.clickTestElement, e.dataSavingWebcams);
await this.page.evaluate(clickElement, e.dataSavingWebcams);
await this.waitForSelector(e.closeConnectionStatusModal, ELEMENT_WAIT_TIME);
await this.page.evaluate(utilB.clickTestElement, e.closeConnectionStatusModal);
await this.page.evaluate(clickElement, e.closeConnectionStatusModal);
await sleep(2000);
const webcamsIsDisabledInDataSaving = await this.page.evaluate(util.countTestElements, e.webcamsIsDisabledInDataSaving) === true;
const webcamsIsDisabledInDataSaving = await this.page.evaluate(checkElementLengthDifferentTo, e.webcamsIsDisabledInDataSaving, 0);
return webcamsIsDisabledInDataSaving === true;
} catch (err) {
await this.logger(err);
@ -79,11 +79,11 @@ class Status extends Page {
await utilScreenshare.waitForScreenshareContainer(this);
await util.connectionStatus(this);
await this.waitForSelector(e.dataSavingScreenshare, ELEMENT_WAIT_TIME);
await this.page.evaluate(utilB.clickTestElement, e.dataSavingScreenshare);
await this.page.evaluate(clickElement, e.dataSavingScreenshare);
await this.waitForSelector(e.closeConnectionStatusModal, ELEMENT_WAIT_TIME);
await this.page.evaluate(utilB.clickTestElement, e.closeConnectionStatusModal);
await this.page.evaluate(clickElement, e.closeConnectionStatusModal);
await sleep(2000);
const webcamsIsDisabledInDataSaving = await this.page.evaluate(util.countTestElements, e.screenshareLocked) === true;
const webcamsIsDisabledInDataSaving = await this.page.evaluate(checkElementLengthEqualTo, e.screenshareLocked, 0);
return webcamsIsDisabledInDataSaving === true;
} catch (err) {
await this.logger(err);
@ -100,8 +100,8 @@ class Status extends Page {
await utilScreenshare.waitForScreenshareContainer(this);
await util.connectionStatus(this);
await sleep(5000);
const connectionStatusItemEmpty = await this.page.evaluate(util.countTestElements, e.connectionStatusItemEmpty) === false;
const connectionStatusItemUser = await this.page.evaluate(util.countTestElements, e.connectionStatusItemUser) === true;
const connectionStatusItemEmpty = await this.page.evaluate(checkElementLengthEqualTo, e.connectionStatusItemEmpty, 0);
const connectionStatusItemUser = await this.page.evaluate(checkElementLengthDifferentTo, e.connectionStatusItemUser, 0);
return connectionStatusItemUser && connectionStatusItemEmpty;
} catch (err) {
await this.logger(err);

View File

@ -10,15 +10,10 @@ async function setStatus(test, status) {
await test.click(status, true);
}
async function countTestElements(element) {
return document.querySelectorAll(element).length !== 0;
}
async function connectionStatus(test) {
await test.click('button[data-test="connectionStatusButton"]', true);
await test.waitForSelector('div[aria-label="Connection status modal"]', ELEMENT_WAIT_TIME);
await test.click(e.connectionStatusBtn, true);
await test.waitForSelector(e.connectionStatusModal, ELEMENT_WAIT_TIME);
}
exports.countTestElements = countTestElements;
exports.setStatus = setStatus;
exports.connectionStatus = connectionStatus;

View File

@ -2,6 +2,7 @@ const Page = require('../core/page');
const params = require('../params');
const { USER_LIST_VLIST_BOTS_LISTENING, ELEMENT_WAIT_TIME } = require('../core/constants');
const ue = require('../user/elements');
const { getElementLength } = require('../core/util')
class VirtualizeList {
constructor() {
@ -31,7 +32,7 @@ class VirtualizeList {
async test() {
try {
const USER_LIST_VLIST_VISIBLE_USERS = await this.page1.page.evaluate(async () => await document.querySelectorAll('[data-test^="userListItem"]').length);
const USER_LIST_VLIST_VISIBLE_USERS = await this.page1.page.evaluate(getElementLength, ue.anyUser);
const totalNumberOfUsersMongo = await this.page1.page.evaluate(() => {
const collection = require('/imports/api/users/index.js');
const users = collection.default._collection.find().count();

View File

@ -1,8 +1,9 @@
const Page = require('../core/page');
const util = require('./util');
const wle = require('./elements');
const { checkElementLengthDifferentTo } = require('../core/util');
const { VIDEO_LOADING_WAIT_TIME } = require('../core/constants'); // core constants (Timeouts vars imported)
const e = require('../core/elements');
const { ELEMENT_WAIT_TIME, VIDEO_LOADING_WAIT_TIME } = require('../core/constants'); // core constants (Timeouts vars imported)
class Share extends Page {
constructor() {
@ -37,7 +38,7 @@ class Share extends Page {
await this.waitForSelector(wle.webcamVideo, VIDEO_LOADING_WAIT_TIME);
await this.waitForSelector(wle.stopSharingWebcam, VIDEO_LOADING_WAIT_TIME);
await this.waitForSelector(e.isTalking);
const foundTestElement = await this.page.evaluate(util.countTestElements, wle.webcamItemTalkingUser) !== 0;
const foundTestElement = await this.page.evaluate(checkElementLengthDifferentTo, wle.webcamItemTalkingUser, 0);
if (foundTestElement === true) {
await this.screenshot(`${testName}`, `success-${testName}`);
this.logger(testName, ' passed');

View File

@ -1,32 +1,29 @@
const we = require('./elements');
const { sleep } = require('../core/helper');
const { checkElement, clickElement , checkElementLengthDifferentTo } = require('../core/util');
const {
LOOP_INTERVAL, ELEMENT_WAIT_TIME, VIDEO_LOADING_WAIT_TIME, ELEMENT_WAIT_LONGER_TIME,
LOOP_INTERVAL,
ELEMENT_WAIT_TIME,
VIDEO_LOADING_WAIT_TIME,
ELEMENT_WAIT_LONGER_TIME,
} = require('../core/constants');
async function enableWebcam(test, videoPreviewTimeout) {
// Enabling webcam
await test.waitForSelector(we.joinVideo, ELEMENT_WAIT_TIME);
await test.page.evaluate(clickTestElement, we.joinVideo);
await test.page.evaluate(clickElement, we.joinVideo);
await test.waitForSelector(we.videoPreview, videoPreviewTimeout);
await test.waitForSelector(we.startSharingWebcam, ELEMENT_WAIT_TIME);
await test.page.evaluate(clickTestElement, we.startSharingWebcam);
await test.page.evaluate(clickElement, we.startSharingWebcam);
await test.waitForSelector(we.webcamConnecting, ELEMENT_WAIT_TIME);
await test.waitForSelector(we.webcamVideo, VIDEO_LOADING_WAIT_TIME);
await test.waitForSelector(we.leaveVideo, VIDEO_LOADING_WAIT_TIME);
const resp = await test.page.evaluate(countTestElements, we.webcamVideo) !== 0;
return resp;
}
async function getFullScreenWebcamButton(element) {
return await document.querySelectorAll(element)[1] !== null;
return test.page.evaluate(checkElementLengthDifferentTo, we.webcamVideo, 0);
}
async function evaluateCheck(test) {
await test.waitForSelector(we.videoContainer, ELEMENT_WAIT_TIME);
const videoContainer = await test.page.evaluate(getFullScreenWebcamButton, we.presentationFullscreenButton);
const response = videoContainer !== false;
return response;
return test.page.evaluate(checkElement, we.presentationFullscreenButton, 1);
}
async function startAndCheckForWebcams(test) {
@ -72,11 +69,6 @@ async function webcamContentCheck(test) {
return check === true;
}
async function clickTestElement(element) {
document.querySelectorAll(element)[0].click();
}
async function countTestElements(element) {
const respCount = await document.querySelectorAll(element).length;
return respCount;
@ -85,6 +77,5 @@ async function countTestElements(element) {
exports.startAndCheckForWebcams = startAndCheckForWebcams;
exports.webcamContentCheck = webcamContentCheck;
exports.evaluateCheck = evaluateCheck;
exports.getFullScreenWebcamButton = getFullScreenWebcamButton;
exports.enableWebcam = enableWebcam;
exports.countTestElements = countTestElements;

2
record-and-playback/core/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.bundle
vendor/bundle

View File

@ -34,7 +34,9 @@ gem 'rubyzip', '~> 2.0'
gem 'trollop', '2.1.3'
gem 'resque', '~> 2.0.0'
gem 'bbbevents', '~> 1.2'
gem 'rake', '>= 12.3', '<14'
group :test, optional: true do
gem 'rubocop', '~> 0.79.0'
gem 'minitest', '~> 5.14.1'
end

View File

@ -45,6 +45,7 @@ GEM
rack-protection (2.0.8.1)
rack
rainbow (3.0.0)
rake (13.0.6)
rb-inotify (0.10.1)
ffi (~> 1.0)
redis (4.1.3)
@ -93,8 +94,10 @@ DEPENDENCIES
jwt (~> 2.2)
locale (~> 2.1)
loofah (~> 2.3)
minitest (~> 5.14.1)
nokogiri (~> 1.11)
open4 (~> 1.3)
rake (>= 12.3, < 14)
rb-inotify (~> 0.10)
redis (~> 4.1)
resque (~> 2.0.0)

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'resque/tasks'
require 'rake/testtask'
task 'resque:setup' => :environment do
props = BigBlueButton.read_props
@ -17,3 +18,9 @@ task :environment do
require_relative 'lib/boot'
require 'recordandplayback/workers'
end
Rake::TestTask.new(:test) do |t|
t.libs << 'test'
t.libs << 'lib'
t.test_files = FileList['test/**/test_*.rb']
end

View File

@ -21,6 +21,7 @@
require 'rubygems'
require 'time'
require 'nokogiri'
require 'loofah'
require 'set'
@ -476,12 +477,134 @@ module BigBlueButton
node['href'] = node['href'][6..-1] if node.name == 'a' && node['href'] && node['href'].start_with?('event:')
end
def self.linkify( text )
def self.linkify(text)
html = Loofah.fragment(text)
html.scrub!(@remove_link_event_prefix).scrub!(:strip).scrub!(:nofollow).scrub!(:unprintable)
html.to_html
end
# Build a map of internal user IDs to anonymized names. This can be used to anonymize users in
# chat, cursor overlays, etc.
def self.anonymous_user_map(events, moderators: false)
viewer_count = 0
moderator_count = 0
external_map = {}
map = {}
events.xpath('/recording/event[@module="PARTICIPANT" and @eventname="ParticipantJoinEvent"]').each do |event|
internal_id = event.at_xpath('./userId')&.content
next if internal_id.nil?
external_id = event.at_xpath('./externalUserId')&.content || internal_id
name = external_map.fetch(external_id) do
role = event.at_xpath('./role').content
new_name = \
if role == 'MODERATOR' && moderators
moderator_count += 1
"Moderator #{moderator_count}"
elsif role == 'MODERATOR'
event.at_xpath('./name')&.content
else
viewer_count += 1
"Viewer #{viewer_count}"
end
external_map[external_id] = new_name unless new_name.nil?
end
map[internal_id] = name unless name.nil?
end
map
end
# Get a list of chat events, with start/end time for segments and recording marks applied.
# Optionally anonymizes chat participant names.
# Reads the keys 'anonymize_chat' and 'anonymize_chat_moderators' from bbb_props, but allows
# per-meeting override using the create meta params 'meta_bbb-anonymize-chat' and
# 'meta_bbb-anonymize-chat-moderators'
# Each event in the return value has the following properties:
# in: 0-based milliseconds timestamp of when chat was sent
# out: 0-based milliseconds timestamp of when chat was cleared (or nil if chat was never cleared)
# sender_id: The internal user id of the sender (can be nil on really old BBB versions)
# sender: The display name of the sender
# message: The chat message, with link cleanup already applied
# date: The real time of when the message was sent (if available) as a DateTime
# text_color: The RGB color value of the chat message text as an integer (old BBB versions only)
# avatar_color: The color of the user's avatar (initials) box (newer BBB versions only)
def self.get_chat_events(events, start_time, end_time, bbb_props = {})
BigBlueButton.logger.info('Getting chat events')
initial_timestamp = first_event_timestamp(events)
start_time -= initial_timestamp
end_time -= initial_timestamp
last_stop_timestamp = start_time
offset = start_time
# Recordings without status events are assumed to have been recorded from the beginning
record = events.at_xpath('/recording/event[@eventname="RecordStatusEvent"]').nil?
# Load the anonymize settings; defaults from bigbluebutton.yml, override with meta params
metadata = events.at_xpath('/recording/metadata')
anonymize_senders = metadata['bbb-anonymize-chat'] unless metadata.nil?
anonymize_senders = bbb_props['anonymize_chat'] if anonymize_senders.nil?
anonymize_senders = anonymize_senders.to_s.casecmp?('true')
anonymize_moderators = metadata['bbb-anonymize-chat-moderators'] unless metadata.nil?
anonymize_moderators = bbb_props['anonymize_chat_moderators'] if anonymize_moderators.nil?
anonymize_moderators = anonymize_moderators.to_s.casecmp?('true')
user_map = anonymize_senders ? anonymous_user_map(events, moderators: anonymize_moderators) : {}
chats = []
events.xpath('/recording/event').each do |event|
timestamp = event[:timestamp].to_i - initial_timestamp
break if timestamp >= end_time
case [event[:module], event[:eventname]]
when %w[CHAT PublicChatEvent]
next if timestamp < start_time || !record
date = event.at_xpath('./date')&.content
date = DateTime.iso8601(date) unless date.nil?
sender_id = event.at_xpath('./senderId')&.content
color = event.at_xpath('./color')&.content
if color&.start_with?('#')
avatar_color = color
else
text_color = color.to_i
end
chats << {
in: timestamp - offset,
out: nil,
sender_id: sender_id,
sender: user_map.fetch(sender_id) { event.at_xpath('./sender').content },
message: linkify(event.at_xpath('./message').content.strip),
avatar_color: avatar_color,
text_color: text_color,
date: date,
}
when %w[CHAT ClearPublicChatEvent]
next if timestamp < start_time
clear_timestamp = (record ? timestamp : last_stop_timestamp) - offset
chats.each do |chat|
chat[:out] = clear_timestamp if chat[:out].nil?
end
when %w[PARTICIPANT RecordStatusEvent]
record = event.at_xpath('status').content == 'true'
next if timestamp < start_time
if record
offset += timestamp - last_stop_timestamp
else
last_stop_timestamp = timestamp
end
end
end
chats
end
def self.get_record_status_events(events_xml)
BigBlueButton.logger.info "Getting record status events"
rec_events = []

View File

@ -0,0 +1,422 @@
<?xml version="1.0" encoding="UTF-8"?>
<recording meeting_id="2a1de53edf0543d950056bf3c0d4d357eba3383f-1630607370684" bbb_version="2.1.0">
<meeting id="2a1de53edf0543d950056bf3c0d4d357eba3383f-1630607370684" externalId="random-9678161_calvin_" name="random-9678161" breakout="false"/>
<metadata bbb-anonymize-chat="true" isBreakout="false" meetingId="random-9678161_calvin_" meetingName="random-9678161"/>
<event timestamp="1131799575" module="PRESENTATION" eventname="CreatePresentationPodEvent">
<currentPresenter/>
<timestampUTC>1630607370704</timestampUTC>
<podId>DEFAULT_PRESENTATION_POD</podId>
<date>2021-09-02T14:29:30.704-04</date>
</event>
<event timestamp="1131799900" module="PAD" eventname="AddPadEvent">
<timestampUTC>1630607371029</timestampUTC>
<date>2021-09-02T14:29:31.029-04</date>
<padId>[2]cfe107fd5780210103d9d4017f8c751d5594</padId>
</event>
<event timestamp="1131803050" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<name>User 7520456</name>
<timestampUTC>1630607374179</timestampUTC>
<role>MODERATOR</role>
<date>2021-09-02T14:29:34.179-04</date>
<externalUserId>w_jw2fcjeovwa6</externalUserId>
<userId>w_jw2fcjeovwa6</userId>
</event>
<event timestamp="1131803051" module="PARTICIPANT" eventname="AssignPresenterEvent">
<name>User 7520456</name>
<timestampUTC>1630607374180</timestampUTC>
<userid>w_jw2fcjeovwa6</userid>
<assignedBy>w_jw2fcjeovwa6</assignedBy>
<date>2021-09-02T14:29:34.180-04</date>
</event>
<event timestamp="1131803066" module="PARTICIPANT" eventname="AssignPresenterEvent">
<name>User 7520456</name>
<timestampUTC>1630607374195</timestampUTC>
<userid>w_jw2fcjeovwa6</userid>
<assignedBy>w_jw2fcjeovwa6</assignedBy>
<date>2021-09-02T14:29:34.195-04</date>
</event>
<event timestamp="1131803067" module="PRESENTATION" eventname="SetPresenterInPodEvent">
<timestampUTC>1630607374196</timestampUTC>
<podId>DEFAULT_PRESENTATION_POD</podId>
<date>2021-09-02T14:29:34.196-04</date>
<nextPresenterId>w_jw2fcjeovwa6</nextPresenterId>
</event>
<event timestamp="1131803071" module="PARTICIPANT" eventname="AssignPresenterEvent">
<name>User 7520456</name>
<timestampUTC>1630607374200</timestampUTC>
<userid>w_jw2fcjeovwa6</userid>
<assignedBy>w_jw2fcjeovwa6</assignedBy>
<date>2021-09-02T14:29:34.200-04</date>
</event>
<event timestamp="1131803072" module="PRESENTATION" eventname="SetPresenterInPodEvent">
<timestampUTC>1630607374201</timestampUTC>
<podId>DEFAULT_PRESENTATION_POD</podId>
<date>2021-09-02T14:29:34.201-04</date>
<nextPresenterId>w_jw2fcjeovwa6</nextPresenterId>
</event>
<event timestamp="1131805474" module="PRESENTATION" eventname="ConversionCompletedEvent">
<originalFilename>default.pdf</originalFilename>
<timestampUTC>1630607376603</timestampUTC>
<presentationName>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentationName>
<podId>DEFAULT_PRESENTATION_POD</podId>
<date>2021-09-02T14:29:36.603-04</date>
</event>
<event timestamp="1131805477" module="PRESENTATION" eventname="SharePresentationEvent">
<timestampUTC>1630607376606</timestampUTC>
<presentationName>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentationName>
<podId>DEFAULT_PRESENTATION_POD</podId>
<share>true</share>
<date>2021-09-02T14:29:36.606-04</date>
</event>
<event timestamp="1131805479" module="PRESENTATION" eventname="SetPresentationDownloadable">
<timestampUTC>1630607376608</timestampUTC>
<presentationName>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentationName>
<podId>DEFAULT_PRESENTATION_POD</podId>
<date>2021-09-02T14:29:36.608-04</date>
<downloadable>false</downloadable>
</event>
<event timestamp="1131824162" module="VOICE" eventname="ParticipantJoinedEvent">
<participant>w_jw2fcjeovwa6</participant>
<timestampUTC>1630607395291</timestampUTC>
<bridge>578447737</bridge>
<callername>User 7520456</callername>
<talking>false</talking>
<callernumber>w_jw2fcjeovwa6_1-bbbID-User 7520456</callernumber>
<date>2021-09-02T14:29:55.291-04</date>
<muted>false</muted>
</event>
<event timestamp="1131824186" module="VOICE" eventname="StartRecordingEvent">
<filename>/var/freeswitch/meetings/2a1de53edf0543d950056bf3c0d4d357eba3383f-1630607370684-1131824162.opus</filename>
<bridge>578447737</bridge>
<timestampUTC>1630607395314</timestampUTC>
<date>2021-09-02T14:29:55.314-04</date>
<recordingTimestamp>1131824184</recordingTimestamp>
</event>
<event timestamp="1131824226" module="VOICE" eventname="ParticipantTalkingEvent">
<participant>w_jw2fcjeovwa6</participant>
<timestampUTC>1630607395355</timestampUTC>
<bridge>578447737</bridge>
<talking>true</talking>
<date>2021-09-02T14:29:55.355-04</date>
</event>
<event timestamp="1131825122" module="VOICE" eventname="ParticipantMutedEvent">
<participant>w_jw2fcjeovwa6</participant>
<timestampUTC>1630607396250</timestampUTC>
<bridge>578447737</bridge>
<date>2021-09-02T14:29:56.250-04</date>
<muted>true</muted>
</event>
<event timestamp="1131825327" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607396456</timestampUTC>
<xOffset>42.81167030334473</xOffset>
<date>2021-09-02T14:29:56.456-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>87.12742558232061</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131825473" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607396602</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:29:56.602-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131834887" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607406015</timestampUTC>
<xOffset>34.58885828653971</xOffset>
<date>2021-09-02T14:30:06.015-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>75.1026690447772</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131835039" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607406168</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:30:06.168-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131839891" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607411020</timestampUTC>
<xOffset>40.557028452555336</xOffset>
<date>2021-09-02T14:30:11.020-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>0.8321106875384296</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131840041" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607411169</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:30:11.169-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131841537" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607412665</timestampUTC>
<xOffset>50.63660303751628</xOffset>
<date>2021-09-02T14:30:12.665-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>9.084394949453849</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131841691" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607412819</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:30:12.819-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131841840" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607412969</timestampUTC>
<xOffset>36.57824834187826</xOffset>
<date>2021-09-02T14:30:12.969-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>33.60546818485967</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131842003" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607413131</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:30:13.131-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131882243" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<name>(guest) Calvin</name>
<timestampUTC>1630607453372</timestampUTC>
<role>VIEWER</role>
<date>2021-09-02T14:30:53.372-04</date>
<externalUserId>3ff859e3-1c2e-48e9-860d-cf230648cfca</externalUserId>
<userId>w_tvumguamxhhs</userId>
</event>
<event timestamp="1131885821" module="VOICE" eventname="ParticipantJoinedEvent">
<participant>w_tvumguamxhhs</participant>
<timestampUTC>1630607456950</timestampUTC>
<bridge>578447737</bridge>
<callername>(guest) Calvin</callername>
<talking>false</talking>
<callernumber>(guest) Calvin</callernumber>
<date>2021-09-02T14:30:56.950-04</date>
<muted>true</muted>
</event>
<event timestamp="1131895190" module="CHAT" eventname="PublicChatEvent">
<timestampUTC>1630607466319</timestampUTC>
<color>#6a1b9a</color>
<senderId>w_jw2fcjeovwa6</senderId>
<date>2021-09-02T14:31:06.319-04</date>
<sender>User 7520456</sender>
<message>Hello, moderator chat message!</message>
</event>
<event timestamp="1131905701" module="CHAT" eventname="PublicChatEvent">
<timestampUTC>1630607476829</timestampUTC>
<color>#4a148c</color>
<senderId>w_tvumguamxhhs</senderId>
<date>2021-09-02T14:31:16.829-04</date>
<sender>(guest) Calvin</sender>
<message>Hello, guest chat message!</message>
</event>
<event timestamp="1131908051" module="CHAT" eventname="PublicChatEvent">
<timestampUTC>1630607479180</timestampUTC>
<color>#4a148c</color>
<senderId>w_tvumguamxhhs</senderId>
<date>2021-09-02T14:31:19.180-04</date>
<sender>(guest) Calvin</sender>
<message>yay!</message>
</event>
<event timestamp="1131949647" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607520776</timestampUTC>
<xOffset>10.185674826304119</xOffset>
<date>2021-09-02T14:32:00.776-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>96.79438838252314</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131949798" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607520927</timestampUTC>
<xOffset>14.297080039978027</xOffset>
<date>2021-09-02T14:32:00.927-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>90.1925602665654</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131949893" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607521022</timestampUTC>
<xOffset>14.297080039978027</xOffset>
<date>2021-09-02T14:32:01.022-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>89.95677806712963</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131950258" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607521387</timestampUTC>
<xOffset>14.297080039978027</xOffset>
<date>2021-09-02T14:32:01.387-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>90.1925602665654</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131950408" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607521537</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:32:01.537-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131955635" module="CHAT" eventname="ClearPublicChatEvent">
<timestampUTC>1630607526763</timestampUTC>
<date>2021-09-02T14:32:06.763-04</date>
</event>
<event timestamp="1131964123" module="CHAT" eventname="PublicChatEvent">
<timestampUTC>1630607535252</timestampUTC>
<color>#6a1b9a</color>
<senderId>w_jw2fcjeovwa6</senderId>
<date>2021-09-02T14:32:15.252-04</date>
<sender>User 7520456</sender>
<message>Chat was cleared</message>
</event>
<event timestamp="1131994645" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607565774</timestampUTC>
<xOffset>43.34217389424642</xOffset>
<date>2021-09-02T14:32:45.774-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>96.55861183449073</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131994767" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607565896</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:32:45.896-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131997197" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607568326</timestampUTC>
<xOffset>32.20158894856771</xOffset>
<date>2021-09-02T14:32:48.326-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>96.55861183449073</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131997346" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607568475</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:32:48.475-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1131999450" module="PARTICIPANT" eventname="RecordStatusEvent">
<timestampUTC>1630607570579</timestampUTC>
<date>2021-09-02T14:32:50.579-04</date>
<status>true</status>
<userId>w_jw2fcjeovwa6</userId>
</event>
<event timestamp="1131999466" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607570595</timestampUTC>
<xOffset>13.501324653625488</xOffset>
<date>2021-09-02T14:32:50.595-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>59.77699562355324</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1132001481" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607572610</timestampUTC>
<xOffset>13.103446960449219</xOffset>
<date>2021-09-02T14:32:52.610-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>59.77699562355324</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1132001799" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607572927</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:32:52.927-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1132011085" module="CHAT" eventname="PublicChatEvent">
<timestampUTC>1630607582214</timestampUTC>
<color>#4a148c</color>
<senderId>w_tvumguamxhhs</senderId>
<date>2021-09-02T14:33:02.214-04</date>
<sender>(guest) Calvin</sender>
<message>whoops, forgot to start recording…</message>
</event>
<event timestamp="1132049253" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607620382</timestampUTC>
<xOffset>36.180369059244796</xOffset>
<date>2021-09-02T14:33:40.382-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>97.9732824254919</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1132049403" module="WHITEBOARD" eventname="WhiteboardCursorMoveEvent">
<timestampUTC>1630607620531</timestampUTC>
<xOffset>-62.891248067220054</xOffset>
<date>2021-09-02T14:33:40.531-04</date>
<whiteboardId>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702/1</whiteboardId>
<pageNumber>0</pageNumber>
<userId>w_jw2fcjeovwa6</userId>
<yOffset>-60.94213415075232</yOffset>
<presentation>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1630607370702</presentation>
</event>
<event timestamp="1132053637" module="PARTICIPANT" eventname="EndAndKickAllEvent">
<timestampUTC>1630607624766</timestampUTC>
<reason>ENDED_AFTER_USER_LOGGED_OUT</reason>
<date>2021-09-02T14:33:44.766-04</date>
</event>
</recording>

View File

@ -0,0 +1,344 @@
<?xml version="1.0" encoding="UTF-8"?>
<recording meeting_id="afa22bf4e2a55835006a0016776f700dcf8e981e-1630339972856" bbb_version="0.9.0">
<meeting
id="afa22bf4e2a55835006a0016776f700dcf8e981e-1630339972856"
externalId="chat_0_9" name="Chat 0.9 Test"
breakout="false"/>
<metadata
meetingName="Chat 0.9 Test"
meetingId="chat_0_9"
isBreakout="false"/>
<event timestamp="1061316615" module="PRESENTATION" eventname="SharePresentationEvent">
<presentationName>d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1503524575790</presentationName>
<share>true</share>
<originalFilename>default.pdf</originalFilename>
</event>
<event timestamp="1061329979" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<name>Marinda Collins</name>
<role>MODERATOR</role>
<userId>9izxq660i7vr_1</userId>
<externalUserId>1000</externalUserId>
</event>
<event timestamp="1061397065" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<userId>vvyha6umxoyt_1</userId>
<externalUserId>1001</externalUserId>
<name>Phelix Fishman</name>
<role>VIEWER</role>
</event>
<event timestamp="1061511972" module="PARTICIPANT" eventname="RecordStatusEvent">
<userId>9izxq660i7vr_1</userId>
<status>true</status>
</event>
<event timestamp="1061536072" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<role>VIEWER</role>
<externalUserId>1002</externalUserId>
<userId>hs7iskkr7xrt_1</userId>
<name>Isaías Seelen</name>
</event>
<event timestamp="1061574575" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<role>VIEWER</role>
<userId>7m940cic73r3_1</userId>
<name>Mireia Castell</name>
<externalUserId>1003</externalUserId>
</event>
<event timestamp="1061629575" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<role>VIEWER</role>
<externalUserId>1004</externalUserId>
<userId>tgfbj6f828sp_1</userId>
<name>Liborius Hayes</name>
</event>
<event timestamp="1061703579" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<role>VIEWER</role>
<externalUserId>1005</externalUserId>
<userId>bepguk6d7dza_1</userId>
<name>Eva Aquino</name>
</event>
<event timestamp="1061749302" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<role>VIEWER</role>
<externalUserId>1006</externalUserId>
<userId>66ntqzexswc2_1</userId>
<name>Rodge Palazzo</name>
</event>
<event timestamp="1061825270" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<externalUserId>1007</externalUserId>
<role>VIEWER</role>
<name>Elias Stablum</name>
<userId>0q1hkmla9asu_1</userId>
</event>
<event timestamp="1061881172" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<userId>yyynfpyca09g_1</userId>
<externalUserId>1008</externalUserId>
<role>VIEWER</role>
<name>Evelina Keller</name>
</event>
<event timestamp="1061933105" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<userId>dmsj3897dwss_1</userId>
<role>VIEWER</role>
<name>Xhesika De Lange</name>
<externalUserId>1009</externalUserId>
</event>
<event timestamp="1061952342" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<name>Nimue Harlan</name>
<role>VIEWER</role>
<externalUserId>1010</externalUserId>
<userId>42dnty7rovjt_1</userId>
</event>
<event timestamp="1061962737" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<name>Elpidio O'Gorman</name>
<role>VIEWER</role>
<externalUserId>1011</externalUserId>
<userId>7ur69btts657_1</userId>
</event>
<event timestamp="1061998179" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>dmsj3897dwss_1</userId>
</event>
<event timestamp="1062008158" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>66ntqzexswc2_1</userId>
</event>
<event timestamp="1062013094" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<externalUserId>1012</externalUserId>
<role>VIEWER</role>
<userId>23uydbo9nauq_1</userId>
<name>Asa Darby</name>
</event>
<event timestamp="1062017073" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<externalUserId>1006</externalUserId>
<userId>12ipastd9pw1_1</userId>
<role>VIEWER</role>
<name>Rodge Palazzo</name>
</event>
<event timestamp="1062026555" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<name>Xhesika De Lange</name>
<role>VIEWER</role>
<externalUserId>1009</externalUserId>
<userId>ainnu65fiycz_1</userId>
</event>
<event timestamp="1062058796" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<externalUserId>1013</externalUserId>
<role>VIEWER</role>
<name>Arethusa Mann</name>
<userId>j73nq5k8xcaa_1</userId>
</event>
<event timestamp="1062142085" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<name>Ninel Mac Ruaidhrí</name>
<role>VIEWER</role>
<userId>nfuklna24flg_1</userId>
<externalUserId>1014</externalUserId>
</event>
<event timestamp="1062159696" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>ainnu65fiycz_1</userId>
</event>
<event timestamp="1062170527" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<externalUserId>1009</externalUserId>
<name>Xhesika De Lange</name>
<role>VIEWER</role>
<userId>2dc8jctma0nj_1</userId>
</event>
<event timestamp="1062260997" module="CHAT" eventname="PublicChatEvent">
<sender>Xhesika De Lange</sender>
<senderId>2dc8jctma0nj_1</senderId>
<message>
<![CDATA[Public chat 1]]>
</message>
<color>0</color>
</event>
<event timestamp="1062483994" module="CHAT" eventname="PublicChatEvent">
<color>0</color>
<senderId>23uydbo9nauq_1</senderId>
<message>
<![CDATA[Public chat 2]]>
</message>
<sender>Asa Darby</sender>
</event>
<event timestamp="1062500000" module="CHAT" eventname="ClearPublicChatEvent">
</event>
<event timestamp="1062914576" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>j73nq5k8xcaa_1</userId>
</event>
<event timestamp="1063007465" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<userId>fzlsahcijxo4_1</userId>
<externalUserId>1013</externalUserId>
<role>VIEWER</role>
<name>Arethusa Mann</name>
</event>
<event timestamp="1063309296" module="CHAT" eventname="PublicChatEvent">
<color>0</color>
<senderId>hs7iskkr7xrt_1</senderId>
<message>
<![CDATA[Public chat 3]]>
</message>
<sender>Isaías Seelen</sender>
</event>
<event timestamp="1064118099" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>fzlsahcijxo4_1</userId>
</event>
<event timestamp="1064118099" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<userId>fzlsahcijxo4_1</userId>
<externalUserId>1013</externalUserId>
<name>Arethusa Mann</name>
<role>VIEWER</role>
</event>
<event timestamp="1064123456" module="PARTICIPANT" eventname="RecordStatusEvent">
<userId>9izxq660i7vr_1</userId>
<status>false</status>
</event>
<event timestamp="1064181418" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>hs7iskkr7xrt_1</userId>
</event>
<event timestamp="1064370077" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<externalUserId>1002</externalUserId>
<role>VIEWER</role>
<userId>y096bmb53yu5_1</userId>
<name>Isaías Seelen</name>
</event>
<event timestamp="1064621935" module="CHAT" eventname="PublicChatEvent">
<color>0</color>
<senderId>nfuklna24flg_1</senderId>
<message>
<![CDATA[Public chat 4]]>
</message>
<sender>Ninel Mac Ruaidhrí</sender>
</event>
<event timestamp="1064635147" module="CHAT" eventname="PublicChatEvent">
<senderId>bepguk6d7dza_1</senderId>
<sender>Eva Aquino</sender>
<message>
<![CDATA[Public chat 5]]>
</message>
<color>0</color>
</event>
<event timestamp="1064645678" module="PARTICIPANT" eventname="RecordStatusEvent">
<userId>9izxq660i7vr_1</userId>
<status>true</status>
</event>
<event timestamp="1064656291" module="CHAT" eventname="PublicChatEvent">
<sender>Elias Stablum</sender>
<senderId>0q1hkmla9asu_1</senderId>
<color>0</color>
<message>
<![CDATA[Public chat 6]]>
</message>
</event>
<event timestamp="1064660118" module="CHAT" eventname="PublicChatEvent">
<senderId>fzlsahcijxo4_1</senderId>
<message>
<![CDATA[Public chat 7]]>
</message>
<sender>Arethusa Mann</sender>
<color>0</color>
</event>
<event timestamp="1064669521" module="CHAT" eventname="PublicChatEvent">
<message>
<![CDATA[Public chat 8]]>
</message>
<senderId>nfuklna24flg_1</senderId>
<sender>Ninel Mac Ruaidhrí</sender>
<color>0</color>
</event>
<event timestamp="1064671034" module="CHAT" eventname="PublicChatEvent">
<message>
<![CDATA[Public chat 9]]>
</message>
<color>0</color>
<sender>Mireia Castell</sender>
<senderId>7m940cic73r3_1</senderId>
</event>
<event timestamp="1064679602" module="CHAT" eventname="PublicChatEvent">
<sender>Ninel Mac Ruaidhrí</sender>
<message>
<![CDATA[Public chat 10]]>
</message>
<color>0</color>
<senderId>nfuklna24flg_1</senderId>
</event>
<event timestamp="1066752701" module="CHAT" eventname="PublicChatEvent">
<sender>Xhesika De Lange</sender>
<color>0</color>
<senderId>2dc8jctma0nj_1</senderId>
<message>
<![CDATA[Public chat 11]]>
</message>
</event>
<event timestamp="1066777963" module="CHAT" eventname="PublicChatEvent">
<sender>Arethusa Mann</sender>
<senderId>fzlsahcijxo4_1</senderId>
<color>0</color>
<message>
<![CDATA[Public chat 12]]>
</message>
</event>
<event timestamp="1066909573" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<userId>y096bmb53yu5_2</userId>
<role>VIEWER</role>
<name>Isaías Seelen</name>
<externalUserId>1002</externalUserId>
</event>
<event timestamp="1066966371" module="CHAT" eventname="PublicChatEvent">
<message>
<![CDATA[Public chat 13]]>
</message>
<color>0</color>
<senderId>2dc8jctma0nj_1</senderId>
<sender>Xhesika De Lange</sender>
</event>
<event timestamp="1069500636" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>0q1hkmla9asu_1</userId>
</event>
<event timestamp="1069502806" module="PARTICIPANT" eventname="ParticipantJoinEvent">
<role>VIEWER</role>
<externalUserId>1008</externalUserId>
<userId>yyynfpyca09g_1</userId>
<name>Evelina Keller</name>
</event>
<event timestamp="1069502545" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>nfuklna24flg_1</userId>
</event>
<event timestamp="1069502806" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>yyynfpyca09g_1</userId>
</event>
<event timestamp="1069504995" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>2dc8jctma0nj_1</userId>
</event>
<event timestamp="1069505733" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>bepguk6d7dza_1</userId>
</event>
<event timestamp="1069507663" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>fzlsahcijxo4_1</userId>
</event>
<event timestamp="1069508668" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>7ur69btts657_1</userId>
</event>
<event timestamp="1069509022" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>23uydbo9nauq_1</userId>
</event>
<event timestamp="1069512636" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>12ipastd9pw1_1</userId>
</event>
<event timestamp="1069514445" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>7m940cic73r3_1</userId>
</event>
<event timestamp="1069595144" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>42dnty7rovjt_1</userId>
</event>
<event timestamp="1069686925" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>vvyha6umxoyt_1</userId>
</event>
<event timestamp="1069700912" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>y096bmb53yu5_2</userId>
</event>
<event timestamp="1069717589" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>tgfbj6f828sp_1</userId>
</event>
<event timestamp="1069722053" module="PARTICIPANT" eventname="RecordStatusEvent">
<userId>9izxq660i7vr_1</userId>
<status>false</status>
</event>
<event timestamp="1069746114" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>9izxq660i7vr_1</userId>
</event>
<event timestamp="1069802390" module="PARTICIPANT" eventname="ParticipantLeftEvent">
<userId>yyynfpyca09g_1</userId>
</event>
<event timestamp="1069897986" module="PARTICIPANT" eventname="EndAndKickAllEvent">
</event>
</recording>

View File

@ -28,6 +28,16 @@ redis_port: 6379
# and have another script process it.
store_recording_status: false
# Whether to anonymize the sender of chat messages in the processed
# recordings. The settings here are the defaults; they can be overridden
# by passing meta parameters on the meeting create call.
# meta param: meta_bbb-anonymize-chat (true/false)
anonymize_chat: false
# By default only names of viewers are anonymized - if you would also
# like to anonymize moderators, you can set this to true:
# meta param: meta_bbb-anonymize-chat-moderators (true/false)
anonymize_chat_moderators: false
# Sequence of recording steps. Keys are the current step, values
# are the next step(s). Examples:
# current_step: next_step

View File

@ -0,0 +1,273 @@
# frozen_string_literal: true
require 'minitest/autorun'
require 'nokogiri'
require 'recordandplayback'
class TestEvents < Minitest::Test
def setup
@events_legacy = File.open('resources/raw/1b199e88-7df7-4842-a5f1-0e84b781c5c8/events.xml') do |io|
Nokogiri::XML(io)
end
@events_chat09 = File.open('resources/raw/chat_0_9.xml') do |io|
Nokogiri::XML(io)
end
@events_devcall = File.open('resources/raw/183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1630430006889/events.xml') do |io|
Nokogiri::XML(io)
end
@events_meta_edt = File.open('resources/raw/2a1de53edf0543d950056bf3c0d4d357eba3383f-1630607370684/events.xml') do |io|
Nokogiri::XML(io)
end
end
def test_anonymous_user_map_legacy
map = BigBlueButton::Events.anonymous_user_map(@events_legacy)
assert_empty(map)
end
def test_anonymous_user_map_legacy_no_viewer_only
map = BigBlueButton::Events.anonymous_user_map(@events_legacy, moderators: true)
assert_equal(1, map.length)
assert_equal('Moderator 1', map['1'])
end
def test_anonymous_user_map_bbb_0_9
map = BigBlueButton::Events.anonymous_user_map(@events_chat09)
assert_equal(21, map.length)
assert_equal('Marinda Collins', map['9izxq660i7vr_1']) # Moderator
assert_equal('Viewer 1', map['vvyha6umxoyt_1'])
assert_equal('Viewer 2', map['hs7iskkr7xrt_1'])
assert_equal('Viewer 3', map['7m940cic73r3_1'])
assert_equal('Viewer 4', map['tgfbj6f828sp_1'])
assert_equal('Viewer 5', map['bepguk6d7dza_1'])
assert_equal('Viewer 6', map['66ntqzexswc2_1'])
assert_equal('Viewer 7', map['0q1hkmla9asu_1'])
assert_equal('Viewer 8', map['yyynfpyca09g_1'])
assert_equal('Viewer 9', map['dmsj3897dwss_1'])
assert_equal('Viewer 10', map['42dnty7rovjt_1'])
assert_equal('Viewer 11', map['7ur69btts657_1'])
assert_equal('Viewer 12', map['23uydbo9nauq_1'])
assert_equal('Viewer 6', map['12ipastd9pw1_1'])
assert_equal('Viewer 9', map['ainnu65fiycz_1'])
assert_equal('Viewer 13', map['j73nq5k8xcaa_1'])
assert_equal('Viewer 14', map['nfuklna24flg_1'])
assert_equal('Viewer 9', map['2dc8jctma0nj_1'])
assert_equal('Viewer 13', map['fzlsahcijxo4_1'])
assert_equal('Viewer 2', map['y096bmb53yu5_1'])
assert_equal('Viewer 2', map['y096bmb53yu5_2'])
assert_equal('Viewer 8', map['yyynfpyca09g_1'])
end
def test_anonymous_user_map_bbb_0_9_no_viewer_only
map = BigBlueButton::Events.anonymous_user_map(@events_chat09, moderators: true)
assert_equal(21, map.length)
assert_equal('Moderator 1', map['9izxq660i7vr_1'])
assert_equal('Viewer 1', map['vvyha6umxoyt_1'])
assert_equal('Viewer 2', map['hs7iskkr7xrt_1'])
assert_equal('Viewer 3', map['7m940cic73r3_1'])
assert_equal('Viewer 4', map['tgfbj6f828sp_1'])
assert_equal('Viewer 5', map['bepguk6d7dza_1'])
assert_equal('Viewer 6', map['66ntqzexswc2_1'])
assert_equal('Viewer 7', map['0q1hkmla9asu_1'])
assert_equal('Viewer 8', map['yyynfpyca09g_1'])
assert_equal('Viewer 9', map['dmsj3897dwss_1'])
assert_equal('Viewer 10', map['42dnty7rovjt_1'])
assert_equal('Viewer 11', map['7ur69btts657_1'])
assert_equal('Viewer 12', map['23uydbo9nauq_1'])
assert_equal('Viewer 6', map['12ipastd9pw1_1'])
assert_equal('Viewer 9', map['ainnu65fiycz_1'])
assert_equal('Viewer 13', map['j73nq5k8xcaa_1'])
assert_equal('Viewer 14', map['nfuklna24flg_1'])
assert_equal('Viewer 9', map['2dc8jctma0nj_1'])
assert_equal('Viewer 13', map['fzlsahcijxo4_1'])
assert_equal('Viewer 2', map['y096bmb53yu5_1'])
assert_equal('Viewer 2', map['y096bmb53yu5_2'])
assert_equal('Viewer 8', map['yyynfpyca09g_1'])
end
def test_get_chat_events_legacy
start_time = BigBlueButton::Events.first_event_timestamp(@events_legacy)
end_time = BigBlueButton::Events.last_event_timestamp(@events_legacy)
bbb_props = { 'anonymize_chat' => true, 'anonymize_chat_moderators' => true }
chats_enum = BigBlueButton::Events.get_chat_events(@events_legacy, start_time, end_time, bbb_props).each
chat = chats_enum.next
assert_equal(34_876, chat[:in])
assert_nil(chat.fetch(:out))
assert_nil(chat.fetch(:sender_id))
# Anonymization doesn't work on really old recordings since there's no connection between
# chat user names and user ids
assert_equal('FRED', chat[:sender])
assert_equal('hello', chat[:message])
assert_nil(chat.fetch(:date))
assert_equal(0, chat[:text_color])
chat = chats_enum.next
assert_equal(42_388, chat[:in])
assert_nil(chat.fetch(:out))
assert_equal('FRED', chat[:sender])
assert_equal('how are you?', chat[:message])
chat = chats_enum.next
assert_equal(90_561, chat[:in])
assert_nil(chat.fetch(:out))
assert_equal('FRED', chat[:sender])
assert_equal('hi fred', chat[:message])
assert_raises(StopIteration) { chats_enum.next }
end
def test_get_chat_events_0_9
start_time = BigBlueButton::Events.first_event_timestamp(@events_chat09)
end_time = BigBlueButton::Events.last_event_timestamp(@events_chat09)
chats_enum = BigBlueButton::Events.get_chat_events(@events_chat09, start_time, end_time).each
chat = chats_enum.next
assert_equal(749_025, chat[:in])
assert_equal(988_028, chat[:out])
assert_equal('2dc8jctma0nj_1', chat[:sender_id])
assert_equal('Xhesika De Lange', chat[:sender])
assert_equal('Public chat 1', chat[:message])
assert_nil(chat.fetch(:date))
assert_equal(0, chat[:text_color])
chat = chats_enum.next
assert_equal(972_022, chat[:in])
assert_equal(988_028, chat[:out])
assert_equal('23uydbo9nauq_1', chat[:sender_id])
assert_equal('Asa Darby', chat[:sender])
assert_equal('Public chat 2', chat[:message])
chat = chats_enum.next
assert_equal(1_797_324, chat[:in])
assert_nil(chat.fetch(:out))
assert_equal('hs7iskkr7xrt_1', chat[:sender_id])
assert_equal('Isaías Seelen', chat[:sender])
assert_equal('Public chat 3', chat[:message])
chat = chats_enum.next
assert_equal(3_144_319 - 522_222, chat[:in])
assert_equal('0q1hkmla9asu_1', chat[:sender_id])
assert_equal('Elias Stablum', chat[:sender])
assert_equal('Public chat 6', chat[:message])
chat = chats_enum.next
assert_equal(3_148_146 - 522_222, chat[:in])
assert_equal('fzlsahcijxo4_1', chat[:sender_id])
assert_equal('Arethusa Mann', chat[:sender])
assert_equal('Public chat 7', chat[:message])
chat = chats_enum.next
assert_equal(3_157_549 - 522_222, chat[:in])
assert_equal('nfuklna24flg_1', chat[:sender_id])
assert_equal('Ninel Mac Ruaidhrí', chat[:sender])
assert_equal('Public chat 8', chat[:message])
chat = chats_enum.next
assert_equal(3_159_062 - 522_222, chat[:in])
assert_equal('7m940cic73r3_1', chat[:sender_id])
assert_equal('Mireia Castell', chat[:sender])
assert_equal('Public chat 9', chat[:message])
chat = chats_enum.next
assert_equal(3_167_630 - 522_222, chat[:in])
assert_equal('nfuklna24flg_1', chat[:sender_id])
assert_equal('Ninel Mac Ruaidhrí', chat[:sender])
assert_equal('Public chat 10', chat[:message])
chat = chats_enum.next
assert_equal(5_240_729 - 522_222, chat[:in])
assert_equal('2dc8jctma0nj_1', chat[:sender_id])
assert_equal('Xhesika De Lange', chat[:sender])
assert_equal('Public chat 11', chat[:message])
chat = chats_enum.next
assert_equal(5_265_991 - 522_222, chat[:in])
assert_equal('fzlsahcijxo4_1', chat[:sender_id])
assert_equal('Arethusa Mann', chat[:sender])
assert_equal('Public chat 12', chat[:message])
chat = chats_enum.next
assert_equal(5_454_399 - 522_222, chat[:in])
assert_equal('2dc8jctma0nj_1', chat[:sender_id])
assert_equal('Xhesika De Lange', chat[:sender])
assert_equal('Public chat 13', chat[:message])
assert_raises(StopIteration) { chats_enum.next }
end
def test_get_chat_events_0_9_anonymized
start_time = BigBlueButton::Events.first_event_timestamp(@events_chat09)
end_time = BigBlueButton::Events.last_event_timestamp(@events_chat09)
bbb_props = { 'anonymize_chat' => true }
chats_enum = BigBlueButton::Events.get_chat_events(@events_chat09, start_time, end_time, bbb_props).each
assert_equal('Viewer 9', chats_enum.next[:sender])
assert_equal('Viewer 12', chats_enum.next[:sender])
assert_equal('Viewer 2', chats_enum.next[:sender])
assert_equal('Viewer 7', chats_enum.next[:sender])
assert_equal('Viewer 13', chats_enum.next[:sender])
assert_equal('Viewer 14', chats_enum.next[:sender])
assert_equal('Viewer 3', chats_enum.next[:sender])
assert_equal('Viewer 14', chats_enum.next[:sender])
assert_equal('Viewer 9', chats_enum.next[:sender])
assert_equal('Viewer 13', chats_enum.next[:sender])
assert_equal('Viewer 9', chats_enum.next[:sender])
assert_raises(StopIteration) { chats_enum.next }
end
def test_get_chat_events_0_9_start_time
end_time = BigBlueButton::Events.last_event_timestamp(@events_chat09)
chats = BigBlueButton::Events.get_chat_events(@events_chat09, 1_063_007_465, end_time)
chat = chats.first
assert_equal(301_831, chat[:in])
assert_equal('hs7iskkr7xrt_1', chat[:sender_id])
assert_equal('Isaías Seelen', chat[:sender])
assert_equal('Public chat 3', chat[:message])
end
def test_get_chat_events_0_9_end_time
start_time = BigBlueButton::Events.first_event_timestamp(@events_chat09)
chats = BigBlueButton::Events.get_chat_events(@events_chat09, start_time, 1_062_490_000)
chat = chats.last
assert_equal(972_022, chat[:in])
assert_nil(chat.fetch(:out))
assert_equal('23uydbo9nauq_1', chat[:sender_id])
assert_equal('Asa Darby', chat[:sender])
assert_equal('Public chat 2', chat[:message])
end
def test_get_chat_events_devcall
start_time = BigBlueButton::Events.first_event_timestamp(@events_devcall)
end_time = BigBlueButton::Events.last_event_timestamp(@events_devcall)
chats = BigBlueButton::Events.get_chat_events(@events_devcall, start_time, end_time)
assert_equal(11, chats.length)
chat = chats[0]
assert_equal(17_013, chat[:in])
assert_equal(148_701, chat[:out])
assert_equal('w_kmm96j1as24f', chat[:sender_id])
assert_equal('Mario', chat[:sender])
assert_equal('#7b1fa2', chat[:avatar_color])
assert_nil(chat.fetch(:text_color))
assert_equal(DateTime.rfc3339('2021-08-31T18:06:14.330+00:00'), chat[:date])
# rubocop:disable Layout/LineLength
assert_equal(
"i get logs of these: \n\n ERROR: clientLogger: Camera VIEWER failed. Reconnecting. <a href=\"https://develop.bigbluebutton.org/html5client/8fb14b479570f65105c7ff9a2960b679501f34ff.js?meteor_js_resource=true:348:579447\" rel=\"nofollow\"><u>https://develop.bigbluebutton.org/html5client/8fb14b479570f65105c7ff9a2960b679501f34ff.js?meteor_js_resource=true:348:579447</u></a>",
chat[:message]
)
# rubocop:enable Layout/LineLength
chat = chats[7]
assert_equal(1_241_014, chat[:in])
assert_nil(chat.fetch(:out))
assert_equal('w_vk0ebqjxox9d', chat[:sender_id])
assert_equal('Anton G', chat[:sender])
assert_equal('#0277bd', chat[:avatar_color])
assert_nil(chat.fetch(:text_color))
assert_equal(DateTime.rfc3339('2021-08-31T18:26:38.332+00:00'), chat[:date])
assert_equal('Nice!', chat[:message])
end
def test_get_chat_events_meta_edt
start_time = BigBlueButton::Events.first_event_timestamp(@events_meta_edt)
end_time = BigBlueButton::Events.last_event_timestamp(@events_meta_edt)
chats = BigBlueButton::Events.get_chat_events(@events_meta_edt, start_time, end_time)
assert_equal(1, chats.length)
chat = chats[0]
assert_equal(11_635, chat[:in])
assert_nil(chat.fetch(:out))
assert_equal('w_tvumguamxhhs', chat[:sender_id])
# This recording has the meta_bbb-anonymize-chat param set
assert_equal('Viewer 1', chat[:sender])
assert_equal(DateTime.rfc3339('2021-09-02T14:33:02.214-04:00'), chat[:date])
assert_equal('whoops, forgot to start recording…', chat[:message])
end
end

View File

@ -1056,33 +1056,21 @@ def processPresentation(package_dir)
File.write("#{package_dir}/#{$cursor_xml_filename}", cursors_doc.to_xml)
end
def processChatMessages
def processChatMessages(events, bbb_props)
BigBlueButton.logger.info("Processing chat events")
# Create slides.xml and chat.
$slides_doc = Nokogiri::XML::Builder.new do |xml|
$xml = xml
$xml.popcorn {
# Process chat events.
current_time = 0
$rec_events.each do |re|
$chat_events.each do |node|
if (node[:timestamp].to_i >= re[:start_timestamp] and node[:timestamp].to_i <= re[:stop_timestamp])
chat_timestamp = node[:timestamp]
chat_sender = node.xpath(".//sender")[0].text()
chat_message = BigBlueButton::Events.linkify(node.xpath(".//message")[0].text())
chat_start = ( translateTimestamp(chat_timestamp) / 1000).to_i
# Creates a list of the clear timestamps that matter for this message
next_clear_timestamps = $clear_chat_timestamps.select{ |e| e >= node[:timestamp] }
# If there is none we skip it, or else we add the out time that will remove a message
if next_clear_timestamps.empty?
$xml.chattimeline(:in => chat_start, :direction => :down, :name => chat_sender, :message => chat_message, :target => :chat )
else
chat_end = ( translateTimestamp( next_clear_timestamps.first ) / 1000).to_i
$xml.chattimeline(:in => chat_start, :out => chat_end, :direction => :down, :name => chat_sender, :message => chat_message, :target => :chat )
end
end
end
current_time += re[:stop_timestamp] - re[:start_timestamp]
Nokogiri::XML::Builder.new do |xml|
xml.popcorn {
BigBlueButton::Events.get_chat_events(events, $meeting_start.to_i, $meeting_end.to_i, bbb_props).each do |chat|
chattimeline = {
in: (chat[:in] / 1000.0).round(1),
direction: 'down',
name: chat[:sender],
message: chat[:message],
target: 'chat'
}
chattimeline[:out] = (chat[:out] / 1000.0).round(1) unless chat[:out].nil?
xml.chattimeline(**chattimeline)
end
}
end
@ -1414,18 +1402,11 @@ begin
#Create slides.xml
BigBlueButton.logger.info("Generating xml for slides and chat")
# Gathering all the events from the events.xml
$chat_events = @doc.xpath("//event[@eventname='PublicChatEvent']")
# Create a list of timestamps when the moderator cleared the public chat
$clear_chat_timestamps = [ ]
clear_chat_events = @doc.xpath("//event[@eventname='ClearPublicChatEvent']")
clear_chat_events.each { |clear| $clear_chat_timestamps << clear[:timestamp] }
$clear_chat_timestamps.sort!
calculateRecordEventsOffset()
processChatMessages()
# Write slides.xml to file
slides_doc = processChatMessages(@doc, bbb_props)
File.open("#{package_dir}/slides_new.xml", 'w') { |f| f.puts slides_doc.to_xml }
processPresentation(package_dir)
@ -1435,9 +1416,6 @@ begin
processExternalVideoEvents(@doc, package_dir)
# Write slides.xml to file
File.open("#{package_dir}/slides_new.xml", 'w') { |f| f.puts $slides_doc.to_xml }
# Write deskshare.xml to file
File.open("#{package_dir}/#{$deskshare_xml_filename}", 'w') { |f| f.puts $deskshare_xml.to_xml }