From 38732c4a3e0c47e0dfb23e7b7622375e67082da0 Mon Sep 17 00:00:00 2001 From: Noah Duncan Date: Thu, 8 Oct 2026 14:09:01 -0500 Subject: [PATCH 1/7] also include skype for jsp --- bots/teams_bot_adapter/teams_chromedriver_payload.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/teams_bot_adapter/teams_chromedriver_payload.js b/bots/teams_bot_adapter/teams_chromedriver_payload.js index a4bb0cb3a..dd230d203 100644 --- a/bots/teams_bot_adapter/teams_chromedriver_payload.js +++ b/bots/teams_bot_adapter/teams_chromedriver_payload.js @@ -1,5 +1,5 @@ (function () { - const CONV_HOST_REGEX = /(^|\.)teams\.microsoft\.(com|us)$/; + const CONV_HOST_REGEX = /(^|\.)(teams\.microsoft\.(com|us)|skype\.com)$/; const CONV_PATH_REGEX = /\/conv\/[^/]+\/?$/; let installed = false; From dec0d1d49c47083d0b9bf88363891d1e6a0bacb3 Mon Sep 17 00:00:00 2001 From: Noah Duncan Date: Thu, 8 Oct 2026 14:15:12 -0500 Subject: [PATCH 2/7] only throw for domain allow list violation if we have not joined yet --- bots/web_bot_adapter/web_bot_adapter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bots/web_bot_adapter/web_bot_adapter.py b/bots/web_bot_adapter/web_bot_adapter.py index 16ba2c0b0..b02bc8de6 100644 --- a/bots/web_bot_adapter/web_bot_adapter.py +++ b/bots/web_bot_adapter/web_bot_adapter.py @@ -1288,6 +1288,8 @@ def check_domain_allow_list_violation(self): return if not self.driver: return + if self.joined_at: + return if time.time() - self.last_domain_allow_list_violation_check_time < 30: return From 97bf5e654ce2ab7a87d48cf7f60da14d756805ef Mon Sep 17 00:00:00 2001 From: Noah Duncan Date: Thu, 8 Oct 2026 14:52:57 -0500 Subject: [PATCH 3/7] look for incoming video is disabled element earlier --- .../google_meet_ui_methods.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/bots/google_meet_bot_adapter/google_meet_ui_methods.py b/bots/google_meet_bot_adapter/google_meet_ui_methods.py index 0452f8093..537b057b1 100644 --- a/bots/google_meet_bot_adapter/google_meet_ui_methods.py +++ b/bots/google_meet_bot_adapter/google_meet_ui_methods.py @@ -541,6 +541,7 @@ def click_captions_button(self): self.look_for_denied_your_request_element("click_captions_button") self.click_this_meeting_is_being_recorded_join_now_button("click_captions_button") self.click_others_may_see_your_meeting_differently_button("click_captions_button") + self.look_for_incoming_video_is_disabled_element() self.check_if_waiting_room_timeout_exceeded(waiting_room_timeout_started_at, "click_captions_button") last_check_timed_out = attempt_to_look_for_captions_button_index == num_attempts_to_look_for_captions_button - 1 @@ -644,11 +645,19 @@ def attempt_to_turn_off_reactions(self): logger.info("Clicking the close button") self.click_element(close_button, "close_button") + def look_for_incoming_video_is_disabled_element(self): + if not self.disable_incoming_video or self.confirmed_incoming_video_is_disabled: + return + incoming_video_is_set_to_audio_only_element = self.find_element_by_selector(By.XPATH, '//*[contains(text(), "Incoming video is set to audio only")]') + if incoming_video_is_set_to_audio_only_element: + logger.info("Confirmed incoming video is disabled") + self.confirmed_incoming_video_is_disabled = True + def disable_incoming_video_in_ui(self): # First check if incoming video is already disabled. This is what we expect because # we are disabling it via setting localstorage. - incoming_video_is_set_to_audio_only_element = self.find_element_by_selector(By.XPATH, '//*[contains(text(), "Incoming video is set to audio only")]') - if incoming_video_is_set_to_audio_only_element: + self.look_for_incoming_video_is_disabled_element() + if self.confirmed_incoming_video_is_disabled: logger.info("No need to disable incoming video via the UI. It is already disabled.") return @@ -1143,6 +1152,8 @@ def position_mouse_for_humanized_interaction(self): # returns nothing if succeeded, raises an exception if failed def attempt_to_join_meeting(self): + self.confirmed_incoming_video_is_disabled = False + if self.google_meet_bot_login_is_available and self.google_meet_bot_login_should_be_used: self.login_to_google_meet_account_with_retries() From c5341185e6202e5a7dfd4bdc25886f4d7cdd3dd4 Mon Sep 17 00:00:00 2001 From: Noah Duncan Date: Thu, 8 Oct 2026 14:53:09 -0500 Subject: [PATCH 4/7] vbump --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 21dff1d57..8678376ce 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "1.82.6" + "version": "1.82.7" } From 2a354b7ebad55f99e6d27307a24b6d97fe5c84ce Mon Sep 17 00:00:00 2001 From: Noah Duncan Date: Thu, 8 Oct 2026 15:43:28 -0500 Subject: [PATCH 5/7] fix spec --- bots/tests/test_teams_bot.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/bots/tests/test_teams_bot.py b/bots/tests/test_teams_bot.py index e13c3b783..0c37cdaa9 100644 --- a/bots/tests/test_teams_bot.py +++ b/bots/tests/test_teams_bot.py @@ -1814,17 +1814,19 @@ def test_domain_allow_list_violation_raises_error( # Create bot controller controller = BotController(self.bot.id) - # Mock the attempt_to_join_meeting to succeed immediately + # Mock the attempt_to_join_meeting to stay in the joining phase until allowed to finish + allow_join = threading.Event() with patch("bots.teams_bot_adapter.teams_ui_methods.TeamsUIMethods.attempt_to_join_meeting") as mock_attempt_to_join: - mock_attempt_to_join.return_value = None # Successful join + mock_attempt_to_join.side_effect = lambda *args, **kwargs: allow_join.wait(timeout=30) # Run the bot in a separate thread since it has an event loop bot_thread = threading.Thread(target=controller.run) bot_thread.daemon = True bot_thread.start() - # Wait for the bot to join and adapter to be created + # Wait for the adapter to be created and start joining time.sleep(3) + self.assertIsNone(controller.adapter.joined_at) # Verify the listener subscribed over the driver's BiDi websocket self.assertEqual(MockBiDiConnect.call_args[0][0], mock_driver.capabilities["webSocketUrl"]) @@ -1836,7 +1838,7 @@ def test_domain_allow_list_violation_raises_error( # and mock open() to capture what would be written without touching filesystem m = mock_open() with patch("bots.web_bot_adapter.web_bot_adapter.os.path.islink", return_value=True): - with patch("builtins.open", m): + with patch("bots.web_bot_adapter.web_bot_adapter.open", m, create=True): controller.adapter.write_chrome_policies_file() # Verify open was called with the correct path @@ -1867,9 +1869,6 @@ def test_domain_allow_list_violation_raises_error( # --- Now test the domain allow list violation detection --- - # Simulate the bot having joined and being in the meeting - controller.adapter.joined_at = time.time() - # Sanity check the allow list matching that backs the history and iframe checks: # allowed domains and their subdomains pass, look-alike domains do not blocked_url = "https://badmicrosoft.com/some-path" @@ -1893,7 +1892,7 @@ def execute_cdp_cmd_side_effect(cmd, params): # Reset the last check time so the check runs immediately controller.adapter.last_domain_allow_list_violation_check_time = 0 - # Verify that check_domain_allow_list_violation raises an exception + # While the bot is still joining, check_domain_allow_list_violation raises an exception with self.assertRaises(Exception) as context: controller.adapter.check_domain_allow_list_violation() @@ -1901,6 +1900,15 @@ def execute_cdp_cmd_side_effect(cmd, params): self.assertIn("Domain allow list violation detected", str(context.exception)) self.assertIn("badmicrosoft.com", str(context.exception)) + # Let the bot finish joining the meeting + allow_join.set() + time.sleep(2) + self.assertIsNotNone(controller.adapter.joined_at) + + # Once the bot has joined the meeting, the check is skipped and does not raise + controller.adapter.last_domain_allow_list_violation_check_time = 0 + controller.adapter.check_domain_allow_list_violation() + # Stop simulating the interstitial so cleanup isn't affected by it mock_driver.execute_cdp_cmd.side_effect = None mock_driver.execute_cdp_cmd.return_value = {} From c02d633a0bf78b8f1bc2d94245b3d3f975c3b49d Mon Sep 17 00:00:00 2001 From: Noah Duncan Date: Thu, 8 Oct 2026 15:46:42 -0500 Subject: [PATCH 6/7] fix spec --- bots/tests/test_teams_bot.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bots/tests/test_teams_bot.py b/bots/tests/test_teams_bot.py index 0c37cdaa9..69b1159a9 100644 --- a/bots/tests/test_teams_bot.py +++ b/bots/tests/test_teams_bot.py @@ -1902,8 +1902,14 @@ def execute_cdp_cmd_side_effect(cmd, params): # Let the bot finish joining the meeting allow_join.set() - time.sleep(2) + + # Wait for the join flow to finish, including starting the screen recorder, + # so it doesn't race with cleanup at the end of the test + deadline = time.time() + 10 + while controller.adapter.media_sending_enable_timestamp_ms is None and time.time() < deadline: + time.sleep(0.1) self.assertIsNotNone(controller.adapter.joined_at) + self.assertIsNotNone(controller.adapter.media_sending_enable_timestamp_ms) # Once the bot has joined the meeting, the check is skipped and does not raise controller.adapter.last_domain_allow_list_violation_check_time = 0 From d97a7ce8b5f816060e5d2a5504fcc2345f6f04eb Mon Sep 17 00:00:00 2001 From: Noah Duncan Date: Thu, 8 Oct 2026 16:00:17 -0500 Subject: [PATCH 7/7] fix spec --- bots/tests/test_teams_bot.py | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/bots/tests/test_teams_bot.py b/bots/tests/test_teams_bot.py index 69b1159a9..12ff3d8a8 100644 --- a/bots/tests/test_teams_bot.py +++ b/bots/tests/test_teams_bot.py @@ -1900,37 +1900,15 @@ def execute_cdp_cmd_side_effect(cmd, params): self.assertIn("Domain allow list violation detected", str(context.exception)) self.assertIn("badmicrosoft.com", str(context.exception)) - # Let the bot finish joining the meeting - allow_join.set() - - # Wait for the join flow to finish, including starting the screen recorder, - # so it doesn't race with cleanup at the end of the test - deadline = time.time() + 10 - while controller.adapter.media_sending_enable_timestamp_ms is None and time.time() < deadline: - time.sleep(0.1) - self.assertIsNotNone(controller.adapter.joined_at) - self.assertIsNotNone(controller.adapter.media_sending_enable_timestamp_ms) - - # Once the bot has joined the meeting, the check is skipped and does not raise - controller.adapter.last_domain_allow_list_violation_check_time = 0 - controller.adapter.check_domain_allow_list_violation() - # Stop simulating the interstitial so cleanup isn't affected by it mock_driver.execute_cdp_cmd.side_effect = None mock_driver.execute_cdp_cmd.return_value = {} - # Clean up: simulate meeting ending to trigger cleanup - controller.adapter.left_meeting = True - controller.adapter.send_message_callback({"message": controller.adapter.Messages.MEETING_ENDED}) - time.sleep(1) - - # Now wait for the thread to finish naturally + # Cleanup + controller.cleanup() + allow_join.set() bot_thread.join(timeout=5) - # If thread is still running after timeout, that's a problem to report - if bot_thread.is_alive(): - print("WARNING: Bot thread did not terminate properly after cleanup") - # Close the database connection since we're in a thread connection.close()