def test_warning_appears_if_orbot_is_used(self, sd_servers_v2, orbot_web_driver): # Given a user navigator = SourceAppNagivator( source_app_base_url=sd_servers_v2.source_app_base_url, # Who is using Orbot instead of the (desktop) Tor browser web_driver=orbot_web_driver, ) # When they access the source app's home page navigator.source_visits_source_homepage() # Then they see a warning warning_banner = navigator.driver.find_element_by_id("browser-android") assert warning_banner.is_displayed() assert "use the desktop version of Tor Browser" in warning_banner.text # And they are able to dismiss the warning warning_dismiss_button = navigator.driver.find_element_by_id( "browser-android-close") warning_dismiss_button.click() def warning_banner_is_hidden(): assert warning_banner.is_displayed() is False navigator.nav_helper.wait_for(warning_banner_is_hidden)
def test_warning_appears_if_tor_browser_not_in_use(self, sd_servers_v2, firefox_web_driver): # Given a user navigator = SourceAppNagivator( source_app_base_url=sd_servers_v2.source_app_base_url, # Who is using Firefox instead of the tor browser web_driver=firefox_web_driver, ) # When they access the source app's home page navigator.source_visits_source_homepage() # Then they see a warning warning_banner = navigator.driver.find_element_by_id("browser-tb") assert warning_banner.is_displayed() assert "It is recommended to use Tor Browser" in warning_banner.text # And they are able to dismiss the warning warning_dismiss_button = navigator.driver.find_element_by_id( "browser-tb-close") warning_dismiss_button.click() def warning_banner_is_hidden(): assert warning_banner.is_displayed() is False navigator.nav_helper.wait_for(warning_banner_is_hidden)
def test_source_session_timeout(self, locale, _sd_servers_with_short_timeout): # Given a source user accessing the app from their browser locale_with_commas = locale.replace("_", "-") with SourceAppNagivator.using_tor_browser_web_driver( source_app_base_url=_sd_servers_with_short_timeout.source_app_base_url, accept_languages=locale_with_commas, ) as navigator: # And they're logged in and are using the app navigator.source_visits_source_homepage() navigator.source_clicks_submit_documents_on_homepage() navigator.source_continues_to_submit_page() # And their session just expired time.sleep(SESSION_EXPIRATION_SECONDS + 1) # When the source user reloads the page navigator.driver.refresh() # Then the source user sees the "session expired" message notification = navigator.driver.find_element_by_class_name("error") assert notification.text if locale == "en_US": expected_text = "You were logged out due to inactivity." assert expected_text in notification.text save_screenshot_and_html(navigator.driver, locale, "source-session_timeout")
def test_warning_high_security(self, sd_servers_v2, tor_browser_web_driver): # Given a user navigator = SourceAppNagivator( source_app_base_url=sd_servers_v2.source_app_base_url, # Who is using the Tor browser web_driver=tor_browser_web_driver, ) # When they access the source app's home page navigator.source_visits_source_homepage() # Then they see a warning banner = navigator.driver.find_element_by_id("browser-security-level") assert banner.is_displayed() assert "Security Level is too low" in banner.text
def test_codenames_exceed_max_cookie_size(self, sd_servers_v2, tor_browser_web_driver): """Test generation of enough codenames that the resulting cookie exceeds the recommended `werkzeug.Response.max_cookie_size` = 4093 bytes. (#6043) """ navigator = SourceAppNagivator( source_app_base_url=sd_servers_v2.source_app_base_url, web_driver=tor_browser_web_driver, ) too_many = 2 * (werkzeug.Response.max_cookie_size // len(VALID_PASSWORD)) for _ in range(too_many): navigator.source_visits_source_homepage() navigator.source_clicks_submit_documents_on_homepage() navigator.source_continues_to_submit_page()
def test_source_cancels_at_submit_page(self, sd_servers_v2, tor_browser_web_driver): # Given a source user who created an account source_app_nav = SourceAppNagivator( source_app_base_url=sd_servers_v2.source_app_base_url, web_driver=tor_browser_web_driver, ) source_app_nav.source_visits_source_homepage() source_app_nav.source_clicks_submit_documents_on_homepage() source_app_nav.source_continues_to_submit_page() # When they click on the cancel button on the submit page, it succeeds source_app_nav.nav_helper.safe_click_by_css_selector(".form-controls a") # And the right message is displayed heading = source_app_nav.driver.find_element_by_id("submit-heading") assert "Submit Files or Messages" == heading.text
def test_submission_notifications_on_first_login(self, sd_servers_v2, tor_browser_web_driver): navigator = SourceAppNagivator( source_app_base_url=sd_servers_v2.source_app_base_url, web_driver=tor_browser_web_driver, ) # Given a source user who creates an account navigator.source_visits_source_homepage() navigator.source_clicks_submit_documents_on_homepage() navigator.source_continues_to_submit_page() # When they submit a message during their first login # Then it succeeds confirmation_text_first_submission = navigator.source_submits_a_message() # And they see the expected confirmation messages for a first submission on first login assert self.FIRST_SUBMISSION_TEXT in confirmation_text_first_submission # And when they submit a second message confirmation_text_second_submission = navigator.source_submits_a_message() # Then they don't see the messages since it's not their first submission assert self.FIRST_SUBMISSION_TEXT not in confirmation_text_second_submission
def test_source_cancels_at_login_page(self, sd_servers_v2, tor_browser_web_driver): # Given a source user who goes to the login page source_app_nav = SourceAppNagivator( source_app_base_url=sd_servers_v2.source_app_base_url, web_driver=tor_browser_web_driver, ) source_app_nav.source_visits_source_homepage() source_app_nav.source_chooses_to_login() # When they click on the cancel button on the login page, it succeeds source_app_nav.nav_helper.safe_click_by_css_selector(".form-controls a") source_app_nav.driver.get(sd_servers_v2.source_app_base_url) assert source_app_nav._is_on_source_homepage()
def test_index_and_logout(self, locale, sd_servers_v2): # Given a source user accessing the app from their browser locale_with_commas = locale.replace("_", "-") with SourceAppNagivator.using_tor_browser_web_driver( source_app_base_url=sd_servers_v2.source_app_base_url, accept_languages=locale_with_commas, ) as navigator: # And they have disabled JS in their browser disable_js(navigator.driver) # When they first login, it succeeds navigator.source_visits_source_homepage() save_screenshot_and_html(navigator.driver, locale, "source-index") navigator.source_clicks_submit_documents_on_homepage() navigator.source_continues_to_submit_page() # And when they logout, it succeeds navigator.source_logs_out() save_screenshot_and_html(navigator.driver, locale, "source-logout_page")
def test_login(self, locale, sd_servers_v2_with_clean_state, tor_browser_web_driver): # Given a source user accessing the app from their browser source_app_nav = SourceAppNagivator( source_app_base_url=sd_servers_v2_with_clean_state. source_app_base_url, web_driver=tor_browser_web_driver, ) # And they created an account source_app_nav.source_visits_source_homepage() # Take a screenshot of the login page source_app_nav.source_chooses_to_login() save_screenshot_and_html(source_app_nav.driver, locale, "source-login") # Take a screenshot of entering text in the login form source_app_nav.nav_helper.safe_send_keys_by_id( "codename", "ascension hypertext concert synopses") save_screenshot_and_html(source_app_nav.driver, locale, "source-enter-codename-in-login")
def test(self, locale, sd_servers_v2_with_clean_state, tor_browser_web_driver): # Given a source user accessing the app from their browser locale_with_commas = locale.replace("_", "-") source_app_nav = SourceAppNagivator( source_app_base_url=sd_servers_v2_with_clean_state. source_app_base_url, web_driver=tor_browser_web_driver, accept_languages=locale_with_commas, ) # And they created an account source_app_nav.source_visits_source_homepage() # Take a screenshot of the "account created" page source_app_nav.source_clicks_submit_documents_on_homepage() save_screenshot_and_html(source_app_nav.driver, locale, "source-generate") # Take a screenshot of showing the codename hint source_app_nav.source_continues_to_submit_page() source_app_nav.source_retrieves_codename_from_hint() save_screenshot_and_html(source_app_nav.driver, locale, "source-lookup-shows-codename") # Take a screenshot of entering text in the message field source_app_nav.nav_helper.safe_send_keys_by_id("msg", "Secret message éè") save_screenshot_and_html(source_app_nav.driver, locale, "source-submission_entered_text") # Take a screenshot of submitting a file source_app_nav.source_submits_a_file() save_screenshot_and_html(source_app_nav.driver, locale, "source-lookup") # Take a screenshot of doing a second submission source_app_nav.source_submits_a_message() save_screenshot_and_html(source_app_nav.driver, locale, "source-next_submission_flashed_message")
def test_submit_and_retrieve_happy_path(self, sd_servers_v2_with_clean_state, tor_browser_web_driver, firefox_web_driver): # Given a source user accessing the app from their browser source_app_nav = SourceAppNagivator( source_app_base_url=sd_servers_v2_with_clean_state. source_app_base_url, web_driver=tor_browser_web_driver, ) # And they created an account source_app_nav.source_visits_source_homepage() source_app_nav.source_clicks_submit_documents_on_homepage() source_app_nav.source_continues_to_submit_page() # And the source user submitted a message submitted_message = "Confidential message with some international characters: éèö" source_app_nav.source_submits_a_message(message=submitted_message) source_app_nav.source_logs_out() # When a journalist logs in journ_app_nav = JournalistAppNavigator( journalist_app_base_url=sd_servers_v2_with_clean_state. journalist_app_base_url, web_driver=firefox_web_driver, ) journ_app_nav.journalist_logs_in( username=sd_servers_v2_with_clean_state.journalist_username, password=sd_servers_v2_with_clean_state.journalist_password, otp_secret=sd_servers_v2_with_clean_state.journalist_otp_secret, ) journ_app_nav.journalist_checks_messages() # And they try to download the message # Then it succeeds and the journalist sees correct message servers_sd_config = sd_servers_v2_with_clean_state.config_in_use retrieved_message = journ_app_nav.journalist_downloads_first_message( encryption_mgr_to_use_for_decryption=EncryptionManager( gpg_key_dir=Path(servers_sd_config.GPG_KEY_DIR), journalist_key_fingerprint=servers_sd_config.JOURNALIST_KEY, )) assert retrieved_message == submitted_message
def test_submit_and_retrieve_happy_path(self, locale, sd_servers_v2_with_clean_state, tor_browser_web_driver, firefox_web_driver): # Given a source user accessing the app from their browser locale_with_commas = locale.replace("_", "-") source_app_nav = SourceAppNagivator( source_app_base_url=sd_servers_v2_with_clean_state. source_app_base_url, web_driver=tor_browser_web_driver, accept_languages=locale_with_commas, ) # And they created an account source_app_nav.source_visits_source_homepage() source_app_nav.source_clicks_submit_documents_on_homepage() source_app_nav.source_continues_to_submit_page() source_codename = source_app_nav.source_retrieves_codename_from_hint() # And the source user submitted a file submitted_content = "Confidential file with some international characters: éèö" source_app_nav.source_submits_a_file(file_content=submitted_content) source_app_nav.source_logs_out() # And a journalist logs in journ_app_nav = JournalistAppNavigator( journalist_app_base_url=sd_servers_v2_with_clean_state. journalist_app_base_url, web_driver=firefox_web_driver, ) journ_app_nav.journalist_logs_in( username=sd_servers_v2_with_clean_state.journalist_username, password=sd_servers_v2_with_clean_state.journalist_password, otp_secret=sd_servers_v2_with_clean_state.journalist_otp_secret, ) journ_app_nav.journalist_checks_messages() # When they star and unstar the submission, then it succeeds self._journalist_stars_and_unstars_single_message(journ_app_nav) # And when they try to download the file # Then it succeeds and the journalist sees the correct content apps_sd_config = sd_servers_v2_with_clean_state.config_in_use retrieved_message = journ_app_nav.journalist_downloads_first_message( encryption_mgr_to_use_for_decryption=EncryptionManager( gpg_key_dir=Path(apps_sd_config.GPG_KEY_DIR), journalist_key_fingerprint=apps_sd_config.JOURNALIST_KEY, )) assert retrieved_message == submitted_content # And when they reply to the source, it succeeds journ_app_nav.journalist_sends_reply_to_source() # And when the source user comes back source_app_nav.source_visits_source_homepage() source_app_nav.source_chooses_to_login() source_app_nav.source_proceeds_to_login(codename=source_codename) save_screenshot_and_html(source_app_nav.driver, locale, "source-checks_for_reply") # When they delete the journalist's reply, it succeeds self._source_deletes_journalist_reply(source_app_nav) save_screenshot_and_html(source_app_nav.driver, locale, "source-deletes_reply")
def test_generate_and_refresh_codenames_in_multiple_tabs( self, sd_servers_v2, tor_browser_web_driver ): navigator = SourceAppNagivator( source_app_base_url=sd_servers_v2.source_app_base_url, web_driver=tor_browser_web_driver, ) # Given a user who generated a codename in Tab A tab_a = navigator.driver.window_handles[0] navigator.source_visits_source_homepage() navigator.source_clicks_submit_documents_on_homepage() codename_a1 = self._extract_generated_codename(navigator) # And they then re-generated their codename in Tab navigator.source_visits_source_homepage() navigator.source_clicks_submit_documents_on_homepage() codename_a2 = self._extract_generated_codename(navigator) assert codename_a1 != codename_a2 # And they then opened a new tab, Tab B navigator.driver.execute_script("window.open('about:blank', '_blank')") tab_b = navigator.driver.window_handles[1] navigator.driver.switch_to.window(tab_b) assert tab_a != tab_b # And they also generated another codename in Tab B navigator.source_visits_source_homepage() navigator.source_clicks_submit_documents_on_homepage() codename_b = self._extract_generated_codename(navigator) assert codename_a2 != codename_b # And they ended up creating their account and submitting documents in Tab A navigator.driver.switch_to.window(tab_a) navigator.source_continues_to_submit_page() self._assert_is_on_lookup_page(navigator) assert navigator.source_retrieves_codename_from_hint() == codename_a2 navigator.source_submits_a_message() # When they try to re-generate a codename in Tab B navigator.driver.switch_to.window(tab_b) navigator.source_visits_source_homepage() navigator.nav_helper.safe_click_by_css_selector("#started-form button") # Then they get redirected to /lookup with the corresponding flash message self._assert_is_on_lookup_page(navigator) notification = self._extract_flash_message_content(navigator) if not navigator.accept_languages: assert "You were redirected because you are already logged in." in notification # And the user's actual codename is the expected one assert navigator.source_retrieves_codename_from_hint() == codename_a2
def test_generate_codenames_in_multiple_tabs(self, sd_servers_v2, tor_browser_web_driver): navigator = SourceAppNagivator( source_app_base_url=sd_servers_v2.source_app_base_url, web_driver=tor_browser_web_driver, ) # Given a user who generated a codename in Tab A tab_a = navigator.driver.window_handles[0] navigator.source_visits_source_homepage() navigator.source_clicks_submit_documents_on_homepage() codename_a = self._extract_generated_codename(navigator) # And they then opened a new tab, Tab B navigator.driver.execute_script("window.open('about:blank', '_blank')") tab_b = navigator.driver.window_handles[1] navigator.driver.switch_to.window(tab_b) assert tab_a != tab_b # And they also generated another codename in Tab B navigator.source_visits_source_homepage() navigator.source_clicks_submit_documents_on_homepage() codename_b = self._extract_generated_codename(navigator) assert codename_a != codename_b # And they ended up creating their account and submitting documents in Tab A navigator.driver.switch_to.window(tab_a) navigator.source_continues_to_submit_page() self._assert_is_on_lookup_page(navigator) assert navigator.source_retrieves_codename_from_hint() == codename_a navigator.source_submits_a_message() # When the user tries to create an account and submit documents in Tab B navigator.driver.switch_to.window(tab_b) navigator.source_continues_to_submit_page() # Then the submission fails and the user sees the corresponding flash message in Tab B self._assert_is_on_lookup_page(navigator) notification = self._extract_flash_message_content(navigator) if not navigator.accept_languages: assert "You are already logged in." in notification # And the user's actual codename is the one initially generated in Tab A assert navigator.source_retrieves_codename_from_hint() == codename_a
def test_no_codename_hint_on_second_login(self, sd_servers_v2, tor_browser_web_driver): navigator = SourceAppNagivator( source_app_base_url=sd_servers_v2.source_app_base_url, web_driver=tor_browser_web_driver, ) # Given a source user who creates an account # When they first login navigator.source_visits_source_homepage() navigator.source_clicks_submit_documents_on_homepage() navigator.source_continues_to_submit_page() # Then they are able to retrieve their codename from the UI source_codename = navigator.source_retrieves_codename_from_hint() assert source_codename # And they are able to close the codename hint UI content = navigator.driver.find_element_by_id("codename-show-checkbox") assert content.get_attribute("checked") is not None navigator.nav_helper.safe_click_by_id("codename-show") assert content.get_attribute("checked") is None # And on their second login navigator.source_logs_out() navigator.source_visits_source_homepage() navigator.source_chooses_to_login() navigator.source_proceeds_to_login(codename=source_codename) # The codename hint UI is no longer present codename = navigator.driver.find_elements_by_css_selector("#codename-reminder") assert len(codename) == 0