diff --git a/src/app/Http/Controllers/API/V4/OpenViduController.php b/src/app/Http/Controllers/API/V4/OpenViduController.php index 1554abcb..e26c2f68 100644 --- a/src/app/Http/Controllers/API/V4/OpenViduController.php +++ b/src/app/Http/Controllers/API/V4/OpenViduController.php @@ -1,585 +1,590 @@ first(); // This isn't a room, bye bye if (!$room) { return $this->errorResponse(404, \trans('meet.room-not-found')); } // Only the moderator can do it if (!$this->isModerator($room)) { return $this->errorResponse(403); } if (!$room->requestAccept($reqid)) { return $this->errorResponse(500, \trans('meet.session-request-accept-error')); } return response()->json(['status' => 'success']); } /** * Deny the room join request. * * @param string $id Room identifier (name) * @param string $reqid Request identifier * * @return \Illuminate\Http\JsonResponse */ public function denyJoinRequest($id, $reqid) { $room = Room::where('name', $id)->first(); // This isn't a room, bye bye if (!$room) { return $this->errorResponse(404, \trans('meet.room-not-found')); } // Only the moderator can do it if (!$this->isModerator($room)) { return $this->errorResponse(403); } if (!$room->requestDeny($reqid)) { return $this->errorResponse(500, \trans('meet.session-request-deny-error')); } return response()->json(['status' => 'success']); } /** * Close the room session. * * @param string $id Room identifier (name) * * @return \Illuminate\Http\JsonResponse */ public function closeRoom($id) { $room = Room::where('name', $id)->first(); // This isn't a room, bye bye if (!$room) { return $this->errorResponse(404, \trans('meet.room-not-found')); } $user = Auth::guard()->user(); // Only the room owner can do it if (!$user || $user->id != $room->user_id) { return $this->errorResponse(403); } if (!$room->deleteSession()) { return $this->errorResponse(500, \trans('meet.session-close-error')); } return response()->json([ 'status' => 'success', 'message' => __('meet.session-close-success'), ]); } /** * Create a connection for screen sharing. * * @param string $id Room identifier (name) * * @return \Illuminate\Http\JsonResponse */ public function createConnection($id) { $room = Room::where('name', $id)->first(); // This isn't a room, bye bye if (!$room) { return $this->errorResponse(404, \trans('meet.room-not-found')); } $connection = $this->getConnectionFromRequest(); if ( !$connection || $connection->session_id != $room->session_id || ($connection->role & Room::ROLE_PUBLISHER) == 0 ) { return $this->errorResponse(403); } $response = $room->getSessionToken(Room::ROLE_SCREEN); return response()->json(['status' => 'success', 'token' => $response['token']]); } /** * Dismiss the participant/connection from the session. * * @param string $id Room identifier (name) * @param string $conn Connection identifier * * @return \Illuminate\Http\JsonResponse */ public function dismissConnection($id, $conn) { $connection = Connection::where('id', $conn)->first(); // There's no such connection, bye bye if (!$connection || $connection->room->name != $id) { return $this->errorResponse(404, \trans('meet.connection-not-found')); } // Only the moderator can do it if (!$this->isModerator($connection->room)) { return $this->errorResponse(403); } if (!$connection->dismiss()) { return $this->errorResponse(500, \trans('meet.connection-dismiss-error')); } return response()->json(['status' => 'success']); } /** * Listing of rooms that belong to the authenticated user. * * @return \Illuminate\Http\JsonResponse */ public function index() { $user = Auth::guard()->user(); $rooms = Room::where('user_id', $user->id)->orderBy('name')->get(); if (count($rooms) == 0) { // Create a room for the user (with a random and unique name) while (true) { $name = strtolower(\App\Utils::randStr(3, 3, '-')); if (!Room::where('name', $name)->count()) { break; } } $room = Room::create([ 'name' => $name, 'user_id' => $user->id ]); $rooms = collect([$room]); } $result = [ 'list' => $rooms, 'count' => count($rooms), ]; return response()->json($result); } /** * Join the room session. Each room has one owner, and the room isn't open until the owner * joins (and effectively creates the session). * * @param string $id Room identifier (name) * * @return \Illuminate\Http\JsonResponse */ public function joinRoom($id) { $room = Room::where('name', $id)->first(); // Room does not exist, or the owner is deleted if (!$room || !$room->owner) { return $this->errorResponse(404, \trans('meet.room-not-found')); } // Check if there's still a valid beta entitlement for the room owner $sku = \App\Sku::where('title', 'meet')->first(); if ($sku && !$room->owner->entitlements()->where('sku_id', $sku->id)->first()) { return $this->errorResponse(404, \trans('meet.room-not-found')); } $user = Auth::guard()->user(); $isOwner = $user && $user->id == $room->user_id; $init = !empty(request()->input('init')); // There's no existing session if (!$room->hasSession()) { // Participants can't join the room until the session is created by the owner if (!$isOwner) { return $this->errorResponse(422, \trans('meet.session-not-found'), ['code' => 323]); } // The room owner can create the session on request if (!$init) { return $this->errorResponse(422, \trans('meet.session-not-found'), ['code' => 324]); } $session = $room->createSession(); if (empty($session)) { return $this->errorResponse(500, \trans('meet.session-create-error')); } } $password = (string) $room->getSetting('password'); $config = [ 'locked' => $room->getSetting('locked') === 'true', + 'nomedia' => $room->getSetting('nomedia') === 'true', 'password' => $isOwner ? $password : '', 'requires_password' => !$isOwner && strlen($password), ]; $response = ['config' => $config]; // Validate room password if (!$isOwner && strlen($password)) { $request_password = request()->input('password'); if ($request_password !== $password) { return $this->errorResponse(422, \trans('meet.session-password-error'), $response + ['code' => 325]); } } // Handle locked room if (!$isOwner && $config['locked']) { $nickname = request()->input('nickname'); $picture = request()->input('picture'); $requestId = request()->input('requestId'); $request = $requestId ? $room->requestGet($requestId) : null; $error = \trans('meet.session-room-locked-error'); // Request already has been processed (not accepted yet, but it could be denied) if (empty($request['status']) || $request['status'] != Room::REQUEST_ACCEPTED) { if (!$request) { if (empty($nickname) || empty($requestId) || !preg_match('/^[a-z0-9]{8,32}$/i', $requestId)) { return $this->errorResponse(422, $error, $response + ['code' => 326]); } if (empty($picture)) { $svg = file_get_contents(resource_path('images/user.svg')); $picture = 'data:image/svg+xml;base64,' . base64_encode($svg); } elseif (!preg_match('|^data:image/png;base64,[a-zA-Z0-9=+/]+$|', $picture)) { return $this->errorResponse(422, $error, $response + ['code' => 326]); } // TODO: Resize when big/make safe the user picture? $request = ['nickname' => $nickname, 'requestId' => $requestId, 'picture' => $picture]; if (!$room->requestSave($requestId, $request)) { // FIXME: should we use error code 500? return $this->errorResponse(422, $error, $response + ['code' => 326]); } // Send the request (signal) to the owner $result = $room->signal('joinRequest', $request, Room::ROLE_MODERATOR); } return $this->errorResponse(422, $error, $response + ['code' => 327]); } } // Initialize connection tokens if ($init) { // Choose the connection role - $canPublish = !empty(request()->input('canPublish')); + $canPublish = empty($config['nomedia']) && !empty(request()->input('canPublish')); $role = $canPublish ? Room::ROLE_PUBLISHER : Room::ROLE_SUBSCRIBER; if ($isOwner) { $role |= Room::ROLE_MODERATOR; $role |= Room::ROLE_OWNER; } // Create session token for the current user/connection $response = $room->getSessionToken($role); if (empty($response)) { return $this->errorResponse(500, \trans('meet.session-join-error')); } // Get up-to-date connections metadata $response['connections'] = $room->getSessionConnections(); $response_code = 200; $response['role'] = $role; $response['config'] = $config; } else { $response_code = 422; $response['code'] = 322; } return response()->json($response, $response_code); } /** * Set the domain configuration. * * @param string $id Room identifier (name) * * @return \Illuminate\Http\JsonResponse|void */ public function setRoomConfig($id) { $room = Room::where('name', $id)->first(); // Room does not exist, or the owner is deleted if (!$room || !$room->owner) { return $this->errorResponse(404); } $user = Auth::guard()->user(); // Only room owner can configure the room if ($user->id != $room->user_id) { return $this->errorResponse(403); } $input = request()->input(); $errors = []; foreach ($input as $key => $value) { switch ($key) { case 'password': if ($value === null || $value === '') { $input[$key] = null; } else { // TODO: Do we have to validate the password in any way? } break; case 'locked': $input[$key] = $value ? 'true' : null; break; + case 'nomedia': + $input[$key] = $value ? 'true' : null; + break; + default: $errors[$key] = \trans('meet.room-unsupported-option-error'); } } if (!empty($errors)) { return response()->json(['status' => 'error', 'errors' => $errors], 422); } if (!empty($input)) { $room->setSettings($input); } return response()->json([ 'status' => 'success', 'message' => \trans('meet.room-setconfig-success'), ]); } /** * Update the participant/connection parameters (e.g. role). * * @param string $id Room identifier (name) * @param string $conn Connection identifier * * @return \Illuminate\Http\JsonResponse */ public function updateConnection($id, $conn) { $connection = Connection::where('id', $conn)->first(); // There's no such connection, bye bye if (!$connection || $connection->room->name != $id) { return $this->errorResponse(404, \trans('meet.connection-not-found')); } foreach (request()->input() as $key => $value) { switch ($key) { case 'hand': // Only possible on user's own connection(s) if (!$this->isSelfConnection($connection)) { return $this->errorResponse(403); } if ($value) { // Store current time, so we know the order in the queue $connection->metadata = ['hand' => time()] + $connection->metadata; } else { $connection->metadata = array_diff_key($connection->metadata, ['hand' => 0]); } break; case 'language': // Only the moderator can do it if (!$this->isModerator($connection->room)) { return $this->errorResponse(403); } if ($value) { if (preg_match('/^[a-z]{2}$/', $value)) { $connection->metadata = ['language' => $value] + $connection->metadata; } } else { $connection->metadata = array_diff_key($connection->metadata, ['language' => 0]); } break; case 'role': // Only the moderator can do it if (!$this->isModerator($connection->room)) { return $this->errorResponse(403); } // The 'owner' role is not assignable if ($value & Room::ROLE_OWNER && !($connection->role & Room::ROLE_OWNER)) { return $this->errorResponse(403); } elseif (!($value & Room::ROLE_OWNER) && ($connection->role & Room::ROLE_OWNER)) { return $this->errorResponse(403); } // The room owner has always a 'moderator' role if (!($value & Room::ROLE_MODERATOR) && $connection->role & Room::ROLE_OWNER) { $value |= Room::ROLE_MODERATOR; } // Promotion to publisher? Put the user hand down if ($value & Room::ROLE_PUBLISHER && !($connection->role & Room::ROLE_PUBLISHER)) { $connection->metadata = array_diff_key($connection->metadata, ['hand' => 0]); } // Non-publisher cannot be a language interpreter if (!($value & Room::ROLE_PUBLISHER)) { $connection->metadata = array_diff_key($connection->metadata, ['language' => 0]); } $connection->{$key} = $value; break; } } // The connection observer will send a signal to everyone when needed $connection->save(); return response()->json(['status' => 'success']); } /** * Webhook as triggered from OpenVidu server * * @param \Illuminate\Http\Request $request The API request. * * @return \Illuminate\Http\Response The response */ public function webhook(Request $request) { \Log::debug($request->getContent()); switch ((string) $request->input('event')) { case 'sessionDestroyed': // When all participants left the room OpenVidu dispatches sessionDestroyed // event. We'll remove the session reference from the database. $sessionId = $request->input('sessionId'); $room = Room::where('session_id', $sessionId)->first(); if ($room) { $room->session_id = null; $room->save(); } // Remove all connections // Note: We could remove connections one-by-one via the 'participantLeft' event // but that could create many INSERTs when the session (with many participants) ends // So, it is better to remove them all in a single INSERT. Connection::where('session_id', $sessionId)->delete(); break; } return response('Success', 200); } /** * Check if current user is a moderator for the specified room. * * @param \App\OpenVidu\Room $room The room * * @return bool True if the current user is the room moderator */ protected function isModerator(Room $room): bool { $user = Auth::guard()->user(); // The room owner is a moderator if ($user && $user->id == $room->user_id) { return true; } // Moderator's authentication via the extra request header if ( ($connection = $this->getConnectionFromRequest()) && $connection->session_id === $room->session_id && $connection->role & Room::ROLE_MODERATOR ) { return true; } return false; } /** * Check if current user "owns" the specified connection. * * @param \App\OpenVidu\Connection $connection The connection * * @return bool */ protected function isSelfConnection(Connection $connection): bool { return ($conn = $this->getConnectionFromRequest()) && $conn->id === $connection->id; } /** * Get the connection object for the token in current request headers. * It will also validate the token. * * @return \App\OpenVidu\Connection|null Connection (if exists and the token is valid) */ protected function getConnectionFromRequest() { // Authenticate the user via the extra request header if ($token = request()->header(self::AUTH_HEADER)) { list($connId, ) = explode(':', base64_decode($token), 2); if ( ($connection = Connection::find($connId)) && $connection->metadata['authToken'] === $token ) { return $connection; } } return null; } } diff --git a/src/resources/vue/Meet/Room.vue b/src/resources/vue/Meet/Room.vue index 57db44c1..05a4052e 100644 --- a/src/resources/vue/Meet/Room.vue +++ b/src/resources/vue/Meet/Room.vue @@ -1,751 +1,749 @@ {{ session.channel.toUpperCase() }} - none - {{ languages[code] }} - - + + Set up your session None {{ mic.label }} None {{ cam.label }} JOIN NOW I'm the owner JOIN Room closed × The session has been closed by the room owner. Media setup × None {{ mic.label }} None {{ cam.label }} - + diff --git a/src/resources/vue/Meet/SessionSecurityOptions.vue b/src/resources/vue/Meet/RoomOptions.vue similarity index 80% rename from src/resources/vue/Meet/SessionSecurityOptions.vue rename to src/resources/vue/Meet/RoomOptions.vue index 99877acb..950bd752 100644 --- a/src/resources/vue/Meet/SessionSecurityOptions.vue +++ b/src/resources/vue/Meet/RoomOptions.vue @@ -1,110 +1,124 @@ - + - Security options + Room options × - + Password: {{ config.password }} none Save Clear password Set password You can add a password to your meeting. Participants will have to provide the password before they are allowed to join the meeting. - + Locked room: - When the room is locked participants have to be approved by you + When the room is locked participants have to be approved by a moderator before they could join the meeting. + + + + Subscribers only: + + + + Forces all participants to join as subscribers (with camera and microphone turned off). + Moderators will be able to promote them to publishers throughout the session. + + diff --git a/src/tests/Browser/Meet/RoomControlsTest.php b/src/tests/Browser/Meet/RoomControlsTest.php index f75e3886..06aef53a 100644 --- a/src/tests/Browser/Meet/RoomControlsTest.php +++ b/src/tests/Browser/Meet/RoomControlsTest.php @@ -1,377 +1,377 @@ setupTestRoom(); } public function tearDown(): void { $this->resetTestRoom(); parent::tearDown(); } /** * Test fullscreen buttons * * @group openvidu */ public function testFullscreen(): void { // TODO: This test does not work in headless mode $this->markTestIncomplete(); /* $this->browse(function (Browser $browser) { // Join the room as an owner (authenticate) $browser->visit(new RoomPage('john')) ->click('@setup-button') ->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@setup-form') ->assertVisible('@login-form') ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->assertMissing('@login-form') ->waitUntilMissing('@setup-status-message.loading') ->click('@setup-button') ->waitFor('@session') // Test fullscreen for the whole room ->click('@menu button.link-fullscreen.closed') ->assertVisible('@toolbar') ->assertVisible('@session') ->assertMissing('nav') ->assertMissing('@menu button.link-fullscreen.closed') ->click('@menu button.link-fullscreen.open') ->assertVisible('nav') // Test fullscreen for the participant video ->click('@session button.link-fullscreen.closed') ->assertVisible('@session') ->assertMissing('@toolbar') ->assertMissing('nav') ->assertMissing('@session button.link-fullscreen.closed') ->click('@session button.link-fullscreen.open') ->assertVisible('nav') ->assertVisible('@toolbar'); }); */ } /** * Test nickname and muting audio/video * * @group openvidu */ public function testNicknameAndMuting(): void { $this->browse(function (Browser $owner, Browser $guest) { // Join the room as an owner (authenticate) $owner->visit(new RoomPage('john')) ->click('@setup-button') ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'john') ->keys('@setup-nickname-input', '{enter}') // Test form submit with Enter key ->waitFor('@session'); // In another browser act as a guest $guest->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-status-message') ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') //->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // Assert current UI state $owner->assertToolbar([ 'audio' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'video' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'fullscreen' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, - 'security' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, + 'options' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'logout' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, ]) ->whenAvailable('div.meet-video.self', function (Browser $browser) { $browser->waitFor('video') ->assertAudioMuted('video', true) ->assertSeeIn('.meet-nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->whenAvailable('div.meet-video:not(.self)', function (Browser $browser) { $browser->waitFor('video') ->assertVisible('.meet-nickname') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertVisible('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2); // Assert current UI state $guest->assertToolbar([ 'audio' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'video' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'fullscreen' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'logout' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, ]) ->whenAvailable('div.meet-video:not(.self)', function (Browser $browser) { $browser->waitFor('video') ->assertSeeIn('.meet-nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->whenAvailable('div.meet-video.self', function (Browser $browser) { $browser->waitFor('video') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertVisible('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2); // Test nickname change propagation $guest->setNickname('div.meet-video.self', 'guest'); $owner->waitFor('div.meet-video:not(.self) .meet-nickname') ->assertSeeIn('div.meet-video:not(.self) .meet-nickname', 'guest'); // Test muting audio $owner->click('@menu button.link-audio') ->assertToolbarButtonState('audio', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED) ->assertVisible('div.meet-video.self .status .status-audio'); // FIXME: It looks that we can't just check the element state // We might consider using OpenVidu API to make sure $guest->waitFor('div.meet-video:not(.self) .status .status-audio'); // Test unmuting audio $owner->click('@menu button.link-audio') ->assertToolbarButtonState('audio', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED) ->assertMissing('div.meet-video.self .status .status-audio'); $guest->waitUntilMissing('div.meet-video:not(.self) .status .status-audio'); // Test muting audio with a keyboard shortcut (key 'm') $owner->driver->getKeyboard()->sendKeys('m'); $owner->assertToolbarButtonState('audio', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED) ->assertVisible('div.meet-video.self .status .status-audio'); $guest->waitFor('div.meet-video:not(.self) .status .status-audio') ->assertAudioMuted('div.meet-video:not(.self) video', true); // Test unmuting audio with a keyboard shortcut (key 'm') $owner->driver->getKeyboard()->sendKeys('m'); $owner->assertToolbarButtonState('audio', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED) ->assertMissing('div.meet-video.self .status .status-audio'); $guest->waitUntilMissing('div.meet-video:not(.self) .status .status-audio') ->assertAudioMuted('div.meet-video:not(.self) video', false); // Test muting video $owner->click('@menu button.link-video') ->assertToolbarButtonState('video', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED) ->assertVisible('div.meet-video.self .status .status-video'); // FIXME: It looks that we can't just check the element state // We might consider using OpenVidu API to make sure $guest->waitFor('div.meet-video:not(.self) .status .status-video'); // Test unmuting video $owner->click('@menu button.link-video') ->assertToolbarButtonState('video', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED) ->assertMissing('div.meet-video.self .status .status-video'); $guest->waitUntilMissing('div.meet-video:not(.self) .status .status-video'); // Test muting other user $guest->with('div.meet-video:not(.self)', function (Browser $browser) { $browser->click('.controls button.link-audio') ->assertAudioMuted('video', true) ->assertVisible('.controls button.link-audio.text-danger') ->click('.controls button.link-audio') ->assertAudioMuted('video', false) ->assertVisible('.controls button.link-audio:not(.text-danger)'); }); }); } /** * Test text chat * * @group openvidu */ public function testChat(): void { $this->browse(function (Browser $owner, Browser $guest) { // Join the room as an owner $owner->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'john') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // In another browser act as a guest $guest->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-status-message') ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') // ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // Test chat elements $owner->click('@menu button.link-chat') ->assertToolbarButtonState('chat', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED) ->assertVisible('@chat') ->assertVisible('@session') ->assertFocused('@chat-input') ->assertElementsCount('@chat-list .message', 0) ->keys('@chat-input', 'test1', '{enter}') ->assertValue('@chat-input', '') ->waitFor('@chat-list .message') ->assertElementsCount('@chat-list .message', 1) ->assertSeeIn('@chat-list .message .nickname', 'john') ->assertSeeIn('@chat-list .message div:last-child', 'test1'); $guest->waitFor('@menu button.link-chat .badge') ->assertTextRegExp('@menu button.link-chat .badge', '/^1$/') ->click('@menu button.link-chat') ->assertToolbarButtonState('chat', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED) ->assertMissing('@menu button.link-chat .badge') ->assertVisible('@chat') ->assertVisible('@session') ->assertElementsCount('@chat-list .message', 1) ->assertSeeIn('@chat-list .message .nickname', 'john') ->assertSeeIn('@chat-list .message div:last-child', 'test1'); // Test the number of (hidden) incoming messages $guest->click('@menu button.link-chat') ->assertMissing('@chat'); $owner->keys('@chat-input', 'test2', '{enter}', 'test3', '{enter}') ->assertElementsCount('@chat-list .message', 1) ->assertSeeIn('@chat-list .message .nickname', 'john') ->assertElementsCount('@chat-list .message div', 4) ->assertSeeIn('@chat-list .message div:last-child', 'test3'); $guest->waitFor('@menu button.link-chat .badge') ->assertSeeIn('@menu button.link-chat .badge', '2') ->click('@menu button.link-chat') ->assertElementsCount('@chat-list .message', 1) ->assertSeeIn('@chat-list .message .nickname', 'john') ->assertSeeIn('@chat-list .message div:last-child', 'test3') ->keys('@chat-input', 'guest1', '{enter}') ->assertElementsCount('@chat-list .message', 2) ->assertMissing('@chat-list .message:last-child .nickname') ->assertSeeIn('@chat-list .message:last-child div:last-child', 'guest1'); $owner->assertElementsCount('@chat-list .message', 2) ->assertMissing('@chat-list .message:last-child .nickname') ->assertSeeIn('@chat-list .message:last-child div:last-child', 'guest1'); // Test nickname change is propagated to chat messages $guest->setNickname('div.meet-video.self', 'guest') ->keys('@chat-input', 'guest2', '{enter}') ->assertElementsCount('@chat-list .message', 2) ->assertSeeIn('@chat-list .message:last-child .nickname', 'guest') ->assertSeeIn('@chat-list .message:last-child div:last-child', 'guest2'); $owner->assertElementsCount('@chat-list .message', 2) ->assertSeeIn('@chat-list .message:last-child .nickname', 'guest') ->assertSeeIn('@chat-list .message:last-child div:last-child', 'guest2'); // TODO: Test text chat features, e.g. link handling }); } /** * Test screen sharing * * @group openvidu */ public function testShareScreen(): void { $this->browse(function (Browser $owner, Browser $guest) { // Join the room as an owner $owner->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'john') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // In another browser act as a guest $guest->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') // Join the room, disable cam/mic ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // Test screen sharing $owner->assertToolbarButtonState('screen', RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED) ->assertElementsCount('@session div.meet-video', 1) ->click('@menu button.link-screen') ->whenAvailable('div.meet-video:not(.self)', function (Browser $browser) { $browser->waitFor('video') ->assertSeeIn('.meet-nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertVisible('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@subscribers .meet-subscriber', 1) ->assertToolbarButtonState('screen', RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED); $guest ->whenAvailable('div.meet-video:nth-child(3)', function (Browser $browser) { $browser->waitFor('video') ->assertSeeIn('.meet-nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertVisible('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@subscribers .meet-subscriber', 1); }); } } diff --git a/src/tests/Browser/Meet/RoomSecurityTest.php b/src/tests/Browser/Meet/RoomOptionsTest.php similarity index 75% rename from src/tests/Browser/Meet/RoomSecurityTest.php rename to src/tests/Browser/Meet/RoomOptionsTest.php index c3232be1..d569d6bd 100644 --- a/src/tests/Browser/Meet/RoomSecurityTest.php +++ b/src/tests/Browser/Meet/RoomOptionsTest.php @@ -1,270 +1,326 @@ setupTestRoom(); } public function tearDown(): void { $this->resetTestRoom(); parent::tearDown(); } /** * Test password protected room * * @group openvidu */ public function testRoomPassword(): void { $this->browse(function (Browser $owner, Browser $guest) { $room = Room::where('name', 'john')->first(); // Join the room as an owner (authenticate) $owner->visit(new RoomPage('john')) ->click('@setup-button') ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-password-input') ->clickWhenEnabled('@setup-button') ->waitFor('@session') - // Enter Security option dialog - ->click('@menu button.link-security') - ->with(new Dialog('#security-options-dialog'), function (Browser $browser) use ($room) { - $browser->assertSeeIn('@title', 'Security options') + // Enter room option dialog + ->click('@menu button.link-options') + ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { + $browser->assertSeeIn('@title', 'Room options') ->assertSeeIn('@button-action', 'Close') ->assertElementsCount('.modal-footer button', 1) ->assertSeeIn('#password-input .label', 'Password:') ->assertSeeIn('#password-input-text.text-muted', 'none') ->assertVisible('#password-input + small') ->assertSeeIn('#password-set-btn', 'Set password') ->assertElementsCount('#password-input button', 1) ->assertMissing('#password-input input') // Test setting a password ->click('#password-set-btn') ->assertMissing('#password-input-text') ->assertVisible('#password-input input') ->assertValue('#password-input input', '') ->assertSeeIn('#password-input #password-save-btn', 'Save') ->assertElementsCount('#password-input button', 1) ->type('#password-input input', 'pass') ->click('#password-input #password-save-btn') ->assertToast(Toast::TYPE_SUCCESS, 'Room configuration updated successfully.') ->assertMissing('#password-input input') ->assertSeeIn('#password-input-text:not(.text-muted)', 'pass') ->assertSeeIn('#password-clear-btn.btn-outline-danger', 'Clear password') ->assertElementsCount('#password-input button', 1) ->click('@button-action'); $this->assertSame('pass', $room->fresh()->getSetting('password')); }); // In another browser act as a guest, expect password required $guest->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertSeeIn('@setup-status-message', "Please, provide a valid password.") ->assertVisible('@setup-form .input-group:nth-child(4) svg') ->assertAttribute('@setup-form .input-group:nth-child(4) .input-group-text', 'title', 'Password') ->assertAttribute('@setup-password-input', 'placeholder', 'Password') ->assertValue('@setup-password-input', '') ->assertSeeIn('@setup-button', "JOIN") // Try to join w/o password ->clickWhenEnabled('@setup-button') ->waitFor('#setup-password.is-invalid') // Try to join with a valid password ->type('#setup-password', 'pass') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // Test removing the password - $owner->click('@menu button.link-security') - ->with(new Dialog('#security-options-dialog'), function (Browser $browser) use ($room) { - $browser->assertSeeIn('@title', 'Security options') + $owner->click('@menu button.link-options') + ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { + $browser->assertSeeIn('@title', 'Room options') ->assertSeeIn('#password-input-text:not(.text-muted)', 'pass') ->assertSeeIn('#password-clear-btn.btn-outline-danger', 'Clear password') ->assertElementsCount('#password-input button', 1) ->click('#password-clear-btn') ->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.") ->assertMissing('#password-input input') ->assertSeeIn('#password-input-text.text-muted', 'none') ->assertSeeIn('#password-set-btn', 'Set password') ->assertElementsCount('#password-input button', 1) ->click('@button-action'); $this->assertSame(null, $room->fresh()->getSetting('password')); }); }); } /** * Test locked room (denying the join request) * * @group openvidu */ public function testLockedRoomDeny(): void { $this->browse(function (Browser $owner, Browser $guest) { - // Make sure there's no session yet $room = Room::where('name', 'john')->first(); // Join the room as an owner (authenticate) $owner->visit(new RoomPage('john')) // ->click('@setup-button') // ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'John') ->clickWhenEnabled('@setup-button') ->waitFor('@session') - // Enter Security option dialog - ->click('@menu button.link-security') - ->with(new Dialog('#security-options-dialog'), function (Browser $browser) use ($room) { - $browser->assertSeeIn('@title', 'Security options') + // Enter room option dialog + ->click('@menu button.link-options') + ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { + $browser->assertSeeIn('@title', 'Room options') ->assertSeeIn('#room-lock label', 'Locked room:') ->assertVisible('#room-lock input[type=checkbox]:not(:checked)') ->assertVisible('#room-lock + small') // Test setting the lock ->click('#room-lock input') ->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.") ->click('@button-action'); $this->assertSame('true', $room->fresh()->getSetting('locked')); }); // In another browser act as a guest $guest->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertButtonEnabled('@setup-button') ->assertSeeIn('@setup-button.btn-success', 'JOIN NOW') // try without the nickname ->clickWhenEnabled('@setup-button') ->waitFor('@setup-nickname-input.is-invalid') ->assertSeeIn( '@setup-status-message', "The room is locked. Please, enter your name and try again." ) ->assertMissing('@setup-password-input') ->assertButtonEnabled('@setup-button') ->assertSeeIn('@setup-button.btn-success', 'JOIN NOW') ->type('@setup-nickname-input', 'Guest') ->clickWhenEnabled('@setup-button') ->assertMissing('@setup-nickname-input.is-invalid') ->waitForText("Waiting for permission to join the room.") ->assertButtonDisabled('@setup-button'); // Test denying the request (this will also test custom toasts) $owner ->whenAvailable(new Toast(Toast::TYPE_CUSTOM), function ($browser) { $browser->assertToastTitle('Join request') ->assertVisible('.toast-header svg.fa-user') ->assertSeeIn('@message', 'Guest requested to join.') ->assertAttributeRegExp('@message img', 'src', '|^data:image|') ->assertSeeIn('@message button.accept.btn-success', 'Accept') ->assertSeeIn('@message button.deny.btn-danger', 'Deny') ->click('@message button.deny'); }) ->waitUntilMissing('.toast') // wait 10 seconds to make sure the request message does not show up again ->pause(10 * 1000) ->assertMissing('.toast'); }); } /** * Test locked room (accepting the join request, and dismissing a user) * * @group openvidu */ public function testLockedRoomAcceptAndDismiss(): void { $this->browse(function (Browser $owner, Browser $guest) { - // Make sure there's no session yet $room = Room::where('name', 'john')->first(); // Join the room as an owner (authenticate) $owner->visit(new RoomPage('john')) // ->click('@setup-button') // ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'John') ->clickWhenEnabled('@setup-button') ->waitFor('@session') - // Enter Security option dialog - ->click('@menu button.link-security') - ->with(new Dialog('#security-options-dialog'), function (Browser $browser) use ($room) { - $browser->assertSeeIn('@title', 'Security options') + // Enter room option dialog + ->click('@menu button.link-options') + ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { + $browser->assertSeeIn('@title', 'Room options') ->assertSeeIn('#room-lock label', 'Locked room:') ->assertVisible('#room-lock input[type=checkbox]:not(:checked)') ->assertVisible('#room-lock + small') // Test setting the lock ->click('#room-lock input') ->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.") ->click('@button-action'); $this->assertSame('true', $room->fresh()->getSetting('locked')); }); // In another browser act as a guest $guest->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'guest') ->clickWhenEnabled('@setup-button') ->waitForText("Waiting for permission to join the room.") ->assertButtonDisabled('@setup-button'); $owner ->whenAvailable(new Toast(Toast::TYPE_CUSTOM), function ($browser) { $browser->assertToastTitle('Join request') ->assertSeeIn('@message', 'guest requested to join.') ->click('@message button.accept'); }); // Guest automatically anters the room $guest->waitFor('@session', 12) // make sure he has no access to the Options menu ->waitFor('@session .meet-video:not(.self)') ->assertSeeIn('@session .meet-video:not(.self) .meet-nickname', 'John') // TODO: Assert title and icon ->click('@session .meet-video:not(.self) .meet-nickname') ->pause(100) ->assertMissing('.dropdown-menu'); // Test dismissing the participant $owner->click('@session .meet-video:not(.self) .meet-nickname') ->waitFor('@session .meet-video:not(.self) .dropdown-menu') ->assertSeeIn('@session .meet-video:not(.self) .dropdown-menu > .action-dismiss', 'Dismiss') ->click('@session .meet-video:not(.self) .dropdown-menu > .action-dismiss') ->waitUntilMissing('.dropdown-menu') ->waitUntilMissing('@session .meet-video:not(.self)'); // Expect a "end of session" dialog on the participant side $guest->with(new Dialog('#leave-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Room closed') ->assertSeeIn('@body', "The session has been closed by the room owner.") ->assertMissing('@button-cancel') ->assertSeeIn('@button-action', 'Close'); }); }); } + + /** + * Test nomedia (subscribers only) feature + * + * @group openvidu + */ + public function testSubscribersOnly(): void + { + $this->browse(function (Browser $owner, Browser $guest) { + $room = Room::where('name', 'john')->first(); + + // Join the room as an owner (authenticate) + $owner->visit(new RoomPage('john')) + // ->click('@setup-button') + // ->submitLogon('john@kolab.org', 'simple123') + ->waitFor('@setup-form') + ->waitUntilMissing('@setup-status-message.loading') + ->type('@setup-nickname-input', 'John') + ->clickWhenEnabled('@setup-button') + ->waitFor('@session') + // Enter room option dialog + ->click('@menu button.link-options') + ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { + $browser->assertSeeIn('@title', 'Room options') + ->assertSeeIn('#room-nomedia label', 'Subscribers only:') + ->assertVisible('#room-nomedia input[type=checkbox]:not(:checked)') + ->assertVisible('#room-nomedia + small') + // Test enabling the option + ->click('#room-nomedia input') + ->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.") + ->click('@button-action'); + + $this->assertSame('true', $room->fresh()->getSetting('nomedia')); + }); + + // In another browser act as a guest + $guest->visit(new RoomPage('john')) + ->waitFor('@setup-form') + ->waitUntilMissing('@setup-status-message.loading') + ->type('@setup-nickname-input', 'John') + ->clickWhenEnabled('@setup-button') + // expect the owner to have a video, but the guest to have none + ->waitFor('@session .meet-video') + ->waitFor('@session .meet-subscriber.self'); + + // Unset the option back + $owner->click('@menu button.link-options') + ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { + $browser->assertVisible('#room-nomedia input[type=checkbox]:checked') + // Test enabling the option + ->click('#room-nomedia input') + ->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.") + ->click('@button-action'); + + $this->assertSame(null, $room->fresh()->getSetting('nomedia')); + }); + }); + } } diff --git a/src/tests/Browser/Meet/RoomSetupTest.php b/src/tests/Browser/Meet/RoomSetupTest.php index 950a9f9f..42346a17 100644 --- a/src/tests/Browser/Meet/RoomSetupTest.php +++ b/src/tests/Browser/Meet/RoomSetupTest.php @@ -1,569 +1,569 @@ setupTestRoom(); } public function tearDown(): void { $this->resetTestRoom(); parent::tearDown(); } /** * Test non-existing room * * @group openvidu */ public function testRoomNonExistingRoom(): void { $this->browse(function (Browser $browser) { $browser->visit(new RoomPage('unknown')) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login']); }); if ($browser->isDesktop()) { $browser->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'tos', 'login']); }); } else { $browser->assertMissing('#footer-menu .navbar-nav'); } // FIXME: Maybe it would be better to just display the usual 404 Not Found error page? $browser->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->assertVisible('@setup-form') ->assertSeeIn('@setup-status-message', "The room does not exist.") ->assertButtonDisabled('@setup-button'); }); } /** * Test the room setup page * * @group openvidu */ public function testRoomSetup(): void { $this->browse(function (Browser $browser) { $browser->visit(new RoomPage('john')) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login']); }); if ($browser->isDesktop()) { $browser->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'tos', 'login']); }); } else { $browser->assertMissing('#footer-menu .navbar-nav'); } // Note: I've found out that if I have another Chrome instance running // that uses media, here the media devices will not be available // TODO: Test enabling/disabling cam/mic in the setup widget $browser->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->assertVisible('@setup-form') ->assertSeeIn('@setup-title', 'Set up your session') ->assertVisible('@setup-video') ->assertVisible('@setup-form .input-group:nth-child(1) svg') ->assertAttribute('@setup-form .input-group:nth-child(1) .input-group-text', 'title', 'Microphone') ->assertVisible('@setup-mic-select') ->assertVisible('@setup-form .input-group:nth-child(2) svg') ->assertAttribute('@setup-form .input-group:nth-child(2) .input-group-text', 'title', 'Camera') ->assertVisible('@setup-cam-select') ->assertVisible('@setup-form .input-group:nth-child(3) svg') ->assertAttribute('@setup-form .input-group:nth-child(3) .input-group-text', 'title', 'Nickname') ->assertValue('@setup-nickname-input', '') ->assertAttribute('@setup-nickname-input', 'placeholder', 'Your name') ->assertMissing('@setup-password-input') ->assertSeeIn( '@setup-status-message', "The room is closed. Please, wait for the owner to start the session." ) ->assertSeeIn('@setup-button', "I'm the owner"); }); } /** * Test two users in a room (joining/leaving and some basic functionality) * * @group openvidu * @depends testRoomSetup */ public function testTwoUsersInARoom(): void { $this->browse(function (Browser $browser, Browser $guest) { // In one browser window act as a guest $guest->visit(new RoomPage('john')) ->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertSeeIn( '@setup-status-message', "The room is closed. Please, wait for the owner to start the session." ) ->assertSeeIn('@setup-button', "I'm the owner"); // In another window join the room as the owner (authenticate) $browser->on(new RoomPage('john')) ->assertSeeIn('@setup-button', "I'm the owner") ->clickWhenEnabled('@setup-button') ->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@setup-form') ->assertVisible('@login-form') ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->assertMissing('@login-form') ->waitUntilMissing('@setup-status-message.loading') ->waitFor('@setup-status-message') ->assertSeeIn('@setup-status-message', "The room is closed. It will be open for others after you join.") ->assertSeeIn('@setup-button', "JOIN") ->type('@setup-nickname-input', 'john') // Join the room (click the button twice, to make sure it does not // produce redundant participants/subscribers in the room) ->clickWhenEnabled('@setup-button') ->pause(10) ->click('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('div.meet-video.self', function (Browser $browser) { $browser->waitFor('video') ->assertSeeIn('.meet-nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'dashboard', 'logout']); }); if ($browser->isDesktop()) { $browser->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'tos', 'dashboard', 'logout']); }); } // After the owner "opened the room" guest should be able to join $guest->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') //->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('div.meet-video.self', function (Browser $browser) { $browser->waitFor('video') ->assertVisible('.meet-nickname') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertVisible('.status .status-audio') ->assertMissing('.status .status-video'); }) ->whenAvailable('div.meet-video:not(.self)', function (Browser $browser) { $browser->waitFor('video') ->assertSeeIn('.meet-nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'signup', 'login']); }); if ($guest->isDesktop()) { $guest->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'tos', 'signup', 'login']); }); } // Check guest's elements in the owner's window $browser ->whenAvailable('div.meet-video:not(.self)', function (Browser $browser) { $browser->waitFor('video') ->assertVisible('.meet-nickname') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertMissing('.controls button.link-setup') ->assertVisible('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2); // Test leaving the room // Guest is leaving $guest->click('@menu button.link-logout') ->waitForLocation('/login'); // Expect the participant removed from other users windows $browser->waitUntilMissing('@session div.meet-video:not(.self)'); // Join the room as guest again $guest->visit(new RoomPage('john')) ->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-status-message') ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') //->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // Leave the room as the room owner // TODO: Test leaving the room by closing the browser window, // it should not destroy the session $browser->click('@menu button.link-logout') ->waitForLocation('/dashboard'); // Expect other participants be informed about the end of the session $guest->with(new Dialog('#leave-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Room closed') ->assertSeeIn('@body', "The session has been closed by the room owner.") ->assertMissing('@button-cancel') ->assertSeeIn('@button-action', 'Close') ->click('@button-action'); }) ->assertMissing('#leave-dialog') ->waitForLocation('/login'); }); } /** * Test two subscribers-only users in a room * * @group openvidu * @depends testTwoUsersInARoom */ public function testSubscribers(): void { $this->browse(function (Browser $browser, Browser $guest) { // Join the room as the owner $browser->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->waitFor('@setup-status-message') ->type('@setup-nickname-input', 'john') ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('@subscribers .meet-subscriber.self', function (Browser $browser) { $browser->assertSeeIn('.meet-nickname', 'john'); }) ->assertElementsCount('@session div.meet-video', 0) ->assertElementsCount('@session video', 0) ->assertElementsCount('@session .meet-subscriber', 1) ->assertToolbar([ 'audio' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'video' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'hand' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'fullscreen' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, - 'security' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, + 'options' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'logout' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, ]); // After the owner "opened the room" guest should be able to join // In one browser window act as a guest $guest->visit(new RoomPage('john')) ->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('@subscribers .meet-subscriber.self', function (Browser $browser) { $browser->assertVisible('.meet-nickname'); }) ->whenAvailable('@subscribers .meet-subscriber:not(.self)', function (Browser $browser) { $browser->assertSeeIn('.meet-nickname', 'john'); }) ->assertElementsCount('@session div.meet-video', 0) ->assertElementsCount('@session video', 0) ->assertElementsCount('@session div.meet-subscriber', 2) ->assertToolbar([ 'audio' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'video' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'hand' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'fullscreen' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'logout' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, ]); // Check guest's elements in the owner's window $browser ->whenAvailable('@subscribers .meet-subscriber:not(.self)', function (Browser $browser) { $browser->assertVisible('.meet-nickname'); }) ->assertElementsCount('@session div.meet-video', 0) ->assertElementsCount('@session video', 0) ->assertElementsCount('@session .meet-subscriber', 2); // Test leaving the room // Guest is leaving $guest->click('@menu button.link-logout') ->waitForLocation('/login'); // Expect the participant removed from other users windows $browser->waitUntilMissing('@session .meet-subscriber:not(.self)'); }); } /** * Test demoting publisher to a subscriber * * @group openvidu * @depends testSubscribers */ public function testDemoteToSubscriber(): void { $this->browse(function (Browser $browser, Browser $guest1, Browser $guest2) { // Join the room as the owner $browser->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->waitFor('@setup-status-message') ->type('@setup-nickname-input', 'john') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->waitFor('@session video'); // In one browser window act as a guest $guest1->visit(new RoomPage('john')) ->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->waitFor('div.meet-video.self') ->waitFor('div.meet-video:not(.self)') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session div.meet-subscriber', 0) // assert there's no moderator-related features for this guess available ->click('@session .meet-video.self .meet-nickname') ->whenAvailable('@session .meet-video.self .dropdown-menu', function (Browser $browser) { $browser->assertMissing('.permissions'); }) ->click('@session .meet-video:not(.self) .meet-nickname') ->pause(50) ->assertMissing('.dropdown-menu'); // Demote the guest to a subscriber $browser ->waitFor('div.meet-video.self') ->waitFor('div.meet-video:not(.self)') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session .meet-subscriber', 0) ->click('@session .meet-video:not(.self) .meet-nickname') ->whenAvailable('@session .meet-video:not(.self) .dropdown-menu', function (Browser $browser) { $browser->assertSeeIn('.action-role-publisher', 'Audio & Video publishing') ->click('.action-role-publisher') ->waitUntilMissing('.dropdown-menu'); }) ->waitUntilMissing('@session .meet-video:not(.self)') ->waitFor('@session div.meet-subscriber') ->assertElementsCount('@session div.meet-video', 1) ->assertElementsCount('@session video', 1) ->assertElementsCount('@session div.meet-subscriber', 1); $guest1 ->waitUntilMissing('@session .meet-video.self') ->waitFor('@session div.meet-subscriber') ->assertElementsCount('@session div.meet-video', 1) ->assertElementsCount('@session video', 1) ->assertElementsCount('@session div.meet-subscriber', 1); // Join as another user to make sure the role change is propagated to new connections $guest2->visit(new RoomPage('john')) ->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->waitFor('div.meet-subscriber:not(.self)') ->assertElementsCount('@session div.meet-video', 1) ->assertElementsCount('@session video', 1) ->assertElementsCount('@session div.meet-subscriber', 2) ->click('@toolbar .link-logout'); // Promote the guest back to a publisher $browser ->click('@session .meet-subscriber .meet-nickname') ->whenAvailable('@session .meet-subscriber .dropdown-menu', function (Browser $browser) { $browser->assertSeeIn('.action-role-publisher', 'Audio & Video publishing') ->assertNotChecked('.action-role-publisher input') ->click('.action-role-publisher') ->waitUntilMissing('.dropdown-menu'); }) ->waitFor('@session .meet-video:not(.self) video') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session div.meet-subscriber', 0); $guest1 ->with(new Dialog('#media-setup-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Media setup') ->click('@button-action'); }) ->waitFor('@session .meet-video.self') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session div.meet-subscriber', 0); // Demote the owner to a subscriber $browser ->click('@session .meet-video.self .meet-nickname') ->whenAvailable('@session .meet-video.self .dropdown-menu', function (Browser $browser) { $browser->assertSeeIn('.action-role-publisher', 'Audio & Video publishing') ->assertChecked('.action-role-publisher input') ->click('.action-role-publisher') ->waitUntilMissing('.dropdown-menu'); }) ->waitUntilMissing('@session .meet-video.self') ->waitFor('@session div.meet-subscriber.self') ->assertElementsCount('@session div.meet-video', 1) ->assertElementsCount('@session video', 1) ->assertElementsCount('@session div.meet-subscriber', 1); // Promote the owner to a publisher $browser ->click('@session .meet-subscriber.self .meet-nickname') ->whenAvailable('@session .meet-subscriber.self .dropdown-menu', function (Browser $browser) { $browser->assertSeeIn('.action-role-publisher', 'Audio & Video publishing') ->assertNotChecked('.action-role-publisher input') ->click('.action-role-publisher') ->waitUntilMissing('.dropdown-menu'); }) ->waitUntilMissing('@session .meet-subscriber.self') ->with(new Dialog('#media-setup-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Media setup') ->click('@button-action'); }) ->waitFor('@session div.meet-video.self') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session div.meet-subscriber', 0); }); } /** * Test the media setup dialog * * @group openvidu * @depends testDemoteToSubscriber */ public function testMediaSetupDialog(): void { $this->browse(function (Browser $browser, $guest) { // Join the room as the owner $browser->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->waitFor('@setup-status-message') ->type('@setup-nickname-input', 'john') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form'); // In one browser window act as a guest $guest->visit(new RoomPage('john')) ->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form'); $browser->waitFor('@session video') ->click('.controls button.link-setup') ->with(new Dialog('#media-setup-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Media setup') ->assertVisible('form video') ->assertVisible('form > div:nth-child(1) video') ->assertVisible('form > div:nth-child(1) .volume') ->assertVisible('form > div:nth-child(2) svg') ->assertAttribute('form > div:nth-child(2) .input-group-text', 'title', 'Microphone') ->assertVisible('form > div:nth-child(2) select') ->assertVisible('form > div:nth-child(3) svg') ->assertAttribute('form > div:nth-child(3) .input-group-text', 'title', 'Camera') ->assertVisible('form > div:nth-child(3) select') ->assertMissing('@button-cancel') ->assertSeeIn('@button-action', 'Close') ->click('@button-action'); }) ->assertMissing('#media-setup-dialog') // Test mute audio and video ->click('.controls button.link-setup') ->with(new Dialog('#media-setup-dialog'), function (Browser $browser) { $browser->select('form > div:nth-child(2) select', '') ->select('form > div:nth-child(3) select', '') ->click('@button-action'); }) ->assertMissing('#media-setup-dialog') ->assertVisible('@session .meet-video .status .status-audio') ->assertVisible('@session .meet-video .status .status-video'); $guest->waitFor('@session video') ->assertVisible('@session .meet-video .status .status-audio') ->assertVisible('@session .meet-video .status .status-video'); }); } } diff --git a/src/tests/TestCaseMeetTrait.php b/src/tests/TestCaseMeetTrait.php index 150883a5..862ec5d5 100644 --- a/src/tests/TestCaseMeetTrait.php +++ b/src/tests/TestCaseMeetTrait.php @@ -1,56 +1,56 @@ getTestUser($user); } $user->assignSku(\App\Sku::where('title', 'meet')->first()); } /** * Removes all 'meet' entitlements from the database */ protected function clearMeetEntitlements(): void { $meet_sku = \App\Sku::where('title', 'meet')->first(); \App\Entitlement::where('sku_id', $meet_sku->id)->delete(); } /** * Reset a room after tests */ public function resetTestRoom($room_name = 'john'): void { $this->clearMeetEntitlements(); $room = Room::where('name', $room_name)->first(); - $room->setSettings(['password' => null, 'locked' => null]); + $room->setSettings(['password' => null, 'locked' => null, 'nomedia' => null]); if ($room->session_id) { $room->session_id = null; $room->save(); } } /** * Prepare a room for testing */ public function setupTestRoom($room_name = 'john'): void { $this->resetTestRoom($room_name); $this->assignMeetEntitlement('john@kolab.org'); } }
The session has been closed by the room owner.
') ->clickWhenEnabled('@setup-button') ->assertMissing('@setup-nickname-input.is-invalid') ->waitForText("Waiting for permission to join the room.") ->assertButtonDisabled('@setup-button'); // Test denying the request (this will also test custom toasts) $owner ->whenAvailable(new Toast(Toast::TYPE_CUSTOM), function ($browser) { $browser->assertToastTitle('Join request') ->assertVisible('.toast-header svg.fa-user') ->assertSeeIn('@message', 'Guest
requested to join.') ->assertAttributeRegExp('@message img', 'src', '|^data:image|') ->assertSeeIn('@message button.accept.btn-success', 'Accept') ->assertSeeIn('@message button.deny.btn-danger', 'Deny') ->click('@message button.deny'); }) ->waitUntilMissing('.toast') // wait 10 seconds to make sure the request message does not show up again ->pause(10 * 1000) ->assertMissing('.toast'); }); } /** * Test locked room (accepting the join request, and dismissing a user) * * @group openvidu */ public function testLockedRoomAcceptAndDismiss(): void { $this->browse(function (Browser $owner, Browser $guest) { - // Make sure there's no session yet $room = Room::where('name', 'john')->first(); // Join the room as an owner (authenticate) $owner->visit(new RoomPage('john')) // ->click('@setup-button') // ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'John') ->clickWhenEnabled('@setup-button') ->waitFor('@session') - // Enter Security option dialog - ->click('@menu button.link-security') - ->with(new Dialog('#security-options-dialog'), function (Browser $browser) use ($room) { - $browser->assertSeeIn('@title', 'Security options') + // Enter room option dialog + ->click('@menu button.link-options') + ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { + $browser->assertSeeIn('@title', 'Room options') ->assertSeeIn('#room-lock label', 'Locked room:') ->assertVisible('#room-lock input[type=checkbox]:not(:checked)') ->assertVisible('#room-lock + small') // Test setting the lock ->click('#room-lock input') ->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.") ->click('@button-action'); $this->assertSame('true', $room->fresh()->getSetting('locked')); }); // In another browser act as a guest $guest->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->type('@setup-nickname-input', 'guest') ->clickWhenEnabled('@setup-button') ->waitForText("Waiting for permission to join the room.") ->assertButtonDisabled('@setup-button'); $owner ->whenAvailable(new Toast(Toast::TYPE_CUSTOM), function ($browser) { $browser->assertToastTitle('Join request') ->assertSeeIn('@message', 'guest requested to join.') ->click('@message button.accept'); }); // Guest automatically anters the room $guest->waitFor('@session', 12) // make sure he has no access to the Options menu ->waitFor('@session .meet-video:not(.self)') ->assertSeeIn('@session .meet-video:not(.self) .meet-nickname', 'John') // TODO: Assert title and icon ->click('@session .meet-video:not(.self) .meet-nickname') ->pause(100) ->assertMissing('.dropdown-menu'); // Test dismissing the participant $owner->click('@session .meet-video:not(.self) .meet-nickname') ->waitFor('@session .meet-video:not(.self) .dropdown-menu') ->assertSeeIn('@session .meet-video:not(.self) .dropdown-menu > .action-dismiss', 'Dismiss') ->click('@session .meet-video:not(.self) .dropdown-menu > .action-dismiss') ->waitUntilMissing('.dropdown-menu') ->waitUntilMissing('@session .meet-video:not(.self)'); // Expect a "end of session" dialog on the participant side $guest->with(new Dialog('#leave-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Room closed') ->assertSeeIn('@body', "The session has been closed by the room owner.") ->assertMissing('@button-cancel') ->assertSeeIn('@button-action', 'Close'); }); }); } + + /** + * Test nomedia (subscribers only) feature + * + * @group openvidu + */ + public function testSubscribersOnly(): void + { + $this->browse(function (Browser $owner, Browser $guest) { + $room = Room::where('name', 'john')->first(); + + // Join the room as an owner (authenticate) + $owner->visit(new RoomPage('john')) + // ->click('@setup-button') + // ->submitLogon('john@kolab.org', 'simple123') + ->waitFor('@setup-form') + ->waitUntilMissing('@setup-status-message.loading') + ->type('@setup-nickname-input', 'John') + ->clickWhenEnabled('@setup-button') + ->waitFor('@session') + // Enter room option dialog + ->click('@menu button.link-options') + ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { + $browser->assertSeeIn('@title', 'Room options') + ->assertSeeIn('#room-nomedia label', 'Subscribers only:') + ->assertVisible('#room-nomedia input[type=checkbox]:not(:checked)') + ->assertVisible('#room-nomedia + small') + // Test enabling the option + ->click('#room-nomedia input') + ->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.") + ->click('@button-action'); + + $this->assertSame('true', $room->fresh()->getSetting('nomedia')); + }); + + // In another browser act as a guest + $guest->visit(new RoomPage('john')) + ->waitFor('@setup-form') + ->waitUntilMissing('@setup-status-message.loading') + ->type('@setup-nickname-input', 'John') + ->clickWhenEnabled('@setup-button') + // expect the owner to have a video, but the guest to have none + ->waitFor('@session .meet-video') + ->waitFor('@session .meet-subscriber.self'); + + // Unset the option back + $owner->click('@menu button.link-options') + ->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) { + $browser->assertVisible('#room-nomedia input[type=checkbox]:checked') + // Test enabling the option + ->click('#room-nomedia input') + ->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.") + ->click('@button-action'); + + $this->assertSame(null, $room->fresh()->getSetting('nomedia')); + }); + }); + } } diff --git a/src/tests/Browser/Meet/RoomSetupTest.php b/src/tests/Browser/Meet/RoomSetupTest.php index 950a9f9f..42346a17 100644 --- a/src/tests/Browser/Meet/RoomSetupTest.php +++ b/src/tests/Browser/Meet/RoomSetupTest.php @@ -1,569 +1,569 @@ setupTestRoom(); } public function tearDown(): void { $this->resetTestRoom(); parent::tearDown(); } /** * Test non-existing room * * @group openvidu */ public function testRoomNonExistingRoom(): void { $this->browse(function (Browser $browser) { $browser->visit(new RoomPage('unknown')) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login']); }); if ($browser->isDesktop()) { $browser->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'tos', 'login']); }); } else { $browser->assertMissing('#footer-menu .navbar-nav'); } // FIXME: Maybe it would be better to just display the usual 404 Not Found error page? $browser->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->assertVisible('@setup-form') ->assertSeeIn('@setup-status-message', "The room does not exist.") ->assertButtonDisabled('@setup-button'); }); } /** * Test the room setup page * * @group openvidu */ public function testRoomSetup(): void { $this->browse(function (Browser $browser) { $browser->visit(new RoomPage('john')) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login']); }); if ($browser->isDesktop()) { $browser->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'tos', 'login']); }); } else { $browser->assertMissing('#footer-menu .navbar-nav'); } // Note: I've found out that if I have another Chrome instance running // that uses media, here the media devices will not be available // TODO: Test enabling/disabling cam/mic in the setup widget $browser->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->assertVisible('@setup-form') ->assertSeeIn('@setup-title', 'Set up your session') ->assertVisible('@setup-video') ->assertVisible('@setup-form .input-group:nth-child(1) svg') ->assertAttribute('@setup-form .input-group:nth-child(1) .input-group-text', 'title', 'Microphone') ->assertVisible('@setup-mic-select') ->assertVisible('@setup-form .input-group:nth-child(2) svg') ->assertAttribute('@setup-form .input-group:nth-child(2) .input-group-text', 'title', 'Camera') ->assertVisible('@setup-cam-select') ->assertVisible('@setup-form .input-group:nth-child(3) svg') ->assertAttribute('@setup-form .input-group:nth-child(3) .input-group-text', 'title', 'Nickname') ->assertValue('@setup-nickname-input', '') ->assertAttribute('@setup-nickname-input', 'placeholder', 'Your name') ->assertMissing('@setup-password-input') ->assertSeeIn( '@setup-status-message', "The room is closed. Please, wait for the owner to start the session." ) ->assertSeeIn('@setup-button', "I'm the owner"); }); } /** * Test two users in a room (joining/leaving and some basic functionality) * * @group openvidu * @depends testRoomSetup */ public function testTwoUsersInARoom(): void { $this->browse(function (Browser $browser, Browser $guest) { // In one browser window act as a guest $guest->visit(new RoomPage('john')) ->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertSeeIn( '@setup-status-message', "The room is closed. Please, wait for the owner to start the session." ) ->assertSeeIn('@setup-button', "I'm the owner"); // In another window join the room as the owner (authenticate) $browser->on(new RoomPage('john')) ->assertSeeIn('@setup-button', "I'm the owner") ->clickWhenEnabled('@setup-button') ->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@setup-form') ->assertVisible('@login-form') ->submitLogon('john@kolab.org', 'simple123') ->waitFor('@setup-form') ->assertMissing('@login-form') ->waitUntilMissing('@setup-status-message.loading') ->waitFor('@setup-status-message') ->assertSeeIn('@setup-status-message', "The room is closed. It will be open for others after you join.") ->assertSeeIn('@setup-button', "JOIN") ->type('@setup-nickname-input', 'john') // Join the room (click the button twice, to make sure it does not // produce redundant participants/subscribers in the room) ->clickWhenEnabled('@setup-button') ->pause(10) ->click('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('div.meet-video.self', function (Browser $browser) { $browser->waitFor('video') ->assertSeeIn('.meet-nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'dashboard', 'logout']); }); if ($browser->isDesktop()) { $browser->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'tos', 'dashboard', 'logout']); }); } // After the owner "opened the room" guest should be able to join $guest->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') //->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('div.meet-video.self', function (Browser $browser) { $browser->waitFor('video') ->assertVisible('.meet-nickname') ->assertVisible('.controls button.link-fullscreen') ->assertMissing('.controls button.link-audio') ->assertVisible('.status .status-audio') ->assertMissing('.status .status-video'); }) ->whenAvailable('div.meet-video:not(.self)', function (Browser $browser) { $browser->waitFor('video') ->assertSeeIn('.meet-nickname', 'john') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertMissing('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2) ->within(new Menu(), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'signup', 'login']); }); if ($guest->isDesktop()) { $guest->within(new Menu('footer'), function ($browser) { $browser->assertMenuItems(['explore', 'blog', 'support', 'tos', 'signup', 'login']); }); } // Check guest's elements in the owner's window $browser ->whenAvailable('div.meet-video:not(.self)', function (Browser $browser) { $browser->waitFor('video') ->assertVisible('.meet-nickname') ->assertVisible('.controls button.link-fullscreen') ->assertVisible('.controls button.link-audio') ->assertMissing('.controls button.link-setup') ->assertVisible('.status .status-audio') ->assertMissing('.status .status-video'); }) ->assertElementsCount('@session div.meet-video', 2); // Test leaving the room // Guest is leaving $guest->click('@menu button.link-logout') ->waitForLocation('/login'); // Expect the participant removed from other users windows $browser->waitUntilMissing('@session div.meet-video:not(.self)'); // Join the room as guest again $guest->visit(new RoomPage('john')) ->assertMissing('@toolbar') ->assertMissing('@menu') ->assertMissing('@session') ->assertMissing('@chat') ->assertMissing('@login-form') ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->assertMissing('@setup-status-message') ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') //->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session'); // Leave the room as the room owner // TODO: Test leaving the room by closing the browser window, // it should not destroy the session $browser->click('@menu button.link-logout') ->waitForLocation('/dashboard'); // Expect other participants be informed about the end of the session $guest->with(new Dialog('#leave-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Room closed') ->assertSeeIn('@body', "The session has been closed by the room owner.") ->assertMissing('@button-cancel') ->assertSeeIn('@button-action', 'Close') ->click('@button-action'); }) ->assertMissing('#leave-dialog') ->waitForLocation('/login'); }); } /** * Test two subscribers-only users in a room * * @group openvidu * @depends testTwoUsersInARoom */ public function testSubscribers(): void { $this->browse(function (Browser $browser, Browser $guest) { // Join the room as the owner $browser->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->waitFor('@setup-status-message') ->type('@setup-nickname-input', 'john') ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('@subscribers .meet-subscriber.self', function (Browser $browser) { $browser->assertSeeIn('.meet-nickname', 'john'); }) ->assertElementsCount('@session div.meet-video', 0) ->assertElementsCount('@session video', 0) ->assertElementsCount('@session .meet-subscriber', 1) ->assertToolbar([ 'audio' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'video' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'hand' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'fullscreen' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, - 'security' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, + 'options' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'logout' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, ]); // After the owner "opened the room" guest should be able to join // In one browser window act as a guest $guest->visit(new RoomPage('john')) ->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") // Join the room, disable cam/mic ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->whenAvailable('@subscribers .meet-subscriber.self', function (Browser $browser) { $browser->assertVisible('.meet-nickname'); }) ->whenAvailable('@subscribers .meet-subscriber:not(.self)', function (Browser $browser) { $browser->assertSeeIn('.meet-nickname', 'john'); }) ->assertElementsCount('@session div.meet-video', 0) ->assertElementsCount('@session video', 0) ->assertElementsCount('@session div.meet-subscriber', 2) ->assertToolbar([ 'audio' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'video' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'screen' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_DISABLED, 'hand' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'chat' => RoomPage::BUTTON_INACTIVE | RoomPage::BUTTON_ENABLED, 'fullscreen' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, 'logout' => RoomPage::BUTTON_ACTIVE | RoomPage::BUTTON_ENABLED, ]); // Check guest's elements in the owner's window $browser ->whenAvailable('@subscribers .meet-subscriber:not(.self)', function (Browser $browser) { $browser->assertVisible('.meet-nickname'); }) ->assertElementsCount('@session div.meet-video', 0) ->assertElementsCount('@session video', 0) ->assertElementsCount('@session .meet-subscriber', 2); // Test leaving the room // Guest is leaving $guest->click('@menu button.link-logout') ->waitForLocation('/login'); // Expect the participant removed from other users windows $browser->waitUntilMissing('@session .meet-subscriber:not(.self)'); }); } /** * Test demoting publisher to a subscriber * * @group openvidu * @depends testSubscribers */ public function testDemoteToSubscriber(): void { $this->browse(function (Browser $browser, Browser $guest1, Browser $guest2) { // Join the room as the owner $browser->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->waitFor('@setup-status-message') ->type('@setup-nickname-input', 'john') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->waitFor('@session video'); // In one browser window act as a guest $guest1->visit(new RoomPage('john')) ->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->waitFor('div.meet-video.self') ->waitFor('div.meet-video:not(.self)') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session div.meet-subscriber', 0) // assert there's no moderator-related features for this guess available ->click('@session .meet-video.self .meet-nickname') ->whenAvailable('@session .meet-video.self .dropdown-menu', function (Browser $browser) { $browser->assertMissing('.permissions'); }) ->click('@session .meet-video:not(.self) .meet-nickname') ->pause(50) ->assertMissing('.dropdown-menu'); // Demote the guest to a subscriber $browser ->waitFor('div.meet-video.self') ->waitFor('div.meet-video:not(.self)') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session .meet-subscriber', 0) ->click('@session .meet-video:not(.self) .meet-nickname') ->whenAvailable('@session .meet-video:not(.self) .dropdown-menu', function (Browser $browser) { $browser->assertSeeIn('.action-role-publisher', 'Audio & Video publishing') ->click('.action-role-publisher') ->waitUntilMissing('.dropdown-menu'); }) ->waitUntilMissing('@session .meet-video:not(.self)') ->waitFor('@session div.meet-subscriber') ->assertElementsCount('@session div.meet-video', 1) ->assertElementsCount('@session video', 1) ->assertElementsCount('@session div.meet-subscriber', 1); $guest1 ->waitUntilMissing('@session .meet-video.self') ->waitFor('@session div.meet-subscriber') ->assertElementsCount('@session div.meet-video', 1) ->assertElementsCount('@session video', 1) ->assertElementsCount('@session div.meet-subscriber', 1); // Join as another user to make sure the role change is propagated to new connections $guest2->visit(new RoomPage('john')) ->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form') ->waitFor('div.meet-subscriber:not(.self)') ->assertElementsCount('@session div.meet-video', 1) ->assertElementsCount('@session video', 1) ->assertElementsCount('@session div.meet-subscriber', 2) ->click('@toolbar .link-logout'); // Promote the guest back to a publisher $browser ->click('@session .meet-subscriber .meet-nickname') ->whenAvailable('@session .meet-subscriber .dropdown-menu', function (Browser $browser) { $browser->assertSeeIn('.action-role-publisher', 'Audio & Video publishing') ->assertNotChecked('.action-role-publisher input') ->click('.action-role-publisher') ->waitUntilMissing('.dropdown-menu'); }) ->waitFor('@session .meet-video:not(.self) video') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session div.meet-subscriber', 0); $guest1 ->with(new Dialog('#media-setup-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Media setup') ->click('@button-action'); }) ->waitFor('@session .meet-video.self') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session div.meet-subscriber', 0); // Demote the owner to a subscriber $browser ->click('@session .meet-video.self .meet-nickname') ->whenAvailable('@session .meet-video.self .dropdown-menu', function (Browser $browser) { $browser->assertSeeIn('.action-role-publisher', 'Audio & Video publishing') ->assertChecked('.action-role-publisher input') ->click('.action-role-publisher') ->waitUntilMissing('.dropdown-menu'); }) ->waitUntilMissing('@session .meet-video.self') ->waitFor('@session div.meet-subscriber.self') ->assertElementsCount('@session div.meet-video', 1) ->assertElementsCount('@session video', 1) ->assertElementsCount('@session div.meet-subscriber', 1); // Promote the owner to a publisher $browser ->click('@session .meet-subscriber.self .meet-nickname') ->whenAvailable('@session .meet-subscriber.self .dropdown-menu', function (Browser $browser) { $browser->assertSeeIn('.action-role-publisher', 'Audio & Video publishing') ->assertNotChecked('.action-role-publisher input') ->click('.action-role-publisher') ->waitUntilMissing('.dropdown-menu'); }) ->waitUntilMissing('@session .meet-subscriber.self') ->with(new Dialog('#media-setup-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Media setup') ->click('@button-action'); }) ->waitFor('@session div.meet-video.self') ->assertElementsCount('@session div.meet-video', 2) ->assertElementsCount('@session video', 2) ->assertElementsCount('@session div.meet-subscriber', 0); }); } /** * Test the media setup dialog * * @group openvidu * @depends testDemoteToSubscriber */ public function testMediaSetupDialog(): void { $this->browse(function (Browser $browser, $guest) { // Join the room as the owner $browser->visit(new RoomPage('john')) ->waitFor('@setup-form') ->waitUntilMissing('@setup-status-message.loading') ->waitFor('@setup-status-message') ->type('@setup-nickname-input', 'john') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form'); // In one browser window act as a guest $guest->visit(new RoomPage('john')) ->waitUntilMissing('@setup-status-message', 10) ->assertSeeIn('@setup-button', "JOIN") ->select('@setup-mic-select', '') ->select('@setup-cam-select', '') ->clickWhenEnabled('@setup-button') ->waitFor('@session') ->assertMissing('@setup-form'); $browser->waitFor('@session video') ->click('.controls button.link-setup') ->with(new Dialog('#media-setup-dialog'), function (Browser $browser) { $browser->assertSeeIn('@title', 'Media setup') ->assertVisible('form video') ->assertVisible('form > div:nth-child(1) video') ->assertVisible('form > div:nth-child(1) .volume') ->assertVisible('form > div:nth-child(2) svg') ->assertAttribute('form > div:nth-child(2) .input-group-text', 'title', 'Microphone') ->assertVisible('form > div:nth-child(2) select') ->assertVisible('form > div:nth-child(3) svg') ->assertAttribute('form > div:nth-child(3) .input-group-text', 'title', 'Camera') ->assertVisible('form > div:nth-child(3) select') ->assertMissing('@button-cancel') ->assertSeeIn('@button-action', 'Close') ->click('@button-action'); }) ->assertMissing('#media-setup-dialog') // Test mute audio and video ->click('.controls button.link-setup') ->with(new Dialog('#media-setup-dialog'), function (Browser $browser) { $browser->select('form > div:nth-child(2) select', '') ->select('form > div:nth-child(3) select', '') ->click('@button-action'); }) ->assertMissing('#media-setup-dialog') ->assertVisible('@session .meet-video .status .status-audio') ->assertVisible('@session .meet-video .status .status-video'); $guest->waitFor('@session video') ->assertVisible('@session .meet-video .status .status-audio') ->assertVisible('@session .meet-video .status .status-video'); }); } } diff --git a/src/tests/TestCaseMeetTrait.php b/src/tests/TestCaseMeetTrait.php index 150883a5..862ec5d5 100644 --- a/src/tests/TestCaseMeetTrait.php +++ b/src/tests/TestCaseMeetTrait.php @@ -1,56 +1,56 @@ getTestUser($user); } $user->assignSku(\App\Sku::where('title', 'meet')->first()); } /** * Removes all 'meet' entitlements from the database */ protected function clearMeetEntitlements(): void { $meet_sku = \App\Sku::where('title', 'meet')->first(); \App\Entitlement::where('sku_id', $meet_sku->id)->delete(); } /** * Reset a room after tests */ public function resetTestRoom($room_name = 'john'): void { $this->clearMeetEntitlements(); $room = Room::where('name', $room_name)->first(); - $room->setSettings(['password' => null, 'locked' => null]); + $room->setSettings(['password' => null, 'locked' => null, 'nomedia' => null]); if ($room->session_id) { $room->session_id = null; $room->save(); } } /** * Prepare a room for testing */ public function setupTestRoom($room_name = 'john'): void { $this->resetTestRoom($room_name); $this->assignMeetEntitlement('john@kolab.org'); } }