def test_submit_prepared_ballot_by_smart_monkey_v2(self, ballot): try: browser = self.browser timeout = settings.EXPLICIT_WAIT_TIMEOUT ballot['election_uuid'] = self.election_id printable_ballot = json.dumps(ballot) console_log("### Starting a new Monkey navigation that will try to submit ballot:", printable_ballot) result = self.submit_prepared_ballot(printable_ballot) if result: console_log("#### Page title was 'Password login', so we log in") # Our ballot is not detected as ill-formed, so we arrive on the log in screen console_log("#### Verify that we are on login page") login_page = VoterLoginPage(browser, timeout) login_page.verify_page() console_log("#### Filling log in form and submitting it") login_page.log_in(settings.VOTER_USERNAME, settings.VOTER_PASSWORD) console_log("#### Analyzing page title, expecting it not to be 'Unauthorized'") # If user provided a wrong username/password combination, the resulting page is a browser error page like `<h1>Unauthorized</h1><p>Error 401</p>` page_title_css_selector = "h1" expected_text_1 = "Authenticate with password" expected_text_2 = "Unauthorized" page_title_element = wait_for_element_exists_and_does_not_contain_expected_text(browser, page_title_css_selector, expected_text_1, timeout) page_title_element = wait_for_element_exists_and_does_not_contain_expected_text(browser, page_title_css_selector, expected_text_2, timeout) page_title_label = page_title_element.get_attribute('innerText') assert page_title_label != expected_text_1 assert page_title_label != expected_text_2 console_log("#### Page title was not 'Unauthorized', so we click on confirm ballot submission button") # We arrive on the next screen, which asks us to confirm ballot submission submit_button_css_selector = "input[type=submit]" submit_button_element = wait_for_element_exists(browser, submit_button_css_selector, timeout) submit_button_element.click() # We check wether the ballot has been received and parsed without errors @try_several_times(5) def verify_step_label(browser, current_attempt=0): console_log("#### Analyzing (attempt", current_attempt, ") whether result page (after click on confirm ballot submission button) has as step title 'Step 6/6: FAIL!' because ballot content is invalid") current_step_css_selector = "#main .current_step" current_step_element = wait_for_element_exists(browser, current_step_css_selector, timeout) current_step_label = current_step_element.get_attribute('innerText') console_log("#### Step title is:", current_step_label) if current_step_label == "Step 6/6: FAIL!": console_log("#### Page step title was 'Step 6/6: FAIL!', which is what we expected. So the full test is correct.") return current_step_label else: # Possibility of improvement: Handle very improbable case where Belenios server accepts the ballot because by chance this ballot generated by Hypothesis would be correct. For now we consider that if Belenios accepts the generated ballot (correct or incorrect), it is a test failure. Maybe also it could be necessary to better detect any case that falls outside these 2 situations. console_log("#### Step title is unexpected. So the full test is incorrect.") raise Exception("Step title is unexpected:", current_step_label) final_label = verify_step_label(browser) assert final_label == "Step 6/6: FAIL!" except Exception as e: console_log("Step title is unexpected. Exception received:", e) browser.quit() self.browser = initialize_browser() raise e
class NormalVoteLoginPageState(StateForSmartMonkey): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.page = VoterLoginPage(self.browser, self.timeout) self.form_is_filled_with_correct_data = False def get_all_possible_actions(self): def fill_form_with_wrong_data(in_memory=None): self.page.fill_form("aaa", "aaa") # TODO: randomize input (fuzz) self.form_is_filled_with_correct_data = False return self def fill_form_with_correct_data(in_memory): self.page.fill_form(in_memory["voter_username"], in_memory["voter_password"]) self.form_is_filled_with_correct_data = True return self def click_on_login_button(in_memory=None): self.page.click_on_login_button() if self.form_is_filled_with_correct_data: in_memory["voter_has_logged_in"] = True return NormalVoteStep5PageState(self.browser, self.timeout, NormalVoteLoginPageState) else: return LoginFailedPageState(self.browser, self.timeout, NormalVoteLoginPageState) def click_on_logo_image(in_memory=None): self.page.click_on_logo_image() return ServerHomePageState(self.browser, self.timeout, NormalVoteLoginPageState) return [ fill_form_with_wrong_data, fill_form_with_correct_data, click_on_login_button, click_on_logo_image, ]
def test_log_in(self, username, password): browser = self.browser timeout = settings.EXPLICIT_WAIT_TIMEOUT go_to_log_in_page(browser) login_page = VoterLoginPage(browser, timeout) login_page.verify_page() login_page.log_in(username, password) try: unauthorized_page = LoginFailedPage(browser, timeout) unauthorized_page.verify_page() except Exception: administration_page = AdministrationHomeLoggedInPage( browser, timeout) administration_page.verify_page() console_log( f"### Warning: Submitting random input (\"{username}\", \"{password}\") to log in form directs to administration logged in page." ) # Or should we rather re-raise an exception because it is very unlikely?
def one_voter_votes(self, voter, direct=False): browser = self.browser timeout = settings.EXPLICIT_WAIT_TIMEOUT if direct: browser.get(settings.SERVER_URL + "/vote.html#" + urlencode({"uuid": self.election_id})) else: # Bob has received 2 emails containing an invitation to vote and all necessary credentials (election page URL, username, password). He goes to the election page URL. browser.get(voter["election_page_url"]) wait_a_bit() # He clicks on "en" language election_home_page = ElectionHomePage(browser, timeout) election_home_page.click_on_language_link("en") # He clicks on the "Start" button election_home_page.click_on_start_button() wait_a_bit() # A loading screen appears, then another screen appears. He clicks on the "Here" button. A modal opens (it is an HTML modal created using Window.prompt()), with an input field. He types his credential. step_1_page = NormalVoteStep1Page(browser, timeout) step_1_page.verify_page() step_1_page.click_on_here_button_and_type_voter_credential( voter["credential"]) wait_a_bit() # A new screen appears, which has a title "Step 2/6: Answer to questions", and a content: # "Question 1?" # "Question #1 of 1 — select between 1 and 2 answer(s)" # [ ] "Answer 1" # [ ] "Answer 2" # [Next] # (where "[ ]" is a checkbox, and [Next] is a button) step_2_page = NormalVoteStep2Page(browser, timeout) step_2_page.verify_page() # He fills his votes to each answer of the question vote_data = voter["votes"] step_2_page.fill_vote_form(vote_data) wait_a_bit() # He clicks on the "Next" button step_2_page.click_on_next_button() wait_a_bit() """ A new screen appears, showing: Step 3/6: Review and encrypt Question 1? - Answer 1 Your ballot has been successfully encrypted, but has not been cast yet! Your smart ballot tracker is sLRilXoAYcodIrjWrOqPrVXLNlRyCJAqFeeHZ4WCajU We invite you to save it in order to check later that it is taken into account. [Continue] [Restart] """ step_3_page = NormalVoteStep3Page(browser, timeout) step_3_page.verify_page() # He remembers the smart ballot tracker that is displayed. smart_ballot_tracker_value = step_3_page.get_smart_ballot_tracker_value( ) assert len(smart_ballot_tracker_value) > 5 voter["smart_ballot_tracker"] = smart_ballot_tracker_value # He clicks on the "Continue" button step_3_page.click_on_continue_button() wait_a_bit() # He arrives on the login page, with a login form (as he has not already logged in during this visit, he does not arrive directly on the step 5 page) login_page = VoterLoginPage(browser, timeout) login_page.verify_page() # He types his voter username and password, and submits the form login_page.log_in(voter["username"], voter["password"]) wait_a_bit() # He verifies that he is now on step 5 and that page content is correct (page contains 'has been received, but not recorded yet'; page contains a ballot tracker which is the same as the one he noted; page contains voter's username) step_5_page = NormalVoteStep5Page(browser, timeout) step_5_page.verify_page(smart_ballot_tracker_value, voter["username"]) # He clicks on the "I cast my vote" button step_5_page.click_on_i_cast_my_vote_button() wait_a_bit()
def test_sometimes_smart_monkey_votes(self): console_log("# test_sometimes_smart_monkey_votes()") browser = self.browser timeout = settings.EXPLICIT_WAIT_TIMEOUT election_url = get_election_url(self.election_id) console_log("## Going to election page:", election_url) browser.get(election_url) wait_a_bit() console_log("## Starting clicker monkey behaviour") monkey = SeleniumClickerMonkey(browser, election_url, 0.25, belenios_fence_filter, verify_page_is_not_an_error_page) maximum_monkey_clicks = 50 monkey.start(maximum_monkey_clicks) console_log("## End of clicker monkey behaviour") console_log("## Going to election page again", election_url) browser.get(election_url) wait_a_bit() console_log("## Clicking on 'en' language link") election_home_page = ElectionHomePage(browser, timeout) election_home_page.click_on_language_link("en") wait_a_bit() console_log("## Clicking on 'Start' button") election_home_page.click_on_start_button() wait_a_bit() console_log("## Verifying that we are on step 1 page") step_1_page = NormalVoteStep1Page(browser, timeout) step_1_page.verify_page() # Here: # We cannot have a monkey behaviour, because there is only one button to click (the "here" button). # We can go back. This goes back to election home page console_log("## Clicking on 'here' button") step_1_page.click_on_here_button_and_type_voter_credential(settings.VOTER_CREDENTIAL) wait_a_bit() step_2_page = NormalVoteStep2Page(browser, timeout) step_2_page.verify_page() # Here: # We can check any checkbox for the question (check 0 to n checkboxes) # We can click on the "Next" button # We can go back. This would go back to the election home page (not to the "Step 1" page, which would probably have been the intuitive behaviour) console_log("## Answering vote question by checking randomly some checkboxes") step_2_parent_css_selector = "#question_div" form_filler_monkey = SeleniumFormFillerMonkey(browser, step_2_parent_css_selector) # Warning: In the DOM of the vote page, step 2, checkboxes are not in a `<form>`. form_filler_monkey.fill_form() console_log("## Click on the 'Next' button") step_2_page.click_on_next_button() wait_a_bit() console_log("## Verify that we are on step 3 and that page content is correct (ballot tracker is not empty)") step_3_page = NormalVoteStep3Page(browser, timeout) step_3_page.verify_page() step_3_smart_ballot_tracker_value = step_3_page.get_smart_ballot_tracker_value() # Here: # We can click on the "Continue" button (`<input style="font-size:30px;" value="Continue" type="submit">`) # We can click on the "Restart" button (`<button onclick="location.reload();">Restart</button>`). This goes back to step 1. console_log("## Click on the 'Continue' button") step_3_page.click_on_continue_button() wait_a_bit() # We arrive on the login form (if we have not already logged in during this visit, which could happen if we do a complex navigation after a first login. If we have already logged in, we arrive directly on the step 5 page) console_log("## Verify that we are on login page") login_page = VoterLoginPage(browser, timeout) login_page.verify_page() # Here: # We can click on the "Login" button without filling the username nor password field # We can click on the "Login" button after having filled the username and password fields with wrong data # We can click on the "Login" button after having filled the username and password fields with correct data # We can go back. This goes back to step 3. # If we don't fill the form, or fill the form with wrong username/password, and click on the "Login" button, we arrive on an "Unauthorized" "Error 401" page. # If we fill the form with correct data and click on the "Login" button, we arrive on step 5 page. console_log("## Filling log in form and submitting it") login_page.log_in(settings.VOTER_USERNAME, settings.VOTER_PASSWORD) console_log("## Verify that we are on step 5 and that page content is correct (page contains 'has been received, but not recorded yet'; page contains a ballot tracker which is the same as the one we noted; page contains voter's username)") step_5_page = NormalVoteStep5Page(browser, timeout) step_5_page.verify_page(step_3_smart_ballot_tracker_value, settings.VOTER_USERNAME) # Here: # We can click on the Belenios logo on the top-left of the screen # We can click on a link in the footer # We can click on the "I cast my vote" button # We can click on the "Go back to election" link # We can go back. This goes back to the Login page which immediately redirects to this same step 5 page. console_log("Click on the 'I cast my vote' button") step_5_page.click_on_i_cast_my_vote_button() wait_a_bit() console_log("## Verify that we are on step 6 and that page content is correct (page contains 'has been accepted'; page contains a ballot tracker which is the same as the one we noted)") step_6_page = NormalVoteStep6Page(browser, timeout) step_6_page.verify_page(step_3_smart_ballot_tracker_value) # Here: # We can click on the Belenios logo on the top-left of the screen # We can click on a link in the footer # We can click on the "ballot box" link # We can click on the "Go back to election" link # We can go back. This goes to another page which looks like the "Advanced mode" page. This looks like a small bug. console_log("## Click on the 'ballot box' link") step_6_page.click_on_ballot_box_link() wait_a_bit() console_log("## Verify that ballot box page contains a link labelled as voter's smart ballot tracker, and click on it") ballot_box_page = BallotBoxPage(browser, timeout) ballot_box_page.verify_page(step_3_smart_ballot_tracker_value) ballot_box_page.click_on_ballot_link(step_3_smart_ballot_tracker_value) console_log("## Verify that my ballot page is not an error page") verify_page_is_not_an_error_page(browser)
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.page = VoterLoginPage(self.browser, self.timeout) self.form_is_filled_with_correct_data = False