diff --git a/README.md b/README.md
index 5911959e39653312df2a76a3d00c10a1356fdf87..43c49d163d229fc8710bdd8630775994b21bb9da 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,17 @@
 BigBlueButton
 =============
-BigBlueButton is an open source web conferencing system for online learning.  
+BigBlueButton is an open source web conferencing system.  
 
-We believe that every student with a web browser should have access to a high-quality online learning experience.  
+BigBlueButton supports real-time sharing of audio, video, slides (with witeboard controls), chat, and the screen.  Instructors can engage remote students with polling, emojis, and breakout rooms.  BigBlueButton can record and playback all content shared in a session.
 
-BigBlueButton supports real-time sharing of audio, video, slides (with annotations), chat, slides, and screen.  Instructors can engage students with polls, emojis, and breakout rooms.  BigBlueButton can record and playback all content shared in a session.
-
-The use cases for BigBlueButton are
+We designed BigBlueButton for online learning (though it can be used for many [other applications](http://www.c4isrnet.com/story/military-tech/disa/2015/02/11/disa-to-save-12m-defense-collaboration-services/23238997/)).  The educational use cases for BigBlueButton are
 
   * One-to-one on-line tutoring
   * Small group collaboration 
   * On-line classes
 
-The BigBlueButton server runs under Ubuntu 16.04 64-bit.  Using packages provided by the project, the overall time for setting up a new server should be about 30 minutes.
+BigBlueButton runs on a Ubuntu 16.04 64-bit server.  If you follow the [installation instructions](http://docs.bigbluebutton.org/install/install.html), we gaurantee you will have BigBlueButton installed and running within 30 minutes (or your money back :-).
 
-For more information on the latest release -- including installation instructions, GreenLight front-end, integrating BigBlueButton with you applications, and detailed technical documentation -- see [http://docs.bigbluebutton.org/](http://docs.bigbluebutton.org/).
+For full technical documentation BigBlueButton -- including architecture, features, API, and GreenLight (the default front-end) -- see [http://docs.bigbluebutton.org/](http://docs.bigbluebutton.org/).
 
 BigBlueButton and the BigBlueButton Logo are trademarks of [BigBlueButton Inc](http://bigbluebutton.org) .
diff --git a/bbb-webhooks/README.md b/bbb-webhooks/README.md
index 5c31849942520dde64df6108172fe66510777de3..0db0618660150c4375db3c0ed939fc271a82a3ba 100644
--- a/bbb-webhooks/README.md
+++ b/bbb-webhooks/README.md
@@ -11,14 +11,14 @@ Development
 
 1. Install node. You can use [NVM](https://github.com/creationix/nvm) if you need multiple versions of node or install it from source. To install from source, first check the exact version you need on `package.json` and replace the all `vX.X.X` by the correct version when running the commands below.
 
-    ```bash
+~~~
 wget http://nodejs.org/dist/vX.X.X/node-vX.X.X.tar.gz
 tar -xvf node-vX.X.X.tar.gz
 cd node-vX.X.X/
 ./configure
 make
 sudo make install
-    ```
+~~~
 
 2. Install the dependencies: `npm install`
 
@@ -26,9 +26,9 @@ sudo make install
 
 4. Run the application with:
 
-    ```bash
+~~~
 node app.js
-    ```
+~~~
 
 5. To test it you can use the test application `post_catcher.js`. It starts a node app that
   registers a hook on the webhooks app and prints all the events it receives. Open the file
@@ -44,14 +44,14 @@ Production
 
 1. Install node. First check the exact version you need on `package.json` and replace the all `vX.X.X` by the correct version in the commands below.
 
-    ```bash
+~~~
 wget http://nodejs.org/dist/vX.X.X/node-vX.X.X.tar.gz
 tar -xvf node-vX.X.X.tar.gz
 cd node-vX.X.X/
 ./configure
 make
 sudo make install
-    ```
+~~~
 
 2. Copy the entire webhooks directory to `/usr/local/bigbluebutton/bbb-webhooks` and cd into it.
 
@@ -64,34 +64,34 @@ sudo make install
 
 6. Copy upstart's configuration file and make sure its permissions are ok:
 
-    ```bash
+~~~
 sudo cp config/upstart-bbb-webhooks.conf /etc/init/bbb-webhooks.conf
 sudo chown root:root /etc/init/bbb-webhooks.conf
-    ```
+~~~
 
     Open the file and edit it. You might need to change things like the user used to run the application.
 
 7. Copy monit's configuration file and make sure its permissions are ok:
 
-    ```bash
+~~~
 sudo cp config/monit-bbb-webhooks /etc/monit/conf.d/bbb-webhooks
 sudo chown root:root /etc/monit/conf.d/bbb-webhooks
-    ```
+~~~
 
     Open the file and edit it. You might need to change things like the port used by the application.
 
 8. Copy logrotate's configuration file and install it:
 
-    ```bash
+~~~
 sudo cp config/bbb-webhooks.logrotate /etc/logrotate.d/bbb-webhooks
 sudo chown root:root /etc/logrotate.d/bbb-webhooks
 sudo chmod 644 /etc/logrotate.d/bbb-webhooks
 sudo logrotate -s /var/lib/logrotate/status /etc/logrotate.d/bbb-webhooks
-    ```
+~~~
 
 9. Start the application:
 
-    ```bash
+~~~
 sudo service bbb-webhooks start
 sudo service bbb-webhooks stop
-    ```
+~~~
diff --git a/bigbluebutton-html5/imports/api/chat/server/methods/sendChat.js b/bigbluebutton-html5/imports/api/chat/server/methods/sendChat.js
index f8779e799f926ffe7b5a9edc783629b8cd6d7f14..fdbf7f45ffe7af0450e88dd794268535e1ecc520 100755
--- a/bigbluebutton-html5/imports/api/chat/server/methods/sendChat.js
+++ b/bigbluebutton-html5/imports/api/chat/server/methods/sendChat.js
@@ -48,6 +48,8 @@ export default function sendChat(credentials, message) {
   let actionName = message.to_userid === requesterUserId ? 'chatSelf' : 'chatPrivate';
   let eventName = 'send_private_chat_message';
 
+  message.message = parseMessage(message.message);
+
   if (message.chat_type === PUBLIC_CHAT_TYPE) {
     eventName = 'send_public_chat_message';
     actionName = 'chatPublic';
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx
index b663e4dbcf5f06344758caf7f2859e6bf526d04c..e3a926a672419aa8e9e6d807798140da65b931cb 100755
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx
@@ -58,7 +58,7 @@ class ActionsDropdown extends Component {
     return null; // temporarily disabling the functionality
 
     return (
-      <Dropdown ref="dropdown">
+      <Dropdown>
         <DropdownTrigger>
           <Button
             label={intl.formatMessage(intlMessages.actionsLabel)}
diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx
index b4372f494cc19551675b2183e956f0b1a92253d9..99a433f189046a6f9dc85c34d67e1877c7e72861 100755
--- a/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx
@@ -61,7 +61,7 @@ class MessageForm extends Component {
         cancelable: true,
       });
 
-      this.refs.form.dispatchEvent(event);
+      this.form.dispatchEvent(event);
     }
   }
 
@@ -124,7 +124,7 @@ class MessageForm extends Component {
 
     return (
       <form
-        ref="form"
+        ref={(ref) => { this.form = ref; }}
         className={cx(this.props.className, styles.form)}
         onSubmit={this.handleSubmit}
       >
diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx
index b442c8ea28f3efae0a300779630f6a06f2e11474..63360a2b59d72989e786fa59fb13663ea1790031 100755
--- a/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx
@@ -35,7 +35,7 @@ class MessageList extends Component {
   }
 
   scrollTo(position = null) {
-    const { scrollArea } = this.refs;
+    const { scrollArea } = this;
 
     if (position === null) {
       position = scrollArea.scrollHeight - scrollArea.clientHeight;
@@ -68,7 +68,7 @@ class MessageList extends Component {
 
   componentWillReceiveProps(nextProps) {
     if (this.props.chatId !== nextProps.chatId) {
-      const { scrollArea } = this.refs;
+      const { scrollArea } = this;
       this.handleScrollUpdate(scrollArea.scrollTop, scrollArea);
     }
   }
@@ -79,7 +79,7 @@ class MessageList extends Component {
       return;
     }
 
-    const { scrollArea } = this.refs;
+    const { scrollArea } = this;
 
     const position = scrollArea.scrollTop + scrollArea.offsetHeight;
 
@@ -101,14 +101,14 @@ class MessageList extends Component {
   }
 
   componentDidMount() {
-    const { scrollArea } = this.refs;
+    const { scrollArea } = this;
 
     this.scrollTo(this.props.scrollPosition);
     scrollArea.addEventListener('scroll', this.handleScrollChange, false);
   }
 
   componentWillUnmount() {
-    const { scrollArea } = this.refs;
+    const { scrollArea } = this;
 
     this.handleScrollUpdate(scrollArea.scrollTop, scrollArea);
     scrollArea.removeEventListener('scroll', this.handleScrollChange, false);
@@ -144,7 +144,7 @@ class MessageList extends Component {
         <div
           role="log"
           tabIndex="0"
-          ref="scrollArea"
+          ref={(ref) => { this.scrollArea = ref; }}
           id={this.props.id}
           className={styles.messageList}
           aria-live="polite"
diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx
index 5786f55d5ba4f19a847ceb500b27ef66b40ec15b..41ffba4af9c8300a14c61e18a3428f7096cf14ee 100755
--- a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx
@@ -48,7 +48,7 @@ export default class MessageListItem extends Component {
 
   handleMessageInViewport() {
     window.requestAnimationFrame(() => {
-      const node = this.refs.item;
+      const node = this.item;
       this.setState({ preventRender: !isElementInViewport(node) });
     });
   }
@@ -103,7 +103,7 @@ export default class MessageListItem extends Component {
 
     return (
       <div className={styles.item}>
-        <div className={styles.wrapper} ref="item">
+        <div className={styles.wrapper} ref={(ref) => { this.item = ref; }}>
           <div className={styles.avatar}>
             <UserAvatar user={user} />
           </div>
@@ -143,7 +143,7 @@ export default class MessageListItem extends Component {
 
     return (
       <div className={cx(styles.item, styles.systemMessage)}>
-        <div className={styles.content} ref="item">
+        <div className={styles.content} ref={(ref) => { this.item = ref; }}>
           <div className={styles.messages}>
             {messages.map((message, i) => (
               <Message
diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/message/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/message/component.jsx
index c2bfb9ad549d36cd64e6e1bd896643836c46b020..d05b068038eac47111f695de095b63bcbb9a1e42 100755
--- a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/message/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/message/component.jsx
@@ -40,7 +40,7 @@ export default class MessageListItem extends Component {
   handleMessageInViewport(e) {
     if (!this.ticking) {
       window.requestAnimationFrame(() => {
-        const node = this.refs.text;
+        const node = this.text;
         const scrollArea = document.getElementById(this.props.chatAreaId);
 
         if (isElementInViewport(node)) {
@@ -62,7 +62,7 @@ export default class MessageListItem extends Component {
       return;
     }
 
-    const node = this.refs.text;
+    const node = this.text;
 
     if (isElementInViewport(node)) {
       this.props.handleReadMessage(this.props.time);
@@ -93,7 +93,7 @@ export default class MessageListItem extends Component {
 
     return (
       <p
-        ref="text"
+        ref={(ref) => { this.text = ref; }}
         dangerouslySetInnerHTML={{ __html: text }}
         className={this.props.className}
       />
diff --git a/bigbluebutton-html5/imports/ui/components/closed-captions/component.jsx b/bigbluebutton-html5/imports/ui/components/closed-captions/component.jsx
index a29c96033c4020f91e8918932f00227ac02c023e..a4581c6c6f03335bd8c2160e3e15073be5fa74fb 100755
--- a/bigbluebutton-html5/imports/ui/components/closed-captions/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/closed-captions/component.jsx
@@ -30,9 +30,8 @@ export default class ClosedCaptions extends React.Component {
   }
 
   componentWillUpdate() {
-    const { ccScrollArea } = this.refs;
 
-    const node = findDOMNode(ccScrollArea);
+    const node = findDOMNode(this.ccScrollArea);
 
     // number 4 is for the border
     // offset height includes the border, but scrollheight doesn't
@@ -41,8 +40,7 @@ export default class ClosedCaptions extends React.Component {
 
   componentDidUpdate() {
     if (this.shouldScrollBottom) {
-      const { ccScrollArea } = this.refs;
-      const node = findDOMNode(ccScrollArea);
+      const node = findDOMNode(this.ccScrollArea);
       node.scrollTop = node.scrollHeight;
     }
   }
@@ -60,7 +58,7 @@ export default class ClosedCaptions extends React.Component {
           <p> {locale || 'Locale is not selected'} </p>
         </div>
         <div
-          ref="ccScrollArea"
+          ref={(ref) => { this.ccScrollArea = ref; }}
           className={styles.frame}
           style={{ background: backgroundColor }}
         >
diff --git a/bigbluebutton-html5/imports/ui/components/closed-captions/styles.scss b/bigbluebutton-html5/imports/ui/components/closed-captions/styles.scss
index 2aea8ed05882fdc4c9bb9fb4f0c557478b5aa2f4..0389830e35db3078a09eb3a66c36fda934f17ab8 100755
--- a/bigbluebutton-html5/imports/ui/components/closed-captions/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/closed-captions/styles.scss
@@ -7,6 +7,7 @@
   height:100%;
   overflow-y: auto;
   overflow-x: hidden;
+  position:absolute;
 }
 
 .frame {
@@ -14,7 +15,6 @@
   height: calc(100% - 80px);
   margin: 20px;
   overflow: auto;
-
   //if you change the thickness of the border
   //you should also change a corresponding value in the componentWillUpdate
   //otherwise auto-scrolling for closed-captions will break
diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx
index dd07ade52b5005d6ca329828119b51f1dcf67386..927a9e11d7985120f4ba30b679b07ef9a4b0b37a 100755
--- a/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx
@@ -98,7 +98,7 @@ class Dropdown extends Component {
     this.setState({ isOpen: false }, this.handleStateCallback);
 
     if (autoFocus) {
-      const triggerElement = findDOMNode(this.refs.trigger);
+      const triggerElement = findDOMNode(this.trigger);
       triggerElement.focus();
     }
   }
@@ -134,14 +134,14 @@ class Dropdown extends Component {
     let content = children.find(x => x.type === DropdownContent);
 
     trigger = React.cloneElement(trigger, {
-      ref: 'trigger',
+      ref: (ref) => { this.trigger = ref; },
       dropdownToggle: this.handleToggle,
       dropdownShow: this.handleShow,
       dropdownHide: this.handleHide,
     });
 
     content = React.cloneElement(content, {
-      ref: 'content',
+      ref: (ref) => { this.content = ref; },
       'aria-expanded': this.state.isOpen,
       dropdownToggle: this.handleToggle,
       dropdownShow: this.handleShow,
diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
index f01a3bdf50c63d5547ac758c65a05d156e0a78b7..62a1707d94c5a4c3606882c5d5a931fc8e52ed89 100755
--- a/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/nav-bar/component.jsx
@@ -126,10 +126,7 @@ class NavBar extends Component {
     }
 
     return (
-      <Dropdown
-        isOpen={this.state.isActionsOpen}
-        ref="dropdown"
-      >
+      <Dropdown isOpen={this.state.isActionsOpen}>
         <DropdownTrigger>
           <h1 className={cx(styles.presentationTitle, styles.dropdownBreakout)}>
             {presentationTitle} <Icon iconName="down-arrow" />
diff --git a/bigbluebutton-html5/imports/ui/components/switch/component.jsx b/bigbluebutton-html5/imports/ui/components/switch/component.jsx
old mode 100644
new mode 100755
index 9e86014c252bc4ed621f08017737cd912ade6fb2..6cc7e3a4182e41cc380657ac3e5c3365fbc8b34f
--- a/bigbluebutton-html5/imports/ui/components/switch/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/switch/component.jsx
@@ -39,7 +39,6 @@ export default class Switch extends Toggle {
         <input
           {...inputProps}
           ref={ref => { this.input = ref; }}
-
           onFocus={this.handleFocus}
           onBlur={this.handleBlur}
           className='react-toggle-screenreader-only'
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx
index 161088c7f5842015e41f6d054a2f7714a639ba42..343bf0b0a14a67554e141f8a0a257110a8603a08 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx
@@ -206,12 +206,12 @@ class UserList extends Component {
         <div
           tabIndex={0}
           className={styles.scrollableList}
-          ref={r => this._msgsList = r}
+          ref={(ref) => { this._msgsList = ref; }}
         >
           <CSSTransitionGroup
             transitionName={listTransition}
-            transitionAppear
-            transitionEnter
+            transitionAppear={true}
+            transitionEnter={true}
             transitionLeave={false}
             transitionAppearTimeout={0}
             transitionEnterTimeout={0}
@@ -219,7 +219,7 @@ class UserList extends Component {
             component="div"
             className={cx(styles.chatsList, styles.scrollableList)}
           >
-            <div ref={r => this._msgItems = r}>
+            <div ref={(ref) => { this._msgItems = ref; }}>
               {openChats.map(chat => (
                 <ChatListItem
                   compact={this.state.compact}
@@ -291,20 +291,20 @@ class UserList extends Component {
         <div
           className={styles.scrollableList}
           tabIndex={0}
-          ref={r => this._usersList = r}
+          ref={(ref) => { this._usersList = ref; }}
         >
           <CSSTransitionGroup
             transitionName={listTransition}
-            transitionAppear
-            transitionEnter
-            transitionLeave
+            transitionAppear={true}
+            transitionEnter={true}
+            transitionLeave={true}
             transitionAppearTimeout={0}
             transitionEnterTimeout={0}
             transitionLeaveTimeout={0}
             component="div"
             className={cx(styles.participantsList, styles.scrollableList)}
           >
-            <div ref={r => this._userItems = r}>
+            <div ref={(ref) => { this._userItems = ref; }}>
               {
                 users.map(user => (
                   <UserListItem
@@ -315,7 +315,8 @@ class UserList extends Component {
                     currentUser={currentUser}
                     userActions={userActions}
                     meeting={meeting}
-                  />))
+                  />
+                ))
               }
             </div>
           </CSSTransitionGroup>
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/component.jsx
index 638d5f238db1161c07675ef8a526ba92b4f1b4c1..e8a2d017ae0053b1d915fd874ca6d18f1ebb4220 100755
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/component.jsx
@@ -154,7 +154,7 @@ class UserListItem extends Component {
    */
   checkDropdownDirection() {
     if (this.isDropdownActivedByUser()) {
-      const dropdown = findDOMNode(this.refs.dropdown);
+      const dropdown = findDOMNode(this.dropdown);
       const dropdownTrigger = dropdown.children[0];
       const dropdownContent = dropdown.children[1];
 
@@ -201,7 +201,7 @@ class UserListItem extends Component {
   }
 
   onActionsShow() {
-    const dropdown = findDOMNode(this.refs.dropdown);
+    const dropdown = findDOMNode(this.dropdown);
     const scrollContainer = dropdown.parentElement.parentElement;
     const dropdownTrigger = dropdown.children[0];
 
@@ -275,7 +275,7 @@ class UserListItem extends Component {
 
     return (
       <Dropdown
-        ref="dropdown"
+        ref={(ref) => { this.dropdown = ref; }}
         isOpen={this.state.isActionsOpen}
         onShow={this.onActionsShow}
         onHide={this.onActionsHide}
@@ -338,7 +338,7 @@ class UserListItem extends Component {
 
     return (
       <Dropdown
-        ref="dropdown"
+        ref={(ref) => { this.dropdown = ref; }}
         isOpen={this.state.isActionsOpen}
         onShow={this.onActionsShow}
         onHide={this.onActionsHide}
diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/shapes/poll/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/shapes/poll/component.jsx
index df65baa57e774d8e2751bcd3b917e2992da18807..c4ef2bac5bae468422c78a2d4f2fcb453afc89d8 100755
--- a/bigbluebutton-html5/imports/ui/components/whiteboard/shapes/poll/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/whiteboard/shapes/poll/component.jsx
@@ -163,9 +163,9 @@ export default class PollDrawComponent extends React.Component {
       const key = `${this.props.shape.id}_key_${this.state.currentLine}`;
       const votes = `${this.props.shape.id}_votes_${this.state.currentLine}`;
       const percent = `${this.props.shape.id}_percent_${this.state.currentLine}`;
-      const keySizes = findDOMNode(this.refs[key]).getBBox();
-      const voteSizes = findDOMNode(this.refs[votes]).getBBox();
-      const percSizes = findDOMNode(this.refs[percent]).getBBox();
+      const keySizes = findDOMNode(this[key]).getBBox();
+      const voteSizes = findDOMNode(this[votes]).getBBox();
+      const percSizes = findDOMNode(this[percent]).getBBox();
 
       // first check if we can still increase the font-size
       if (this.state.fontSizeDirection == 1) {
@@ -218,8 +218,8 @@ export default class PollDrawComponent extends React.Component {
     for (let i = 0; i < this.state.textArray.length; ++i) {
       const key = `${this.props.shape.id}_key_${i}`;
       const percent = `${this.props.shape.id}_percent_${i}`;
-      const keySizes = findDOMNode(this.refs[key]).getBBox();
-      const percSizes = findDOMNode(this.refs[percent]).getBBox();
+      const keySizes = findDOMNode(this[key]).getBBox();
+      const percSizes = findDOMNode(this[percent]).getBBox();
 
       if (keySizes.width > maxLeftWidth) {
         maxLeftWidth = keySizes.width;
@@ -239,8 +239,8 @@ export default class PollDrawComponent extends React.Component {
     }
 
     const digitRef = `${this.props.shape.id}_digit`;
-    const maxDigitWidth = findDOMNode(this.refs[digitRef]).getBBox().width;
-    const maxDigitHeight = findDOMNode(this.refs[digitRef]).getBBox().height;
+    const maxDigitWidth = findDOMNode(this[digitRef]).getBBox().width;
+    const maxDigitHeight = findDOMNode(this[digitRef]).getBBox().height;
 
     this.setState({
       maxLeftWidth,
@@ -453,7 +453,7 @@ export default class PollDrawComponent extends React.Component {
         <text
           fontFamily="Arial"
           fontSize={this.state.calcFontSize}
-          ref={`${this.props.shape.id}_key_${line[3]}`}
+          ref={(ref) => { this[`${this.props.shape.id}_key_${line[3]}`] = ref; }}
         >
           <tspan>
             {line[0]}
@@ -462,7 +462,7 @@ export default class PollDrawComponent extends React.Component {
         <text
           fontFamily="Arial"
           fontSize={this.state.calcFontSize}
-          ref={`${this.props.shape.id}_votes_${line[3]}`}
+          ref={(ref) => { this[`${this.props.shape.id}_votes_${line[3]}`] = ref; }}
         >
           <tspan>
             {line[1]}
@@ -471,7 +471,7 @@ export default class PollDrawComponent extends React.Component {
         <text
           fontFamily="Arial"
           fontSize={this.state.calcFontSize}
-          ref={`${this.props.shape.id}_percent_${line[3]}`}
+          ref={(ref) => { this[`${this.props.shape.id}_percent_${line[3]}`] = ref; }}
         >
           <tspan>
             {line[2]}
@@ -492,15 +492,16 @@ export default class PollDrawComponent extends React.Component {
       <g>
         {this.state.textArray.map(line =>
             this.renderLine(line),
-          )}
+          )
+        }
         <text
           fontFamily="Arial"
           fontSize={this.state.calcFontSize}
-          ref={`${this.props.shape.id}_digit`}
+          ref={(ref) => { this[`${this.props.shape.id}_digit`] = ref; }}
         >
           <tspan>
               0
-            </tspan>
+          </tspan>
         </text>
       </g>
     );
diff --git a/bigbluebutton-html5/package-lock.json b/bigbluebutton-html5/package-lock.json
index 4e76104c4c83142b968159a0d883ec4b77005a25..b717961ee23b44d86d8de3dc711f770f41d590e2 100644
--- a/bigbluebutton-html5/package-lock.json
+++ b/bigbluebutton-html5/package-lock.json
@@ -3389,12 +3389,6 @@
       "version": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
       "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
     },
-    "shebang-command": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
-      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
-      "dev": true
-    },
     "shebang-regex": {
       "version": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
       "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
diff --git a/bigbluebutton-html5/package.json b/bigbluebutton-html5/package.json
old mode 100755
new mode 100644
index 10025ea752062bcb27b8d12d38f6eddaaafd127f..172f773ba9b89fc24832e164ff78217906b2c05b
--- a/bigbluebutton-html5/package.json
+++ b/bigbluebutton-html5/package.json
@@ -4,13 +4,16 @@
   "scripts": {
     "start": "cd app;./start.sh",
     "preinstall": "npm install grunt-cli",
-    "test": "cd app;JASMINE_BROWSER=PhantomJS JASMINE_MIRROR_PORT=3000 meteor run --test",
+    "test": "wdio ./tests/webdriverio/wdio.conf.js",
     "lint": "eslint . --ext .jsx,.js",
     "precommit": "lint-staged"
   },
   "lint-staged": {
     "gitDir": "../",
-    "*.{js,jsx}": ["eslint --fix", "git add"]
+    "*.{js,jsx}": [
+      "eslint --fix",
+      "git add"
+    ]
   },
   "dependencies": {
     "babel-runtime": "^6.23.0",
@@ -39,6 +42,7 @@
     "xml2js": "^0.4.17"
   },
   "devDependencies": {
+    "chai": "^3.5.0",
     "autoprefixer": "^7.1.1",
     "eslint": "~3.19.0",
     "eslint-config-airbnb": "^15.0.1",
@@ -55,6 +59,10 @@
     "grunt-shell": "~1.2.1",
     "husky": "^0.13.4",
     "lint-staged": "^3.6.0",
+    "wdio-jasmine-framework": "^0.3.1",
+    "wdio-junit-reporter": "^0.3.0",
+    "wdio-spec-reporter": "^0.1.0",
+    "webdriverio": "^4.8.0",
     "load-grunt-tasks": "~3.4.1",
     "postcss-modules-extract-imports": "1.1.0",
     "postcss-modules-local-by-default": "1.2.0",
diff --git a/bigbluebutton-html5/start.sh b/bigbluebutton-html5/start.sh
index 8da3cbeae88020387f309e7a3bc1f3abe0eda733..8a5827bb2a7233af7010038ce1589109fd8b5027 100755
--- a/bigbluebutton-html5/start.sh
+++ b/bigbluebutton-html5/start.sh
@@ -3,5 +3,5 @@
 # Change to start meteor in production or development mode
 ENVIRONMENT_TYPE=development
 
-JASMINE_SERVER_UNIT=0 JASMINE_SERVER_INTEGRATION=0 JASMINE_CLIENT_INTEGRATION=0 JASMINE_BROWSER=PhantomJS JASMINE_MIRROR_PORT=3000 ROOT_URL=http://127.0.0.1/html5client NODE_ENV=$ENVIRONMENT_TYPE meteor
+ROOT_URL=http://127.0.0.1/html5client NODE_ENV=$ENVIRONMENT_TYPE meteor
 
diff --git a/bigbluebutton-html5/tests/jasmine/client/integration/templates/usersList/usersListSpec.js b/bigbluebutton-html5/tests/jasmine/client/integration/templates/usersList/usersListSpec.js
deleted file mode 100755
index 45ca596a49bc74763099c3d86001857cc05643a4..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/tests/jasmine/client/integration/templates/usersList/usersListSpec.js
+++ /dev/null
@@ -1,266 +0,0 @@
-var emptyUsersCollection = function () {
-  Users.find().map(function (item) {
-    Users.remove({ _id: item._id });
-  });
-};
-
-var renderUsersListTemplate = function () {
-  var div = document.createElement('div');
-  var data = {};
-  data.id = 'users';
-  data.name = 'usersList';
-  var comp = Blaze.renderWithData(Template.usersList, data); // loading data is optional
-  Blaze.insert(comp, div);
-  return div;
-};
-
-// TODO: try to use Meteor methods instead
-var removeUserFromCollection = function (id) {
-  Users.find().map(function (item) {
-    if (item.userId == id) Users.remove({ _id: item._id });
-  });
-};
-
-// TODO: try to start with calling the app's methods instead of modifying the collection
-describe('usersList template', function () {
-  beforeEach(function () {
-    emptyUsersCollection();
-  });
-
-  it('should have no users when we start with an empty Users collection', function () {
-    var div = renderUsersListTemplate();
-
-    expect($(div).find('.userNameEntry')[0]).not.toBeDefined();
-  });
-
-  it('should not display presenter icon next to a non-presenter user', function () {
-    var document1 = {
-      meetingId: 'meeting001',
-      userId: 'user001',
-      user: {
-        presenter: false,
-      },
-    };
-    Users.insert(document1);
-    var div = renderUsersListTemplate();
-
-    expect($(div).find('.glyphicon-picture')[0]).not.toBeDefined();
-  });
-
-  it("should display presenter icon next to the presenter's username", function () {
-    var document1 = {
-      meetingId: 'meeting001',
-      userId: 'user001',
-      user: {
-        presenter: true,
-      },
-    };
-    Users.insert(document1);
-    var div = renderUsersListTemplate();
-
-    expect($(div).find('.glyphicon-picture')[0]).toBeDefined();
-  });
-
-  it('should display usernames correctly', function () {
-    var document1 = {
-      meetingId: 'meeting001',
-      userId: 'user001',
-      user: {
-        name: 'Maxim',
-      },
-    };
-    Users.insert(document1);
-    var div = renderUsersListTemplate();
-
-    expect($(div).find('.userNameEntry').html().trim()).toEqual('Maxim');
-  });
-
-  it('should display all the users in chat (correct number)', function () {
-    var document1 = {
-      meetingId: 'meeting001',
-      userId: 'user001',
-      user: {
-        name: 'Maxim',
-      },
-    };
-    var document2 = {
-      meetingId: 'meeting001',
-      userId: 'user002',
-      user: {
-        name: 'Anton',
-      },
-    };
-    var document3 = {
-      meetingId: 'meeting001',
-      userId: 'user003',
-      user: {
-        name: 'Danny',
-      },
-    };
-
-    Users.insert(document1);
-    Users.insert(document2);
-    Users.insert(document3);
-    var div = renderUsersListTemplate();
-
-    expect($(div).find('.userNameEntry').size()).toEqual(3);
-  });
-
-  it(
-    'should be able to reactively handle new and logged-out users ' +
-    '(1 user -> 3 users -> 4 users -> 2 users -> 5 users)', function () {
-    var document1 = {
-      meetingId: 'meeting001',
-      userId: 'user001',
-      user: {
-        name: 'Maxim',
-      },
-    };
-    var document2 = {
-      meetingId: 'meeting001',
-      userId: 'user002',
-      user: {
-        name: 'Anton',
-      },
-    };
-    var document3 = {
-      meetingId: 'meeting001',
-      userId: 'user003',
-      user: {
-        name: 'Danny',
-      },
-    };
-    var document4 = {
-      meetingId: 'meeting001',
-      userId: 'user004',
-      user: {
-        name: 'Chad',
-      },
-    };
-    var document5 = {
-      meetingId: 'meeting001',
-      userId: 'user005',
-      user: {
-        name: 'Fardad',
-      },
-    };
-    var document6 = {
-      meetingId: 'meeting001',
-      userId: 'user006',
-      user: {
-        name: 'Adam',
-      },
-    };
-    var document7 = {
-      meetingId: 'meeting001',
-      userId: 'user007',
-      user: {
-        name: 'Gary',
-      },
-    };
-
-    Users.insert(document1);
-    var div = renderUsersListTemplate();
-
-    expect($(div).find('.userNameEntry').size()).toEqual(1);
-    expect($(div).find('.userNameEntry:eq(0)').html().trim()).toEqual('Maxim');
-
-    Users.insert(document2);
-    Users.insert(document3);
-
-    expect($(div).find('.userNameEntry').size()).toEqual(3);
-    expect($(div).find('.userNameEntry:eq(0)').html().trim()).toEqual('Maxim');
-    expect($(div).find('.userNameEntry:eq(1)').html().trim()).toEqual('Anton');
-    expect($(div).find('.userNameEntry:eq(2)').html().trim()).toEqual('Danny');
-
-    Users.insert(document4);
-
-    expect($(div).find('.userNameEntry').size()).toEqual(4);
-    expect($(div).find('.userNameEntry:eq(0)').html().trim()).toEqual('Maxim');
-    expect($(div).find('.userNameEntry:eq(1)').html().trim()).toEqual('Anton');
-    expect($(div).find('.userNameEntry:eq(2)').html().trim()).toEqual('Danny');
-    expect($(div).find('.userNameEntry:eq(3)').html().trim()).toEqual('Chad');
-
-    removeUserFromCollection('user002');
-    removeUserFromCollection('user004');
-
-    expect($(div).find('.userNameEntry').size()).toEqual(2);
-    expect($(div).find('.userNameEntry:eq(0)').html().trim()).toEqual('Maxim');
-    expect($(div).find('.userNameEntry:eq(1)').html().trim()).toEqual('Danny');
-
-    Users.insert(document5);
-    Users.insert(document6);
-    Users.insert(document7);
-
-    expect($(div).find('.userNameEntry:eq(0)').html().trim()).toEqual('Maxim');
-    expect($(div).find('.userNameEntry:eq(1)').html().trim()).toEqual('Danny');
-    expect($(div).find('.userNameEntry:eq(2)').html().trim()).toEqual('Fardad');
-    expect($(div).find('.userNameEntry:eq(3)').html().trim()).toEqual('Adam');
-    expect($(div).find('.userNameEntry:eq(4)').html().trim()).toEqual('Gary');
-  });
-
-  it('should display usernames in the correct order', function () {
-    var document1 = {
-      meetingId: 'meeting001',
-      userId: 'user001',
-      user: {
-        name: 'Maxim',
-      },
-    };
-    var document2 = {
-      meetingId: 'meeting001',
-      userId: 'user002',
-      user: {
-        name: 'Anton',
-      },
-    };
-    var document3 = {
-      meetingId: 'meeting001',
-      userId: 'user003',
-      user: {
-        name: 'Danny',
-      },
-    };
-
-    Users.insert(document1);
-    Users.insert(document2);
-    Users.insert(document3);
-    var div = renderUsersListTemplate();
-
-    expect($(div).find('.userNameEntry:eq(0)').html().trim()).toEqual('Maxim');
-    expect($(div).find('.userNameEntry:eq(1)').html().trim()).toEqual('Anton');
-    expect($(div).find('.userNameEntry:eq(2)').html().trim()).toEqual('Danny');
-  });
-
-  it('should handle listen-only users properly', function () {
-    var document1 = {
-      meetingId: 'meeting001',
-      userId: 'user001',
-      user: {
-        name: 'Maxim',
-      },
-    };
-    var document2 = {
-      meetingId: 'meeting001',
-      userId: 'user002',
-      user: {
-        name: 'Anton',
-        listenOnly: true,
-      },
-    };
-    var document3 = {
-      meetingId: 'meeting001',
-      userId: 'user003',
-      user: {
-        name: 'Danny',
-      },
-    };
-
-    Users.insert(document1);
-    Users.insert(document2);
-    Users.insert(document3);
-    var div = renderUsersListTemplate();
-
-    expect($(div).find('.glyphicon-headphones')).toBeDefined();
-  });
-});
diff --git a/bigbluebutton-html5/tests/jasmine/client/integration/templates/whiteboard/whiteboardSpec.js b/bigbluebutton-html5/tests/jasmine/client/integration/templates/whiteboard/whiteboardSpec.js
deleted file mode 100755
index 4275cd7de0b03500def2db82d2b53968ba8368e8..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/tests/jasmine/client/integration/templates/whiteboard/whiteboardSpec.js
+++ /dev/null
@@ -1,95 +0,0 @@
-var emptyMeetingsCollection = function () {
-  Meetings.find().map(function (item) {
-    Meetings.remove({ _id: item._id });
-  });
-};
-
-var emptyPresentationsCollection = function () {
-  Presentations.find().map(function (item) {
-    Presentations.remove({ _id: item._id });
-  });
-};
-
-var emptySlidesCollection = function () {
-  Slides.find().map(function (item) {
-    Slides.remove({ _id: item._id });
-  });
-};
-
-var emptyShapesCollection = function () {
-  Shapes.find().map(function (item) {
-    Shapes.remove({ _id: item._id });
-  });
-};
-
-var renderWhiteboardTemplate = function (title) {
-  var div = document.createElement('div');
-  var data = {};
-  data.id = 'whiteboard';
-  data.title = title;
-  data.name = 'whiteboard';
-  var comp = Blaze.renderWithData(Template.whiteboard, data); // loading data is optional
-  Blaze.insert(comp, div);
-  return div;
-};
-
-describe('whiteboard template', function () {
-  beforeEach(function () {
-    emptyMeetingsCollection();
-    emptyPresentationsCollection();
-    emptySlidesCollection();
-    emptyShapesCollection();
-  });
-
-  it('should contain a pencil icon inside the title entry', function () {
-    var div = renderWhiteboardTemplate('Whiteboard: default.pdf');
-
-    expect($(div).find('.title').find('.glyphicon-pencil')[0]).toBeDefined();
-  });
-
-  it('should contain the correct title', function () {
-    var div = renderWhiteboardTemplate('Whiteboard: default.pdf');
-
-    expect($(div).find('.title:eq(0)').html()).toContain('Whiteboard: default.pdf');
-  });
-
-  // TODO: finish the following
-  it('should be rendered successfully', function () {
-    var meeting1 = {
-      meetingId: 'meeting001',
-      meetingName: 'first meeting',
-    };
-    Meetings.insert(meeting1);
-
-    var presentation1 = {
-      meetingId: 'meeting001',
-      presentation: {
-        id: 'presentation001',
-        name: 'default.pdf',
-        current: true,
-      },
-    };
-    Presentations.insert(presentation1);
-
-    var slide1 = {
-      meetingId: 'meeting001',
-      presentationId: 'presentation001',
-      slide: {
-        id: 'slide001',
-        num: 1,
-        current: true,
-        width_ratio: 100.0,
-        height_ratio: 100.0,
-        x_offset: 0.0,
-        y_offset: 0.0,
-        png_uri: 'http://bigbluebutton.org/wp-content/uploads/2013/05/bbb-overview.png',
-      },
-    };
-    Slides.insert(slide1);
-
-    var div = document.createElement('DIV');
-    Blaze.render(Template.main, div);
-
-    expect($(div).find('#whiteboard-navbar')[0]).toBeDefined();
-  });
-});
diff --git a/bigbluebutton-html5/tests/jasmine/client/unit/globalsSpec.js b/bigbluebutton-html5/tests/jasmine/client/unit/globalsSpec.js
deleted file mode 100755
index e83236e850576cb3ce7d129c6313f1fe00c43a00..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/tests/jasmine/client/unit/globalsSpec.js
+++ /dev/null
@@ -1,11 +0,0 @@
-describe('Global getters', function () {
-  it('should correctly return current config information required by the footer', function () {
-    var returnedInfo = getBuildInformation();
-    expect(returnedInfo.copyrightYear).toEqual('2014');
-    expect(returnedInfo.bbbServerVersion).toEqual('0.9.0');
-    expect(returnedInfo.dateOfBuild).toEqual('Sept 25, 2014');
-    expect(returnedInfo.link).toEqual(
-      "<a href='http://bigbluebutton.org/' target='_blank'>http://bigbluebutton.org</a>"
-    );
-  });
-});
diff --git a/bigbluebutton-html5/tests/jasmine/server/unit/CollectionMethodsSpec.js b/bigbluebutton-html5/tests/jasmine/server/unit/CollectionMethodsSpec.js
deleted file mode 100755
index 1ca49dee84cb2a14f68d4c5d179636948fb2691b..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/tests/jasmine/server/unit/CollectionMethodsSpec.js
+++ /dev/null
@@ -1,746 +0,0 @@
-describe('Collections', function () {
-  beforeEach(function () {
-    MeteorStubs.install();
-  });
-
-  afterEach(function () {
-    MeteorStubs.uninstall();
-  });
-
-  //----------------------------------------------------------------------
-  // publish.coffee
-  //----------------------------------------------------------------------
-
-  it('should all be correctly handled by remove() after calling clearCollections()', function () {
-    spyOn(Users, 'remove');
-    spyOn(Chat, 'remove');
-    spyOn(Meetings, 'remove');
-    spyOn(Shapes, 'remove');
-    spyOn(Slides, 'remove');
-    spyOn(Presentations, 'remove');
-
-    clearUsersCollection();
-    clearChatCollection();
-    clearMeetingsCollection();
-    clearShapesCollection();
-    clearSlidesCollection();
-    clearPresentationsCollection();
-
-    expect(Users.remove).toHaveBeenCalled();
-    expect(Chat.remove).toHaveBeenCalled();
-    expect(Meetings.remove).toHaveBeenCalled();
-    expect(Shapes.remove).toHaveBeenCalled();
-    expect(Slides.remove).toHaveBeenCalled();
-    expect(Presentations.remove).toHaveBeenCalled();
-  });
-
-  //----------------------------------------------------------------------
-  // chat.coffee
-  //----------------------------------------------------------------------
-
-  it('should be handled correctly by insert() on calling addChatToCollection()' +
-    ' in case of private chat', function () {
-    spyOn(Users, 'findOne').and.callFake(function (doc) {
-      if (doc.userId == 'user001') return { userId: 'user001' };
-      else if (doc.userId == 'user002') return { userUd: 'user002' };
-    });
-
-    spyOn(Chat, 'insert');
-
-    addChatToCollection('meeting001', {
-      from_time: '123',
-      from_userid: 'user001',
-      to_userid: 'user002',
-      chat_type: 'PRIVATE_CHAT',
-      message: 'Hello!',
-      to_username: 'Anton',
-      from_tz_offset: '240',
-      from_color: '0x000000',
-      from_username: 'Maxim',
-      from_lang: 'en',
-    });
-
-    expect(Chat.insert).toHaveBeenCalledWith({
-      meetingId: 'meeting001',
-      message: {
-        chat_type: 'PRIVATE_CHAT',
-        message: 'Hello!',
-        to_username: 'Anton',
-        from_tz_offset: '240',
-        from_color: '0x000000',
-        to_userid: 'user002',//not "dbid002"
-        from_userid: 'user001',//not "dbid001"
-        from_time: '123',
-        from_username: 'Maxim',
-        from_lang: 'en',
-      },
-    });
-  });
-
-  it('should be handled correctly by insert() on calling ' +
-    'addChatToCollection() in case of public chat', function () {
-    spyOn(Users, 'findOne').and.callFake(function (doc) {
-      if (doc.userId == 'user001') return { _id: 'dbid001' };
-      else if (doc.userId == 'user002') return { _id: 'dbid002' };
-    });
-
-    spyOn(Chat, 'insert');
-
-    addChatToCollection('meeting001', {
-      from_time: '123',
-      from_userid: 'user001',
-      to_userid: 'public_chat_userid',
-      chat_type: 'PUBLIC_CHAT',
-      message: 'Hello!',
-      to_username: 'public_chat_username',
-      from_tz_offset: '240',
-      from_color: '0x000000',
-      from_username: 'Maxim',
-      from_lang: 'en',
-    });
-
-    expect(Chat.insert).toHaveBeenCalledWith({
-      meetingId: 'meeting001',
-      message: {
-        chat_type: 'PUBLIC_CHAT',
-        message: 'Hello!',
-        to_username: 'public_chat_username',
-        from_tz_offset: '240',
-        from_color: '0x000000',
-        to_userid: 'public_chat_userid',
-        from_userid: 'user001',
-        from_time: '123',
-        from_username: 'Maxim',
-        from_lang: 'en',
-      },
-    });
-  });
-
-  //----------------------------------------------------------------------
-  // meetings.coffee
-  //----------------------------------------------------------------------
-
-  it(
-    'should not be updated on calling addMeetingToCollection() if ' +
-    'the meeting is already in the collection', function () {
-    spyOn(Meetings, 'findOne').and.callFake(function (doc) {
-      if (doc.meetingId == 'meeting001') return { meetingId: 'meeting001' };
-      else return undefined;
-    });
-
-    spyOn(Meetings, 'insert');
-
-    addMeetingToCollection('meeting001', 'Demo Meeting', false, '12345', '0');
-
-    expect(Meetings.insert).not.toHaveBeenCalled();
-  });
-
-  it(
-    'should be handled correctly by insert() on calling addMeetingToCollection() ' +
-    'with a brand new meeting', function () {
-    spyOn(Meetings, 'findOne').and.returnValue(undefined);//collection is empty
-    spyOn(Meetings, 'insert');
-
-    addMeetingToCollection('meeting001', 'Demo Meeting', false, '12345', '0');
-
-    expect(Meetings.insert).toHaveBeenCalledWith({
-      meetingId: 'meeting001',
-      meetingName: 'Demo Meeting',
-      intendedForRecording: false,
-      currentlyBeingRecorded: false,//default value
-      voiceConf: '12345',
-      duration: '0',
-    });
-  });
-
-  it(
-    'should not be touched on calling removeMeetingFromCollection() ' +
-    'if there is no wanted meeting in the collection', function () {
-    spyOn(Meetings, 'findOne').and.returnValue(undefined);//collection is empty
-    spyOn(Meetings, 'remove');
-
-    removeMeetingFromCollection('meeting001');
-
-    expect(Meetings.remove).not.toHaveBeenCalled();
-  });
-
-  //TODO: emulate a find() call
-  /*it("should be correctly updated after the removeMeetingFromCollection() call", function () {
-    spyOn(Meetings, "findOne").and.callFake(function(doc) {
-      if(doc.meetingId == "meeting001") return { _id: "id001", meetingId: "meeting001" };
-      else return undefined;
-    });
-
-    spyOn(Meetings, "remove");
-
-    removeMeetingFromCollection("meeting001");
-
-    expect(Meetings.remove).toHaveBeenCalled();
-  });*/
-
-  //----------------------------------------------------------------------
-  // shapes.coffee
-  //----------------------------------------------------------------------
-
-  // addShapeToCollection()
-  it(
-    'should be handled correctly by insert() on calling addShapeToCollection() ' +
-    'with a text', function () {
-    spyOn(Shapes, 'find').and.returnValue({
-      count: function () {
-        return 1;
-      },
-    });
-    spyOn(Shapes, 'insert');
-
-    addShapeToCollection('meeting001', 'whiteboard001', {
-      shape_type: 'text',
-      status: 'textPublished',
-      shape: {
-        type: 'text',
-        textBoxHeight: 24.5,
-        backgroundColor: 16777215,
-        fontColor: 0,
-        status: 'textPublished',
-        dataPoints: '36.5,55.0',
-        x: 36.5,
-        textBoxWidth: 36.0,
-        whiteboardId: 'whiteboard001',
-        fontSize: 18,
-        id: 'shape001',
-        y: 55.0,
-        calcedFontSize: 3.6,
-        text: 'Hello World!',
-        background: true,
-      },
-    });
-
-    expect(Shapes.insert).toHaveBeenCalledWith({
-      meetingId: 'meeting001',
-      whiteboardId: 'whiteboard001',
-      shape: {
-        type: 'text',
-        textBoxHeight: 24.5,
-        backgroundColor: 16777215,
-        fontColor: 0,
-        status: 'textPublished',
-        dataPoints: '36.5,55.0',
-        x: 36.5,
-        textBoxWidth: 36.0,
-        whiteboardId: 'whiteboard001',
-        fontSize: 18,
-        id: 'shape001',
-        y: 55.0,
-        calcedFontSize: 3.6,
-        text: 'Hello World!',
-        background: true,
-      },
-    });
-  });
-
-  it(
-    'should be handled correctly by insert() on calling addShapeToCollection() with a ' +
-    'finished standard shape', function () {
-    spyOn(Shapes, 'find').and.returnValue({
-      count: function () {
-        return 1;
-      },
-    });
-    spyOn(Shapes, 'insert');
-
-    addShapeToCollection('meeting001', 'whiteboard001', {
-      wb_id: 'whiteboard001',
-      shape_type: 'rectangle',
-      status: 'DRAW_END',
-      id: 'shape001',
-      shape: {
-        type: 'rectangle',
-        status: 'DRAW_END',
-        points: [60.0, 17.0, 73.0, 57.5],
-        whiteboardId: 'whiteboard001',
-        id: 'shape001',
-        square: false,
-        transparency: false,
-        thickness: 10,
-        color: 0,
-      },
-    });
-
-    expect(Shapes.insert).toHaveBeenCalledWith({
-      meetingId: 'meeting001',
-      whiteboardId: 'whiteboard001',
-      shape: {
-        wb_id: 'whiteboard001',
-        shape_type: 'rectangle',
-        status: 'DRAW_END',
-        id: 'shape001',
-        shape: {
-          type: 'rectangle',
-          status: 'DRAW_END',
-          points: [60.0, 17.0, 73.0, 57.5],
-          whiteboardId: 'whiteboard001',
-          id: 'shape001',
-          square: false,
-          transparency: false,
-          thickness: 10,
-          color: 0,
-        },
-      },
-    });
-  });
-
-  it(
-    'should be handled correctly by insert() on calling addShapeToCollection() ' +
-    'with a pencil being used', function () {
-    spyOn(Shapes, 'find').and.returnValue({
-      count: function () {
-        return 1;
-      },
-    });
-    spyOn(Shapes, 'insert');
-
-    addShapeToCollection('meeting001', 'whiteboard001', {
-      wb_id: 'whiteboard001',
-      shape_type: 'pencil',
-      status: 'DRAW_START',
-      id: 'shape001',
-      shape: {
-        type: 'pencil',
-        status: 'DRAW_START',
-        points: [35.8, 63.6, 36.1, 63.4, 36.2, 63.2],
-        whiteboardId: 'whiteboard001',
-        id: 'shape001',
-        square: undefined,
-        transparency: false,
-        thickness: 10,
-        color: 0,
-      },
-    });
-
-    expect(Shapes.insert).toHaveBeenCalledWith({
-      meetingId: 'meeting001',
-      whiteboardId: 'whiteboard001',
-      shape: {
-        wb_id: 'whiteboard001',
-        shape_type: 'pencil',
-        status: 'DRAW_START',
-        id: 'shape001',
-        shape: {
-          type: 'pencil',
-          status: 'DRAW_START',
-          points: [35.8, 63.6, 36.1, 63.4, 36.2, 63.2],
-          whiteboardId: 'whiteboard001',
-          id: 'shape001',
-          square: undefined,
-          transparency: false,
-          thickness: 10,
-          color: 0,
-        },
-      },
-    });
-  });
-
-  // removeAllShapesFromSlide()
-  it(
-    'should not be touched on calling removeAllShapesFromSlide() ' +
-    'with undefined meetingId', function () {
-    spyOn(Shapes, 'remove');
-    removeAllShapesFromSlide(undefined, 'whiteboard001');
-    expect(Shapes.remove).not.toHaveBeenCalled();
-  });
-
-  it(
-    'should not be touched on calling removeAllShapesFromSlide() ' +
-    'with undefined whiteboardId', function () {
-    spyOn(Shapes, 'remove');
-    removeAllShapesFromSlide('meeting001', undefined);
-    expect(Shapes.remove).not.toHaveBeenCalled();
-  });
-
-  it(
-    'should not be touched on calling removeAllShapesFromSlide() ' +
-    'if there is no shapes on the whiteboard', function () {
-    spyOn(Shapes, 'find').and.returnValue(undefined);
-    spyOn(Shapes, 'remove');
-    removeAllShapesFromSlide('meeting001', 'whiteboard001');
-    expect(Shapes.remove).not.toHaveBeenCalled();
-  });
-
-  it(
-    'should be cleared on calling removeAllShapesFromSlide() ' +
-    'if there are shapes on the whiteboard', function () {
-    spyOn(Shapes, 'find').and.callFake(function (doc) {
-      if (doc.meetingId === 'meeting001' && doc.whiteboardId === 'whiteboard001')
-        return {
-          fetch: function () {
-            return [{ shape: { id: 'shape001' } }];
-          },
-        };
-      else return undefined;
-    });
-
-    spyOn(Shapes, 'findOne').and.callFake(function (doc) {
-      if (
-        doc.meetingId === 'meeting001' &&
-        doc.whiteboardId === 'whiteboard001' &&
-        doc['shape.id'] === 'shape001'
-      )
-        return {
-          _id: 'doc001',
-        };
-      else return undefined;
-    });
-
-    spyOn(Shapes, 'remove');
-    removeAllShapesFromSlide('meeting001', 'whiteboard001');
-    expect(Shapes.remove).toHaveBeenCalledWith('doc001');
-  });
-
-  // removeShapeFromSlide()
-  it(
-    'should not be touched on calling removeShapeFromSlide() ' +
-    'with undefined meetingId', function () {
-    spyOn(Shapes, 'find').and.callFake(function (doc) {
-      if (doc.meetingId === undefined && doc.whiteboardId === 'whiteboard001')
-        return {
-          count: function () {
-            return 0;
-          },
-        };
-      else return undefined;
-    });
-
-    spyOn(Shapes, 'findOne').and.callFake(function (doc) {
-      if (
-        doc.meetingId === undefined &&
-        doc.whiteboardId === 'whiteboard001' &&
-        doc['shape.id'] === 'shape001'
-      )
-        return {
-          _id: 'doc001',
-        };
-      else return undefined;
-    });
-
-    spyOn(Shapes, 'remove');
-
-    removeShapeFromSlide(undefined, 'whiteboard001', 'shape001');
-
-    expect(Shapes.remove).not.toHaveBeenCalled();
-  });
-
-  it(
-    'should not be touched on calling removeShapeFromSlide() ' +
-    'with undefined whiteboardId', function () {
-    spyOn(Shapes, 'find').and.callFake(function (doc) {
-      if (doc.meetingId === 'meeting001' && doc.whiteboardId === undefined)
-        return {
-          count: function () {
-            return 0;
-          },
-        };
-      else return undefined;
-    });
-
-    spyOn(Shapes, 'findOne').and.callFake(function (doc) {
-      if (
-        doc.meetingId === 'meeting001' &&
-        doc.whiteboardId === undefined &&
-        doc['shape.id'] === 'shape001'
-      )
-        return {
-          _id: 'doc001',
-        };
-      else return undefined;
-    });
-
-    spyOn(Shapes, 'remove');
-
-    removeShapeFromSlide('meeting001', undefined, 'shape001');
-
-    expect(Shapes.remove).not.toHaveBeenCalled();
-  });
-
-  it(
-    'should not be touched on calling removeShapeFromSlide() ' +
-    'with undefined shapeId', function () {
-    spyOn(Shapes, 'find').and.callFake(function (doc) {
-      if (doc.meetingId === 'meeting001' && doc.whiteboardId === 'whiteboard001')
-        return {
-          count: function () {
-            return 0;
-          },
-        };
-      else return undefined;
-    });
-
-    spyOn(Shapes, 'findOne').and.callFake(function (doc) {
-      if (
-        doc.meetingId === 'meeting001' &&
-        doc.whiteboardId === 'whiteboard001' &&
-        doc['shape.id'] === undefined
-      )
-        return {
-          _id: 'doc001',
-        };
-      else return undefined;
-    });
-
-    spyOn(Shapes, 'remove');
-
-    removeShapeFromSlide('meeting001', 'whiteboard001', undefined);
-
-    expect(Shapes.remove).not.toHaveBeenCalled();
-  });
-
-  it(
-    'should not be touched on calling removeShapeFromSlide() ' +
-    'if there is no wanted shape on the whiteboard', function () {
-    spyOn(Shapes, 'find').and.callFake(function (doc) {
-      if (doc.meetingId === 'meeting001' && doc.whiteboardId === 'whiteboard001')
-        return {
-          count: function () {
-            return 0;
-          },
-        };
-      else return undefined;
-    });
-
-    spyOn(Shapes, 'findOne').and.callFake(function (doc) {
-      if (
-        doc.meetingId === 'meeting001' &&
-        doc.whiteboardId === 'whiteboard001' &&
-        doc['shape.id'] === 'shape001'
-      )
-        return undefined;
-      else return {
-        _id: 'doc001',
-      };
-    });
-
-    spyOn(Shapes, 'remove');
-
-    removeShapeFromSlide('meeting001', 'whiteboard001', undefined);
-
-    expect(Shapes.remove).not.toHaveBeenCalled();
-  });
-
-  it(
-    'should be updated correctly on calling removeShapeFromSlide() ' +
-    'with an existing shape', function () {
-    spyOn(Shapes, 'find').and.callFake(function (doc) {
-      if (doc.meetingId === 'meeting001' && doc.whiteboardId === 'whiteboard001')
-        return {
-          count: function () {
-            return 0;
-          },
-        };
-      else return undefined;
-    });
-
-    spyOn(Shapes, 'findOne').and.callFake(function (doc) {
-      if (
-        doc.meetingId === 'meeting001' &&
-        doc.whiteboardId === 'whiteboard001' &&
-        doc['shape.id'] === 'shape001'
-      )
-        return {
-          _id: 'doc001',
-        };
-      else return undefined;
-    });
-
-    spyOn(Shapes, 'remove');
-    removeShapeFromSlide('meeting001', 'whiteboard001', 'shape001');
-    expect(Shapes.remove).toHaveBeenCalledWith('doc001');
-  });
-
-  //----------------------------------------------------------------------
-  // presentation.coffee
-  //----------------------------------------------------------------------
-
-  it(
-    'should be handled correctly by insert() ' +
-    'on calling addPresentationToCollection()', function () {
-    spyOn(Presentations, 'findOne').and.returnValue(undefined);
-
-    spyOn(Presentations, 'insert');
-
-    addPresentationToCollection('meeting001', {
-      id: 'presentation001',
-      name: 'Presentation 001',
-      current: true,
-    });
-
-    expect(Presentations.insert).toHaveBeenCalledWith({
-      meetingId: 'meeting001',
-      presentation: {
-        id: 'presentation001',
-        name: 'Presentation 001',
-        current: true,
-      },
-      pointer: {
-        x: 0.0,
-        y: 0.0,
-      },
-    });
-  });
-
-  it(
-    'should be handled correctly on calling addPresentationToCollection() ' +
-    'when presentation is already in the collection', function () {
-    spyOn(Presentations, 'findOne').and.returnValue({ _id: 'dbid001' });
-
-    spyOn(Presentations, 'insert');
-
-    addPresentationToCollection('meeting001', {
-      id: 'presentation001',
-      name: 'Presentation 001',
-      current: true,
-    });
-
-    expect(Presentations.insert).not.toHaveBeenCalledWith({
-      meetingId: 'meeting001',
-      presentation: {
-        id: 'presentation001',
-        name: 'Presentation 001',
-        current: true,
-      },
-      pointer: {
-        x: 0.0,
-        y: 0.0,
-      },
-    });
-  });
-
-  it(
-    'should be handled correctly by remove() ' +
-    'on calling removePresentationFromCollection', function () {
-    spyOn(Presentations, 'findOne').and.returnValue({ _id: 'dbid001' });
-    spyOn(Presentations, 'remove');
-
-    removePresentationFromCollection('meeting0001', 'presentation001');
-
-    expect(Presentations.remove).toHaveBeenCalled();
-  });
-
-  it(
-    'should be handled correctly by remove() ' +
-    'on calling removePresentationFromCollection', function () {
-    spyOn(Presentations, 'findOne').and.returnValue(undefined);
-    spyOn(Presentations, 'remove');
-
-    removePresentationFromCollection('meeting0001', 'presentation001');
-
-    expect(Presentations.remove).not.toHaveBeenCalled();
-  });
-
-  //----------------------------------------------------------------------
-  // slides.coffee
-  //----------------------------------------------------------------------
-
-  // removeSlideFromCollection()
-  it(
-    'should not be touched on calling removeSlideFromCollection() ' +
-    'with undefined meetingId', function () {
-    spyOn(Slides, 'remove');
-    removeSlideFromCollection(undefined, 'presentation001/2');
-    expect(Slides.remove).not.toHaveBeenCalled();
-  });
-
-  it(
-    'should not be touched on calling removeSlideFromCollection() ' +
-    'with undefined slideId', function () {
-    spyOn(Slides, 'remove');
-    removeSlideFromCollection('meeting001', undefined);
-    expect(Slides.remove).not.toHaveBeenCalled();
-  });
-
-  it(
-    'should not be touched on calling removeSlideFromCollection() ' +
-    'with a slide that does not exist', function () {
-    spyOn(Slides, 'findOne').and.callFake(function (doc) {
-      if (doc.meetingId === 'meeting001' && doc['slide.id'] === 'slide001')
-        return undefined;
-      else return { meetingId: 'meeting001' };
-    });
-
-    spyOn(Slides, 'remove');
-    removeSlideFromCollection('meeting001', 'slide001');
-    expect(Slides.remove).not.toHaveBeenCalled();
-  });
-
-  it(
-    'should be handled correctly by remove() on calling removeSlideFromCollection() ' +
-    'with an existing slide', function () {
-    spyOn(Slides, 'findOne').and.callFake(function (doc) {
-      if (doc.meetingId === 'meeting001' && doc['slide.id'] === 'slide001')
-        return { _id: 'doc001' };
-      else return undefined;
-    });
-
-    spyOn(Slides, 'remove');
-    removeSlideFromCollection('meeting001', 'slide001');
-    expect(Slides.remove).toHaveBeenCalledWith('doc001');
-  });
-
-  // addSlideToCollection()
-  it(
-    'should not be touched on calling addSlideToCollection() ' +
-    'if the slide is already in the collection', function () {
-    spyOn(Slides, 'findOne').and.callFake(function (doc) {
-      if (doc.meetingId === 'meeting001' && doc['slide.id'] === 'presentation001/2')
-        return { _id: 'doc001' };
-      else return undefined;
-    });
-
-    spyOn(Slides, 'insert');
-    addSlideToCollection('meeting001', 'presentation001', {
-      id: 'presentation001/2',
-    });
-    expect(Slides.insert).not.toHaveBeenCalled();
-  });
-
-  it(
-    'should be handled correctly by insert() on calling addSlideToCollection() ' +
-    'with a brand new slide', function () {
-    spyOn(Slides, 'findOne').and.callFake(function (doc) {
-      if (doc.meetingId === 'meeting001' && doc['slide.id'] === 'presentation001/2')
-        return undefined;
-      else return { _id: 'doc001' };
-    });
-
-    spyOn(Slides, 'insert');
-    addSlideToCollection('meeting001', 'presentation001', {
-      height_ratio: 100,
-      y_offset: 0,
-      num: 2,
-      x_offset: 0,
-      current: true,
-      png_uri: 'http://localhost/bigbluebutton/presentation/presentation001/png/2',
-      txt_uri: 'http://localhost/bigbluebutton/presentation/presentation001/textfiles/slide-2.txt',
-      id: 'presentation001/2',
-      width_ratio: 100,
-      swf_uri: 'http://localhost/bigbluebutton/presentation/presentation001/slide/2',
-      thumb_uri: 'http://localhost/bigbluebutton/presentation/presentation001/thumbnail/1',
-    });
-    expect(Slides.insert).toHaveBeenCalledWith({
-      meetingId: 'meeting001',
-      presentationId: 'presentation001',
-      slide: {
-          height_ratio: 100,
-          y_offset: 0,
-          num: 2,
-          x_offset: 0,
-          current: true,
-          png_uri: 'http://localhost/bigbluebutton/presentation/presentation001/png/2',
-          txt_uri: 'http://localhost/bigbluebutton/presentation/presentation001/textfiles/' +
-            'slide-2.txt',
-          id: 'presentation001/2',
-          width_ratio: 100,
-          swf_uri: 'http://localhost/bigbluebutton/presentation/presentation001/slide/2',
-          thumb_uri: 'http://localhost/bigbluebutton/presentation/presentation001/thumbnail/1',
-        },
-    });
-  });
-});
diff --git a/bigbluebutton-html5/tests/jasmine/server/unit/config-stubs.js b/bigbluebutton-html5/tests/jasmine/server/unit/config-stubs.js
deleted file mode 100644
index 83ddbb1184c4b9884825639c21d23840fda37f80..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/tests/jasmine/server/unit/config-stubs.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/*
-  Stub the logger
-*/
-
-Logger = {};
-Logger.prototype = {
-  constructor: Logger,
-};
-Logger.info = function () {};
-
-//Meteor.log = Logger; //TODO this should import logger
-
diff --git a/bigbluebutton-html5/tests/nightwatch/chatting.js b/bigbluebutton-html5/tests/nightwatch/chatting.js
deleted file mode 100755
index 09b9b14919aedf0f00320009d3ee62f77ffaaa58..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/tests/nightwatch/chatting.js
+++ /dev/null
@@ -1,71 +0,0 @@
-module.exports = {
-  'Welcome message title in the public chat of Demo Meeting is correct': function (browser) {
-    browser
-      .url('http://192.168.244.140:4000')
-      .waitForElementVisible('body', 1000)
-      .assert.visible('input[ng-model=username]')
-      .setValue('input[ng-model=username]', ['Maxim', browser.Keys.ENTER])
-      .waitForElementVisible('#chatbody .chat li:last-of-type', 10000)
-      .verify.containsText('#chatbody .chat li:last-of-type div', 'Welcome to Demo Meeting')
-      .deleteCookies()
-      .closeWindow()
-      .end();
-  },
-
-  'Public chat`s welcome message title in the non-default meeting is correct': function (browser) {
-    browser
-      .url('http://192.168.244.140:4000')
-      .waitForElementVisible('body', 1000)
-      .assert.visible('input[ng-model=username]')
-      .setValue('input[ng-model=username]', 'Maxim')
-      .assert.visible('input[ng-model=meetingName]')
-      .setValue('input[ng-model=meetingName]', ['Meeting1', browser.Keys.ENTER])
-      .waitForElementVisible('#chatbody .chat li:last-of-type', 10000)
-      .verify.containsText('#chatbody .chat li:last-of-type div', 'Welcome to Meeting1')
-      .deleteCookies()
-      .closeWindow()
-      .end();
-  },
-
-  'Sending a message in a public chat using Enter': function (browser) {
-    browser
-      .url('http://192.168.244.140:4000')
-      .waitForElementVisible('body', 1000)
-      .assert.visible('input[ng-model=username]')
-      .setValue('input[ng-model=username]', ['Maxim', browser.Keys.ENTER])
-      .waitForElementVisible('#newMessageInput', 10000)
-      .setValue(
-        '#newMessageInput',
-        ['this message is to be sent via Enter key', browser.Keys.ENTER]
-      )
-      .pause(500)
-      .verify.containsText(
-        '#chatbody .chat li:last-of-type div',
-        'this message is to be sent via Enter key'
-      )
-      .verify.containsText('#chatbody .chat li:nth-last-of-type(2) div', 'Welcome to Demo Meeting')
-      .deleteCookies()
-      .closeWindow()
-      .end();
-  },
-
-  'Sending a message in a public chat using Send button': function (browser) {
-    browser
-      .url('http://192.168.244.140:4000')
-      .waitForElementVisible('body', 1000)
-      .assert.visible('input[ng-model=username]')
-      .setValue('input[ng-model=username]', ['Maxim', browser.Keys.ENTER])
-      .waitForElementVisible('#newMessageInput', 10000)
-      .setValue('#newMessageInput', 'this message is to be sent via Send button')
-      .click('#sendMessageButton')
-      .pause(500)
-      .verify.containsText(
-        '#chatbody .chat li:last-of-type div',
-        'this message is to be sent via Send button'
-      )
-      .verify.containsText('#chatbody .chat li:nth-last-of-type(2) div', 'Welcome to Demo Meeting')
-      .deleteCookies()
-      .closeWindow()
-      .end();
-  },
-};
diff --git a/bigbluebutton-html5/tests/nightwatch/loggingInOut.js b/bigbluebutton-html5/tests/nightwatch/loggingInOut.js
deleted file mode 100755
index caa6592bf5e06a5fbb96cd89a82ce21ab99150ea..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/tests/nightwatch/loggingInOut.js
+++ /dev/null
@@ -1,60 +0,0 @@
-module.exports = {
-  'Logging into Demo Meeting using Enter key': function (browser) {
-    browser
-      .url('http://192.168.244.140:4000')
-      .waitForElementVisible('body', 1000)
-      .assert.visible('input[ng-model=username]')
-      .setValue('input[ng-model=username]', ['Maxim', browser.Keys.ENTER])
-      .waitForElementVisible('body #main', 10000)
-      .verify.urlEquals('http://192.168.244.140:3000/')
-      .waitForElementVisible('.navbarTitle span', 1000)
-      .verify.containsText('.navbarTitle span', 'Demo Meeting')
-      .deleteCookies()
-      .closeWindow()
-      .end();
-  },
-
-  'Logging into Demo Meeting using Send button': function (browser) {
-    browser
-      .url('http://192.168.244.140:4000')
-      .waitForElementVisible('body', 1000)
-      .assert.visible('input[ng-model=username]')
-      .setValue('input[ng-model=username]', 'Maxim')
-      .click('input.success')
-      .waitForElementVisible('body #main', 10000)
-      .verify.urlEquals('http://192.168.244.140:3000/')
-      .waitForElementVisible('.navbarTitle span', 1000)
-      .verify.containsText('.navbarTitle span', 'Demo Meeting')
-      .deleteCookies()
-      .closeWindow()
-      .end();
-  },
-
-  'Logging into a meeting with non-default name': function (browser) {
-    browser
-      .url('http://192.168.244.140:4000')
-      .waitForElementVisible('body', 1000)
-      .assert.visible('input[ng-model=username]')
-      .setValue('input[ng-model=username]', 'Maxim')
-      .assert.visible('input[ng-model=meetingName]')
-      .setValue('input[ng-model=meetingName]', ['Meeting1', browser.Keys.ENTER])
-      .waitForElementVisible('.navbarTitle span', 10000)
-      .verify.containsText('.navbarTitle span', 'Meeting1')
-      .deleteCookies()
-      .closeWindow()
-      .end();
-  },
-
-  'Loading the presentation': function (browser) {
-    browser
-      .url('http://192.168.244.140:4000')
-      .waitForElementVisible('body', 1000)
-      .assert.visible('input[ng-model=username]')
-      .setValue('input[ng-model=username]', ['Maxim', browser.Keys.ENTER])
-      .waitForElementVisible('#whiteboard-paper', 10000)
-      .waitForElementVisible('#whiteboard-paper > #svggroup', 30000)
-      .deleteCookies()
-      .closeWindow()
-      .end();
-  },
-};
diff --git a/bigbluebutton-html5/tests/nightwatch/scaling.js b/bigbluebutton-html5/tests/nightwatch/scaling.js
deleted file mode 100755
index f828ab147526437f8606ca98cc193643687472b4..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/tests/nightwatch/scaling.js
+++ /dev/null
@@ -1,69 +0,0 @@
-module.exports = {
-  'Checking navbar`s height while scaling if there is a long meeting name ': function (browser) {
-    var longMeetingName = new Array(101).join('x'); // 100-element array of 'x'
-    browser
-      .url('http://192.168.244.140:4000')
-      .resizeWindow(1920, 1080)
-      .waitForElementVisible('body', 1000)
-      .assert.visible('input[ng-model=username]')
-      .setValue('input[ng-model=username]', 'Maxim')
-      .assert.visible('input[ng-model=meetingName]')
-      .setValue('input[ng-model=meetingName]', [longMeetingName, browser.Keys.ENTER])
-      .waitForElementVisible('#navbar', 10000)
-      .getElementSize('#navbar', function (result1) {
-        _this = this;
-        for (var w = 1900; w >= 100; w -= 100) {
-          _this = _this
-            .resizeWindow(w, 1080)
-            .getElementSize('#navbar', function (result2) {
-              this.verify.equal(result2.value.height, result1.value.height);
-            });
-        }
-
-        for (var w = 100; w <= 1900; w += 100) {
-          _this = _this
-            .resizeWindow(w, 1080)
-            .getElementSize('#navbar', function (result2) {
-              this.verify.equal(result2.value.height, result1.value.height);
-            });
-        }
-      })
-      .deleteCookies()
-      .closeWindow()
-      .end();
-  },
-
-  'Checking the margin between the navbar and the top while scaling horizont': function (browser) {
-    var longMeetingName = new Array(101).join('x'); // 100-element array of 'x'
-    browser
-      .url('http://192.168.244.140:4000')
-      .resizeWindow(1920, 1080)
-      .waitForElementVisible('body', 1000)
-      .assert.visible('input[ng-model=username]')
-      .setValue('input[ng-model=username]', 'Maxim')
-      .assert.visible('input[ng-model=meetingName]')
-      .setValue('input[ng-model=meetingName]', [longMeetingName, browser.Keys.ENTER])
-      .waitForElementVisible('#navbar', 10000)
-      .getLocation('#whiteboard-navbar', function (result1) {
-        _this = this;
-        for (var w = 1000; w >= 100; w -= 100) {
-          _this = _this
-            .resizeWindow(w, 1080)
-            .getLocation('#whiteboard-navbar', function (result2) {
-              this.verify.equal(result2.value.y, result1.value.y);
-            });
-        }
-
-        for (var w = 100; w <= 100; w += 100) {
-          _this = _this
-            .resizeWindow(w, 1080)
-            .getLocation('#whiteboard-navbar', function (result2) {
-              this.verify.equal(result2.value.y, result1.value.y);
-            });
-        }
-      })
-      .deleteCookies()
-      .closeWindow()
-      .end();
-  },
-};
diff --git a/bigbluebutton-html5/tests/webdriverio/INSTALL.md b/bigbluebutton-html5/tests/webdriverio/INSTALL.md
new file mode 100644
index 0000000000000000000000000000000000000000..7ecd165f45c3086e9422f31994f4bc431e0775be
--- /dev/null
+++ b/bigbluebutton-html5/tests/webdriverio/INSTALL.md
@@ -0,0 +1,91 @@
+# Acceptance Testing in HTML Client. Getting Started
+
+The test suite for HTML5 client is currently under active development. The following instructions will help you install all the necessary tools and libraries to run the exiting specs and start writing your own tests.
+
+## Run Selenium Server
+
+Assuming that you have the BigBlueButton repository in `/home/firstuser/dev`, navigate to the directory containing acceptance tests:
+```sh
+$ cd /home/firstuser/dev/bigbluebutton/bigbluebutton-html5/tests/webdriverio
+```
+
+Now, you should navigate to the `tools` directory inside `webdriverio`. This folder will store all the Selenium- and browser-related third-party files that you need to download.
+
+Download Selenium jar file:
+```sh
+$ curl -O http://selenium-release.storage.googleapis.com/3.4/selenium-server-standalone-3.4.0.jar
+```
+
+and browser-specific WebDriver server.
+
+Firefox WebDriver (GeckoDriver):
+```sh
+$ curl -L https://github.com/mozilla/geckodriver/releases/download/v0.16.1/geckodriver-v0.16.1-linux64.tar.gz | tar xz
+```
+
+Chrome WebDriver (ChromeDriver):
+```sh
+$ wget https://chromedriver.storage.googleapis.com/2.29/chromedriver_linux64.zip
+$ unzip chromedriver_linux64.zip
+```
+
+Along with the WebDriver, you need to install the browser itself.
+
+How to install Chrome:
+```sh
+$ wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
+$ sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
+$ sudo apt-get update
+$ sudo apt-get install google-chrome-stable
+```
+
+How to install Firefox:
+```sh
+$ wget sourceforge.net/projects/ubuntuzilla/files/mozilla/apt/pool/main/f/firefox-mozilla-build/firefox-mozilla-build_53.0.3-0ubuntu1_amd64.deb
+$ sudo dpkg -i firefox-mozilla-build_53.0.3-0ubuntu1_amd64.deb
+```
+
+In order to run headless browser, we will use Xvfb display server:
+```sh
+$ sudo apt-get install xvfb
+```
+
+At this point, you can run the Selenium server (replace `./geckodriver` with `./chromedriver` if you use Chrome):
+```sh
+$ xvfb-run java -jar selenium-server-standalone-3.4.0.jar
+```
+
+If you get an error `Xvfb failed to start`, run it with an `-a` option (Xvfb will use another display if the current one is already in use):
+```sh
+$ xvfb-run -a java -jar selenium-server-standalone-3.4.0.jar
+```
+
+Congratulations! You have Selenium server up and running. It is ready to handle your test cases. Now, keep the `xvfb-run` process running and continue in a new terminal session.
+
+## Run the test specs
+
+We use WebdriverIO interface to write the acceptance test cases. In order to execute the existing tests, you need to use `wdio` test runner. By default, it will look into any `*.spec.js` file inside the `/home/firstuser/dev/bigbluebutton/bigbluebutton-html5/tests/webdriverio/specs` directory. You can change the location of the test specs by modifying the `wdio` config file: `wdio.conf.js` (inside the `webdriverio` directory).
+
+Before proceeding any further, make sure HTML5 client is up and running.
+Node.js installation is also required:
+
+```sh
+$ sudo apt-get install nodejs-legacy
+```
+
+You can run all of the existing test specs with a single npm command:
+
+```sh
+$ cd /home/firstuser/dev/bigbluebutton/bigbluebutton-html5
+$ npm run test
+```
+
+### Test suites
+
+To make it easier to run a single specific set of tests, we group the specs into test suits. All the suits are defined in `wdio.conf.js`.
+
+To run a single test suite, you need to pass its name to the npm script:
+```sh
+$ npm run test -- --suite login
+```
+
diff --git a/bigbluebutton-html5/tests/webdriverio/pageobjects/landing.page.js b/bigbluebutton-html5/tests/webdriverio/pageobjects/landing.page.js
new file mode 100644
index 0000000000000000000000000000000000000000..d93adeddadff045c273985c49b8da20d913a631b
--- /dev/null
+++ b/bigbluebutton-html5/tests/webdriverio/pageobjects/landing.page.js
@@ -0,0 +1,49 @@
+'use strict';
+
+let Page = require('./page');
+let pageObject = new Page();
+
+class LandingPage extends Page {
+  open() {
+    super.open('demo/demoHTML5.jsp');
+  }
+
+  get title() {
+    return 'Join Meeting via HTML5 Client';
+  }
+
+  get url() {
+    return 'http://localhost:8080/demo/demoHTML5.jsp';
+  }
+
+  get username() {
+    return $('input[name=username]');
+  }
+
+  get joinButton() {
+    return $('input[type=submit]');
+  }
+
+  joinWithButtonClick() {
+    this.joinButton.click();
+  }
+
+  joinWithEnterKey() {
+    pageObject.pressEnter();
+  }
+
+  get loadedHomePage() {
+    return $('#app');
+  }
+}
+
+// To use in the future tests that will require login
+browser.addCommand('loginToClient', function (page) {
+  page.open();
+  page.username.waitForExist();
+  page.username.setValue('Maxim');
+  page.joinWithButtonClick();
+});
+
+module.exports = new LandingPage();
+
diff --git a/bigbluebutton-html5/tests/webdriverio/pageobjects/page.js b/bigbluebutton-html5/tests/webdriverio/pageobjects/page.js
new file mode 100644
index 0000000000000000000000000000000000000000..5bd871a89d49236687e663fc479a5f7a3374a8e7
--- /dev/null
+++ b/bigbluebutton-html5/tests/webdriverio/pageobjects/page.js
@@ -0,0 +1,18 @@
+'use strict';
+
+class Page {
+  open(path) {
+    browser.url(path);
+  }
+
+  pressEnter() {
+    browser.keys('Enter');
+  }
+
+  isFirefox() {
+    return browser.desiredCapabilities.browserName == 'firefox';
+  }
+}
+
+module.exports = Page;
+
diff --git a/bigbluebutton-html5/tests/webdriverio/specs/login.spec.js b/bigbluebutton-html5/tests/webdriverio/specs/login.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..8680daeb450a86f3b537394239312ffc22c1e629
--- /dev/null
+++ b/bigbluebutton-html5/tests/webdriverio/specs/login.spec.js
@@ -0,0 +1,57 @@
+'use strict';
+
+let LandingPage = require('../pageobjects/landing.page');
+let chai = require('chai');
+
+describe('Landing page', function () {
+  it('should have correct title', function () {
+    LandingPage.open();
+    chai.expect(browser.getTitle()).to.equal(LandingPage.title);
+  });
+
+  it('should allow user to login if the username is specified and the Join button is clicked',
+    function () {
+      LandingPage.open();
+      LandingPage.username.waitForExist();
+      LandingPage.username.setValue('Maxim');
+      LandingPage.joinWithButtonClick();
+      LandingPage.loadedHomePage.waitForExist(5000);
+    });
+
+  it('should not allow user to login if the username is not specified (login using a button)',
+    function () {
+      LandingPage.open();
+
+      // we intentionally don't enter username here
+
+      LandingPage.joinWithButtonClick();
+
+      browser.pause(5000); // amount of time we usually wait for the home page to appear
+
+      // verify that we are still on the landing page
+      chai.expect(browser.getUrl()).to.equal(LandingPage.url);
+    });
+
+  if (!LandingPage.isFirefox()) {
+    it('should allow user to login if the username is specified and then Enter key is pressed',
+      function () {
+        LandingPage.open();
+        LandingPage.username.waitForExist();
+        LandingPage.username.setValue('Maxim');
+        LandingPage.joinWithEnterKey();
+        LandingPage.loadedHomePage.waitForExist(5000);
+      });
+
+    it('should not allow user to login if the username is not specified (login using Enter key)',
+      function () {
+        LandingPage.open();
+
+        // we intentionally don't enter username here
+
+        LandingPage.joinWithEnterKey();
+        browser.pause(5000); // amount of time we usually wait for the gome page to appear
+        chai.expect(browser.getUrl()).to.equal(LandingPage.url);
+      });
+  }
+});
+
diff --git a/bigbluebutton-html5/tests/webdriverio/tools/.gitkeep b/bigbluebutton-html5/tests/webdriverio/tools/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/bigbluebutton-html5/tests/webdriverio/wdio.conf.js b/bigbluebutton-html5/tests/webdriverio/wdio.conf.js
new file mode 100644
index 0000000000000000000000000000000000000000..bd5d50e992b2640f294e92ad23bcccc4ec1a1325
--- /dev/null
+++ b/bigbluebutton-html5/tests/webdriverio/wdio.conf.js
@@ -0,0 +1,23 @@
+exports.config = {
+  specs: ['tests/webdriverio/specs/**/*.spec.js'],
+  capabilities: [
+    {
+      browserName: 'chrome',
+    },
+  ],
+  baseUrl: 'http://localhost:8080',
+  framework: 'jasmine',
+  reporters: ['spec', 'junit'],
+  reporterOptions: {
+    junit: {
+      outputDir: './tests/webdriverio/reports',
+    },
+  },
+  screenshotPath: 'screenshots',
+  suites: {
+    login: [
+      'tests/webdriverio/specs/login.spec.js',
+    ],
+  },
+};
+