def administrator_completes_creation_of_election(self): # Alice, as an administrator of an election, wants to finalize her draft election creation, to start the vote. # She opens a browser self.browser = initialize_browser_for_scenario_2() browser = self.browser # She logs in as administrator log_in_as_administrator(browser) # She goes to the draft election administration page browser.get(self.draft_election_administration_page_url) # - In "Validate creation" section, she clicks on the "Create election" link # - (She arrives on the "Checklist" page, that lists all main parameters of the election for review, and that flags incoherent or misconfigured parameters. For example, in this test scenario, it displays 2 warnings: "Warning: No trustees were set. This means that the server will manage the election key by itself.", and "Warning: No contact was set!") # - In the "Validate creation" section, she clicks on the "Create election" button # - (She arrives back on the "My test election for Scenario 1 — Administration" page. Its contents have changed. There is now a text saying "The election is open. Voters can vote.", and there are now buttons "Close election", "Archive election", "Delete election") # - She remembers the URL of the voting page, that is where the "Election home" link points to # - She checks that a "Close election" button is present (but she does not click on it) self.election_page_url = administrator_validates_creation_of_election(browser) console_log("election_page_url:", self.election_page_url) self.election_id = election_page_url_to_election_id(self.election_page_url) console_log("election_id:", self.election_id) # She logs out log_out(browser) # She closes the window, and re-opens it (for next emulated user) browser.quit() self.browser = initialize_browser_for_scenario_2()
def administrator_completes_creation_of_election(self): # Alice, as an administrator of an election, wants to finalize her draft election creation, to start the vote. # She opens a browser self.browser = initialize_browser_for_scenario_2() browser = self.browser # She logs in as administrator log_in_as_administrator(browser) # She goes to the draft election administration page browser.get(self.draft_election_administration_page_url) # - In "Validate creation" section, she clicks on the "Create election" link # - (She arrives on the "Checklist" page, that lists all main parameters of the election for review, and that flags incoherent or misconfigured parameters. For example, in this test scenario, it displays 2 warnings: "Warning: No trustees were set. This means that the server will manage the election key by itself.", and "Warning: No contact was set!") # - In the "Validate creation" section, she clicks on the "Create election" button # - (She arrives back on the "My test election for Scenario 1 — Administration" page. Its contents have changed. There is now a text saying "The election is open. Voters can vote.", and there are now buttons "Close election", "Archive election", "Delete election") # - She remembers the URL of the voting page, that is where the "Election home" link points to # - She checks that a "Close election" button is present (but she does not click on it) self.election_page_url = administrator_validates_creation_of_election( browser) console_log("election_page_url:", self.election_page_url) self.election_id = election_page_url_to_election_id( self.election_page_url) console_log("election_id:", self.election_id) # She logs out log_out(browser, self.election_id) # She closes the window, and re-opens it (for next emulated user) browser.quit() self.browser = initialize_browser_for_scenario_2()
def administrator_finishes_tallying_of_election(self, max_trustees=None): self.browser = initialize_browser_for_scenario_2() browser = self.browser # Alice goes to the election page election_url = self.election_page_url browser.get(election_url) wait_a_bit() # She clicks on "en" language select = Select( wait_for_element_exists(browser, ".lang_box select", settings.EXPLICIT_WAIT_TIMEOUT)) select.select_by_visible_text("en") submit = wait_for_element_exists(browser, ".lang_box input[type=submit]", settings.EXPLICIT_WAIT_TIMEOUT) submit.click() wait_a_bit() # She clicks on the "Administer this election" link administration_link_label = "Administer this election" administration_link_element = wait_for_an_element_with_partial_link_text_exists( browser, administration_link_label, settings.EXPLICIT_WAIT_TIMEOUT) administration_link_element.click() # She logs in as administrator log_in_as_administrator(browser, from_a_login_page=True) wait_a_bit() # She checks that the "DONE?" column of each trustee is to "Yes" (or if there is a threshold on trustees, she checks that at least `max_trustees` have a "Yes") expected_label = "Yes" yes_cells_selector = "#main table tr td:nth-last-child(1)" attribute_name = "innerText" if max_trustees is None: verify_all_elements_have_attribute_value(browser, yes_cells_selector, attribute_name, expected_label) else: verify_some_elements_have_attribute_value(browser, yes_cells_selector, attribute_name, expected_label, max_trustees) # She clicks on the "Compute the result" button compute_result_button_expected_label = "Compute the result" compute_result_button_css_selector = "#main input[type=submit][value='" + compute_result_button_expected_label + "']" compute_result_button_element = wait_for_element_exists( browser, compute_result_button_css_selector) compute_result_button_element.click() wait_a_bit() self.administrator_verifies_vote_results()
def administrator_completes_creation_of_election(self): # Alice, as an administrator of an election, wants to finalize her draft election creation, to start the vote. # She opens a browser self.browser = initialize_browser_for_scenario_2() browser = self.browser # She logs in as administrator log_in_as_administrator(browser) # She goes to the draft election administration page browser.get(self.draft_election_administration_page_url) # In the "Trustees" section, she clicks on "here" # TODO: use a better selector: edit Belenios page to use an ID in this DOM element setup_election_key_link_label = "here" setup_election_key_link_element = wait_for_an_element_with_partial_link_text_exists( browser, setup_election_key_link_label) setup_election_key_link_element.click() # She checks that in the table on all rows, the "STATE" column is now "done" state_column_css_selector = "#main table tr td:last-of-type" attribute_name = "innerText" attribute_value = "done" verify_all_elements_have_attribute_value(browser, state_column_css_selector, attribute_name, attribute_value) wait_a_bit() # She clicks on the "Go back to election draft" link go_back_link_label = "Go back to election draft" go_back_link_element = wait_for_an_element_with_partial_link_text_exists( browser, go_back_link_label, settings.EXPLICIT_WAIT_TIMEOUT) go_back_link_element.click() # - In "Validate creation" section, she clicks on the "Create election" link # - (She arrives on the "Checklist" page, that lists all main parameters of the election for review, and that flags incoherent or misconfigured parameters.) # - She checks the presence of text "election ready" # - In the "Validate creation" section, she clicks on the "Create election" button # - (She arrives back on the "My test election for Scenario 1 — Administration" page. Its contents have changed. There is now a text saying "The election is open. Voters can vote.", and there are now buttons "Close election", "Archive election", "Delete election") # - She remembers the URL of the voting page, that is where the "Election home" link points to # - She checks that a "Close election" button is present (but she does not click on it) self.election_page_url = administrator_validates_creation_of_election( browser) console_log("election_page_url:", self.election_page_url) self.election_id = election_page_url_to_election_id( self.election_page_url) console_log("election_id:", self.election_id) wait_a_bit() # She logs out log_out(browser) # She closes the window, and re-opens it (for next emulated user) browser.quit() self.browser = initialize_browser_for_scenario_2()
def administrator_does_tallying_of_election(self): browser = self.browser # Alice goes to the election page election_url = self.election_page_url # Could also be obtained with self.voters_data[self.voters_email_addresses[0]]["election_page_url"] browser.get(election_url) wait_a_bit() # She clicks on "en" language english_language_link_expected_label = "en" english_language_link_element = wait_for_an_element_with_link_text_exists( browser, english_language_link_expected_label, settings.EXPLICIT_WAIT_TIMEOUT) english_language_link_element.click() wait_a_bit() # She clicks on the "Administer this election" link administration_link_label = "Administer this election" administration_link_element = wait_for_an_element_with_partial_link_text_exists( browser, administration_link_label, settings.EXPLICIT_WAIT_TIMEOUT) administration_link_element.click() # She logs in as administrator log_in_as_administrator(browser, from_a_login_page=True) wait_a_bit() # She clicks on the "Close election" button close_election_button_label = "Close election" close_election_button_css_selector = build_css_selector_to_find_buttons_in_page_content_by_value( close_election_button_label) close_election_button_element = wait_for_element_exists( browser, close_election_button_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) close_election_button_element.click() wait_a_bit() # She clicks on the "Proceed to vote counting" button proceed_button_label = "Proceed to vote counting" proceed_button_css_selector = build_css_selector_to_find_buttons_in_page_content_by_value( proceed_button_label) proceed_button_element = wait_for_element_exists( browser, proceed_button_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) proceed_button_element.click() wait_a_bit() # FIXME: If no voter has cast their vote, it shows a "Internal Server Error" "Error 500" page self.administrator_verifies_vote_results()
def administrator_finishes_tallying_of_election(self): self.browser = initialize_browser_for_scenario_2() browser = self.browser # Alice goes to the election page election_url = self.election_page_url browser.get(election_url) wait_a_bit() # She clicks on the "Administer this election" link administration_link_label = "Administer this election" administration_link_element = wait_for_an_element_with_partial_link_text_exists( browser, administration_link_label, settings.EXPLICIT_WAIT_TIMEOUT) administration_link_element.click() # She logs in as administrator log_in_as_administrator(browser, from_a_login_page=True) wait_a_bit() # She checks that encrypted tally hash is still the same as the first time it has been displayed to her encrypted_tally_hash_css_selector = "#encrypted_tally_hash" encrypted_tally_hash_element = wait_for_element_exists_and_has_non_empty_content( browser, encrypted_tally_hash_css_selector) encrypted_tally_hash = encrypted_tally_hash_element.get_attribute( 'innerText') assert encrypted_tally_hash == self.encrypted_tally_hash, "Error: Encrypted tally hash displayed to trustee (" + encrypted_tally_hash + ") is not the same as the one displayed to election administrator (" + self.encrypted_tally_hash + ")." # She checks that the "DONE?" column of each trustee is to "Yes" expected_label = "Yes" yes_cells_selector = "#main table tr td:nth-last-child(1)" yes_cells_elements = wait_for_elements_exist(browser, yes_cells_selector) assert len(yes_cells_elements ) > 0, "Error: could not find last cell in table in page" for element in yes_cells_elements: assert element.get_attribute( 'innerText' ) == expected_label, "Error: The last cell of a row in table that is displayed in page has a value that is '" + element.get_attribute( 'innerText') + "' instead of expected '" + expected_label + "'" # She clicks on the "Compute the result" button compute_result_button_expected_label = "Compute the result" compute_result_button_css_selector = "#main input[type=submit][value='" + compute_result_button_expected_label + "']" compute_result_button_element = wait_for_element_exists( browser, compute_result_button_css_selector) compute_result_button_element.click() wait_a_bit() self.administrator_verifies_vote_results()
def administrator_finishes_tallying_of_election(self): self.browser = initialize_browser_for_scenario_2() browser = self.browser # Alice goes to the election page election_url = self.election_page_url browser.get(election_url) wait_a_bit() # She clicks on the "Administer this election" link administration_link_label = "Administer this election" administration_link_element = wait_for_an_element_with_partial_link_text_exists(browser, administration_link_label, settings.EXPLICIT_WAIT_TIMEOUT) administration_link_element.click() # She logs in as administrator log_in_as_administrator(browser, from_a_login_page=True) wait_a_bit() # She checks that encrypted tally hash is still the same as the first time it has been displayed to her encrypted_tally_hash_css_selector = "#encrypted_tally_hash" encrypted_tally_hash_element = wait_for_element_exists_and_has_non_empty_content(browser, encrypted_tally_hash_css_selector) encrypted_tally_hash = encrypted_tally_hash_element.get_attribute('innerText') assert encrypted_tally_hash == self.encrypted_tally_hash, "Error: Encrypted tally hash displayed to trustee (" + encrypted_tally_hash + ") is not the same as the one displayed to election administrator (" + self.encrypted_tally_hash + ")." # She checks that the "DONE?" column of each trustee is to "Yes" expected_label = "Yes" yes_cells_selector = "#main table tr td:nth-last-child(1)" yes_cells_elements = wait_for_elements_exist(browser, yes_cells_selector) assert len(yes_cells_elements) > 0, "Error: could not find last cell in table in page" for element in yes_cells_elements: assert element.get_attribute('innerText') == expected_label, "Error: The last cell of a row in table that is displayed in page has a value that is '" + element.get_attribute('innerText') + "' instead of expected '" + expected_label + "'" # She clicks on the "Compute the result" button compute_result_button_expected_label = "Compute the result" compute_result_button_css_selector = "#main input[type=submit][value='" + compute_result_button_expected_label + "']" compute_result_button_element = wait_for_element_exists(browser, compute_result_button_css_selector) compute_result_button_element.click() wait_a_bit() self.administrator_verifies_vote_results()
def administrator_does_tallying_of_election(self): browser = self.browser # Alice goes to the election page election_url = self.election_page_url # Could also be obtained with self.voters_data[self.voters_email_addresses[0]]["election_page_url"] browser.get(election_url) wait_a_bit() # She clicks on the "Administer this election" link administration_link_label = "Administer this election" administration_link_element = wait_for_an_element_with_partial_link_text_exists(browser, administration_link_label, settings.EXPLICIT_WAIT_TIMEOUT) administration_link_element.click() # She logs in as administrator log_in_as_administrator(browser, from_a_login_page=True) wait_a_bit() # She clicks on the "Close election" button close_election_button_label = "Close election" close_election_button_css_selector = build_css_selector_to_find_buttons_in_page_content_by_value(close_election_button_label) close_election_button_element = wait_for_element_exists(browser, close_election_button_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) close_election_button_element.click() wait_a_bit() # She clicks on the "Proceed to vote counting" button proceed_button_label = "Proceed to vote counting" proceed_button_css_selector = build_css_selector_to_find_buttons_in_page_content_by_value(proceed_button_label) proceed_button_element = wait_for_element_exists(browser, proceed_button_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) proceed_button_element.click() wait_a_bit() # She clicks on the "Proceed to decryption" button decrypt_button_label = "Proceed to decryption" decrypt_button_css_selector = build_css_selector_to_find_buttons_in_page_content_by_value(decrypt_button_label) decrypt_button_element = wait_for_element_exists(browser, decrypt_button_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) decrypt_button_element.click() wait_a_bit()
def administrator_does_tallying_of_election(self): browser = self.browser # Alice goes to the election page election_url = self.election_page_url # Could also be obtained with self.voters_data[self.voters_email_addresses[0]]["election_page_url"] browser.get(election_url) wait_a_bit() # She clicks on the "Administer this election" link administration_link_label = "Administer this election" administration_link_element = wait_for_an_element_with_partial_link_text_exists(browser, administration_link_label, settings.EXPLICIT_WAIT_TIMEOUT) administration_link_element.click() # She logs in as administrator log_in_as_administrator(browser, from_a_login_page=True) wait_a_bit() # She clicks on the "Close election" button close_election_button_label = "Close election" close_election_button_css_selector = build_css_selector_to_find_buttons_in_page_content_by_value(close_election_button_label) close_election_button_element = wait_for_element_exists(browser, close_election_button_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) close_election_button_element.click() wait_a_bit() # She clicks on the "Proceed to vote counting" button proceed_button_label = "Proceed to vote counting" proceed_button_css_selector = build_css_selector_to_find_buttons_in_page_content_by_value(proceed_button_label) proceed_button_element = wait_for_element_exists(browser, proceed_button_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) proceed_button_element.click() wait_a_bit() # FIXME: If no voter has cast their vote, it shows a "Internal Server Error" "Error 500" page self.administrator_verifies_vote_results()
def administrator_invites_trustees(self): self.browser = initialize_browser_for_scenario_2() browser = self.browser log_in_as_administrator(browser) browser.get(self.draft_election_administration_page_url) wait_a_bit() # In the trustees section, she clicks on the "here" link # TODO: use a better selector: edit Belenios page to use an ID in this DOM element setup_election_key_link_label = "here" setup_election_key_link_element = wait_for_an_element_with_partial_link_text_exists( browser, setup_election_key_link_label) setup_election_key_link_element.click() wait_a_bit() # She adds two trustees (their email address), and remembers the link she will send to each trustee self.links_for_trustees = [] email_address_field_css_selector = "#main form input[type=text]" submit_button_css_selector = "#main form input[type=submit][value=Add]" for idx, email_address in enumerate(settings.TRUSTEES_EMAIL_ADDRESSES): email_address_field_element = wait_for_element_exists( browser, email_address_field_css_selector) email_address_field_element.clear() email_address_field_element.send_keys(email_address) submit_button_element = wait_for_element_exists( browser, submit_button_css_selector) submit_button_element.click() wait_a_bit() trustee_link_css_selector = "#main table tr:nth-of-type(" + str( idx + 3 ) + ") td:nth-of-type(4) a" # First row of table corresponds to column titles. Second row correpond to server trustee. trustee_link_element = wait_for_element_exists_and_has_non_empty_content( browser, trustee_link_css_selector) self.links_for_trustees.append( trustee_link_element.get_attribute('href')) wait_a_bit() # She sends to each trustee an email containing their own link subject = "Link to generate the decryption key" content_format = """\ Dear trustee, You will find below the link to generate your private decryption key, used to tally the election. {link_for_trustee} Here's the instructions: 1. click on the link 2. click on "generate a new key pair" 3. your private key will appear in another window or tab. Make sure you SAVE IT properly otherwise it will not possible to tally and the election will be canceled. 4. in the first window, click on "submit" to send the public part of your key, used encrypt the votes. For verification purposes, you should save this part (that starts with "pok" "challenge"), for example sending yourself an email. Regarding your private key, it is crucial you save it (otherwise the election will be canceled) and store it securely (if your private key is known together with the private keys of the other trustees, then vote privacy is no longer guaranteed). We suggest two options: 1. you may store the key on a USB stick and store it in a safe. 2. Or you may simply print it and store it in a safe. Of course, more cryptographic solutions are welcome as well. Thank you for your help, -- The election administrator.\ """ for idx, trustee_email_address in enumerate( settings.TRUSTEES_EMAIL_ADDRESSES): custom_content = content_format.format( link_for_trustee=self.links_for_trustees[idx]) self.fake_sent_emails_manager.send_email( settings.ADMINISTRATOR_EMAIL_ADDRESS, trustee_email_address, subject, custom_content) # Optionnaly, she logs out # log_out(browser) # She closes the window browser.quit()
def administrator_invites_trustees_and_sets_threshold(self): self.browser = initialize_browser_for_scenario_2() browser = self.browser log_in_as_administrator(browser) browser.get(self.draft_election_administration_page_url) wait_a_bit() # In the trustees section, she clicks on the "here" link setup_election_key_link_label = "here" setup_election_key_link_element = wait_for_an_element_with_partial_link_text_exists( browser, setup_election_key_link_label) setup_election_key_link_element.click() wait_a_bit() # She clicks on the "threshold mode" link threshold_mode_link_label = "threshold mode" threshold_mode_link_element = wait_for_an_element_with_partial_link_text_exists( browser, threshold_mode_link_label) threshold_mode_link_element.click() wait_a_bit() # She adds `NUMBER_OF_TRUSTEES` trustees (their email address), and remembers the link she will send to each trustee # (The threshold field appears only after user has added the first trustee) self.links_for_trustees = [] email_address_field_css_selector = "#main form input[name=__co_eliom_id]" # TODO: Maybe we should edit Belenios' HTML template to rename `__co_eliom_id` to something more explicit, like `__co_eliom_new_trustee_email_address` submit_button_css_selector = "#main form input[type=submit][value=Add]" for idx, email_address in enumerate(settings.TRUSTEES_EMAIL_ADDRESSES): email_address_field_element = wait_for_element_exists( browser, email_address_field_css_selector) email_address_field_element.clear() email_address_field_element.send_keys(email_address) submit_button_element = wait_for_element_exists( browser, submit_button_css_selector) submit_button_element.click() trustee_link_css_selector = "#main table tbody tr:nth-of-type(" + str( idx + 2) + ") td:nth-of-type(3) a" trustee_link_element = wait_for_element_exists_and_has_non_empty_content( browser, trustee_link_css_selector) self.links_for_trustees.append( trustee_link_element.get_attribute('href')) wait_a_bit() # In the field next to "Threshold:", she types the value of `U` (aka `TRUSTEES_THRESHOLD_VALUE`) threshold_value_field_css_selector = "#main form input[name=__co_eliom_threshold]" threshold_value_field_element = wait_for_element_exists( browser, threshold_value_field_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) threshold_value_field_value = settings.TRUSTEES_THRESHOLD_VALUE threshold_value_field_element.clear() threshold_value_field_element.send_keys(threshold_value_field_value) wait_a_bit() # She clicks on the "Set" button submit_button_css_selector = "#main form input[type=submit][value=Set]" submit_button_element = wait_for_element_exists( browser, submit_button_css_selector) submit_button_element.click() wait_a_bit() # She checks that in the table, the "STATE" column is "1a" on every row expected_value = "1a" verify_all_trustee_states_in_table(browser, expected_value) # She sends to each trustee an email containing their own link subject = "Link to generate the decryption key" content_format = """\ Dear trustee, You will find below the link to generate your private decryption key, used to tally the election. {link_for_trustee} Here's the instructions: 1. click on the link 2. click on "generate a new key pair" 3. your private key will appear in another window or tab. Make sure you SAVE IT properly otherwise it will not possible to tally and the election will be canceled. 4. in the first window, click on "submit" to send the public part of your key, used encrypt the votes. For verification purposes, you should save this part (that starts with "pok" "challenge"), for example sending yourself an email. Regarding your private key, it is crucial you save it (otherwise the election will be canceled) and store it securely (if your private key is known together with the private keys of the other trustees, then vote privacy is no longer guaranteed). We suggest two options: 1. you may store the key on a USB stick and store it in a safe. 2. Or you may simply print it and store it in a safe. Of course, more cryptographic solutions are welcome as well. Thank you for your help, -- The election administrator.\ """ for idx, trustee_email_address in enumerate( settings.TRUSTEES_EMAIL_ADDRESSES): custom_content = content_format.format( link_for_trustee=self.links_for_trustees[idx]) self.fake_sent_emails_manager.send_email( settings.ADMINISTRATOR_EMAIL_ADDRESS, trustee_email_address, subject, custom_content) # Optionnaly, she logs out # log_out(browser) # She closes the window browser.quit()
def administrator_creates_election(self): # # Setting up a new election (action of the administrator) browser = self.browser # Alice has been given administrator rights on an online voting app called Belenios. She goes # to check out its homepage and logs in log_in_as_administrator(browser) # She starts creation of the election: # - She clicks on the "Prepare a new election" link # (- She keeps default values on the form: Credential management is automatic (not manual), and Authentication method is Password, not CAS) # - She clicks on the "Proceed" button (this redirects to the "Preparation of election" page) # - She changes values of fields name and description of the election # - She clicks on the "Save changes button" (the one that is next to the election description field) administrator_starts_creation_of_election(browser) # She edits election's questions: # - She clicks on the "Edit questions" link, to write her own questions # - She arrives on the Questions page. She checks that the page title is correct # - She removes answer 3 # - She clicks on the "Save changes" button (this redirects to the "Preparation of election" page) administrator_edits_election_questions(browser) # She sets election's voters: # - She clicks on the "Edit voters" link, to then type the list of voters # - She types N e-mail addresses (the list of invited voters) # - She clicks on the "Add" button to submit changes # - She clicks on "Return to draft page" link self.voters_email_addresses = random_email_addresses_generator(settings.NUMBER_OF_INVITED_VOTERS) administrator_sets_election_voters(browser, self.voters_email_addresses) # She clicks on button "Generate on server" generate_on_server_button_label = "Generate on server" generate_on_server_button_css_selector = build_css_selector_to_find_buttons_in_page_content_by_value(generate_on_server_button_label) generate_on_server_button_element = wait_for_element_exists(browser, generate_on_server_button_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) generate_on_server_button_element.click() wait_a_bit() # (Server sends emails to voters.) She checks that server does not show any error that would happen when trying to send these emails (this can happen if sendmail is not configured) confirmation_sentence_expected_text = "Credentials have been generated and mailed!" confirmation_sentence_css_selector = "#main p" wait_for_element_exists_and_contains_expected_text(browser, confirmation_sentence_css_selector, confirmation_sentence_expected_text, settings.EXPLICIT_WAIT_TIMEOUT) # Now we do a sanity check that server has really tried to send emails. For this, we look for email addresses in the temporary file where our fake sendmail executable redirects its inputs to. """ An email sent by Belenios (using sendmail or using the fake sendmail) to a voter looks like this: Content-type: text/plain; charset="UTF-8" Content-transfer-encoding: quoted-printable From: Belenios public server <*****@*****.**> To: "*****@*****.**" <*****@*****.**> Subject: Your credential for election My test election for Scenario 1 MIME-Version: 1.0 X-Mailer: OcamlNet (ocamlnet.sourceforge.net) Date: Wed, 31 Oct 2018 15:22:27 +0100 You are listed as a voter for the election My test election for Scenario 1 You will find below your credential. To cast a vote, you will also need a password, sent in a separate email. Be careful, passwords and credentials look similar but play different roles. You will be asked to enter your credential before entering the voting booth. Login and passwords are required once your ballot is ready to be cast. Credential: yQVDQaKSAQVjdZq Page of the election: http://localhost:8001/elections/AFFNDEPnpy21bw/ Note that you are allowed to vote several times. Only the last vote counts. ---------- Vous =C3=AAtes enregistr=C3=A9(e) en tant qu=27=C3=A9lecteur(trice) pour=20= l=27=C3=A9lection My test election for Scenario 1 Veuillez trouver ci-dessous votre code de vote. Pour soumettre un bulletin, vous aurez =C3=A9galement besoin d=27un mot de passe, envoy=C3=A9= dans un e-mail s=C3=A9par=C3=A9. Soyez attentif(ve), le mot de passe et le cod= e de vote se ressemblent mais jouent des r=C3=B4les diff=C3=A9rents. Le syst=C3= =A8me vous demandera votre code de vote d=C3=A8s l=27entr=C3=A9e dans l=27isoloir=20= virtuel. Le nom d=27utilisateur et le mot de passe sont n=C3=A9cessaires lorsque votr= e bulletin est pr=C3=AAt =C3=A0 =C3=AAtre soumis. Code de vote=C2=A0: yQVDQaKSAQVjdZq Page de l=27=C3=A9lection=C2=A0: http://localhost:8001/elections/AFFNDEPn= py21bw/ Notez que vous pouvez voter plusieurs fois. Seul le dernier vote est pris en compte. --=20 """ email_address_to_look_for = self.voters_email_addresses[0] text_to_look_for = 'To: "' + email_address_to_look_for + '"' email_address_found = self.fake_sent_emails_manager.find_in_sent_emails(text_to_look_for) assert email_address_found, "Text '" + email_address_to_look_for + "'' not found in fake sendmail log file" # She clicks on the "Proceed" link proceed_link_css_selector = "#generic_proceed_link" proceed_link_element = wait_for_element_exists(browser, proceed_link_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) proceed_link_element.click() wait_a_bit() # In "Authentication" section, she clicks on the "Generate and mail missing passwords" button generate_and_mail_missing_passwords_button_label = "Generate and mail missing passwords" generate_and_mail_missing_passwords_button_element = wait_for_element_exists(browser, build_css_selector_to_find_buttons_in_page_content_by_value(generate_and_mail_missing_passwords_button_label), settings.EXPLICIT_WAIT_TIMEOUT) generate_and_mail_missing_passwords_button_element.click() wait_a_bit() # She checks that the page contains expected confirmation text, instead of an error (TODO: explain in which case an error can happen, and check that it does not show) confirmation_sentence_expected_text = "Passwords have been generated and mailed!" confirmation_sentence_css_selector = "#main p" wait_for_element_exists_and_contains_expected_text(browser, confirmation_sentence_css_selector, confirmation_sentence_expected_text, settings.EXPLICIT_WAIT_TIMEOUT) # She clicks on the "Proceed" link (this redirects to the "Preparation of election" page) proceed_link_css_selector = "#generic_proceed_link" proceed_link_element = wait_for_element_exists(browser, proceed_link_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) proceed_link_element.click() wait_a_bit() self.election_page_url = administrator_validates_creation_of_election(browser) console_log("election_page_url:", self.election_page_url) self.election_id = election_page_url_to_election_id(self.election_page_url) console_log("election_id:", self.election_id) log_out(browser)
def administrator_starts_creation_of_manual_election(self): # # Setting up a new election (action of the administrator) browser = self.browser # Alice has been given administrator rights on an online voting app called Belenios. She goes # to check out its homepage and logs in log_in_as_administrator(browser) # She starts creation of the election: # - She clicks on the "Prepare a new election" link # - She picks the Credential management method: manual # (- She keeps default value for Authentication method: it is Password, not CAS) # - She clicks on the "Proceed" button (this redirects to the "Preparation of election" page) # - She changes values of fields name and description of the election # - She clicks on the "Save changes button" (the one that is next to the election description field) administrator_starts_creation_of_election(browser, True) # She remembers the URL of the draft election administration page self.draft_election_administration_page_url = browser.current_url # She edits election's questions: # - She clicks on the "Edit questions" link, to write her own questions # - She arrives on the Questions page. She checks that the page title is correct # - She removes answer 3 # - She clicks on the "Save changes" button (this redirects to the "Preparation of election" page) administrator_edits_election_questions(browser) # She sets election's voters: # - She clicks on the "Edit voters" link, to then type the list of voters # - She types N e-mail addresses (the list of invited voters) # - She clicks on the "Add" button to submit changes # - She clicks on "Return to draft page" link self.voters_email_addresses = random_email_addresses_generator(settings.NUMBER_OF_INVITED_VOTERS) administrator_sets_election_voters(browser, self.voters_email_addresses) # In "Authentication" section, she clicks on the "Generate and mail missing passwords" button generate_and_mail_missing_passwords_button_label = "Generate and mail missing passwords" generate_and_mail_missing_passwords_button_css_selector = "#main input[type=submit][value='" + generate_and_mail_missing_passwords_button_label + "']" generate_and_mail_missing_passwords_button_element = wait_for_element_exists(browser, generate_and_mail_missing_passwords_button_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) generate_and_mail_missing_passwords_button_element.click() # FIXME: This click does not get triggered when we have maximized the browser window wait_a_bit() # She checks that the page contains expected confirmation text, instead of an error (TODO: explain in which case an error can happen, and check that it does not show) confirmation_sentence_expected_text = "Passwords have been generated and mailed!" confirmation_sentence_css_selector = "#main p" wait_for_element_exists_and_contains_expected_text(browser, confirmation_sentence_css_selector, confirmation_sentence_expected_text, settings.EXPLICIT_WAIT_TIMEOUT) # She clicks on the "Proceed" link (this redirects to the "Preparation of election" page) proceed_link_expected_label = "Proceed" proceed_link_css_selector = "#main a" proceed_link_element = wait_for_element_exists_and_contains_expected_text(browser, proceed_link_css_selector, proceed_link_expected_label, settings.EXPLICIT_WAIT_TIMEOUT) proceed_link_element.click() wait_a_bit() # In "Credentials" section, she clicks on "Credential management" link credential_management_expected_label = "Credential management" credential_management_link_element = wait_for_an_element_with_partial_link_text_exists(browser, credential_management_expected_label) credential_management_link_element.click() wait_a_bit() # She remembers the link displayed link_for_credential_authority_css_selector = "#main a" link_for_credential_authority_element = wait_for_element_exists_and_has_non_empty_content(browser, link_for_credential_authority_css_selector) link_label = link_for_credential_authority_element.get_attribute('innerText').strip() self.credential_authority_link = link_label # She sends the remembered link to the credential authority by email (actually we don't need to send anything because we will act as the credential authority) # Optionnaly, she logs out # log_out(browser) # She closes the browser window browser.quit()
def administrator_starts_tallying_of_election(self): browser = self.browser # Alice goes to the election page election_url = self.election_page_url # Could also be obtained with self.voters_data[self.voters_email_addresses[0]]["election_page_url"] browser.get(election_url) wait_a_bit() # She clicks on the "Administer this election" link administration_link_label = "Administer this election" administration_link_element = wait_for_an_element_with_partial_link_text_exists( browser, administration_link_label, settings.EXPLICIT_WAIT_TIMEOUT) administration_link_element.click() # She logs in as administrator log_in_as_administrator(browser, from_a_login_page=True) wait_a_bit() # She clicks on the "Close election" button close_election_button_label = "Close election" close_election_button_css_selector = build_css_selector_to_find_buttons_in_page_content_by_value( close_election_button_label) close_election_button_element = wait_for_element_exists( browser, close_election_button_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) close_election_button_element.click() wait_a_bit() # She clicks on the "Proceed to vote counting" button proceed_button_label = "Proceed to vote counting" proceed_button_css_selector = build_css_selector_to_find_buttons_in_page_content_by_value( proceed_button_label) proceed_button_element = wait_for_element_exists( browser, proceed_button_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) proceed_button_element.click() wait_a_bit() # She remembers the encrypted tally hash encrypted_tally_hash_css_selector = "#encrypted_tally_hash" encrypted_tally_hash_element = wait_for_element_exists_and_has_non_empty_content( browser, encrypted_tally_hash_css_selector) self.encrypted_tally_hash = encrypted_tally_hash_element.get_attribute( 'innerText') # She remembers the link to send to each trustee, so they can tally the election self.closed_election_tally_links_for_trustees = [] for idx, email_address in enumerate(settings.TRUSTEES_EMAIL_ADDRESSES): trustee_link_css_selector = "#main table tr:nth-child(" + str( idx + 3) + ") td:nth-child(3) a" trustee_link_element = wait_for_element_exists_and_has_non_empty_content( browser, trustee_link_css_selector) self.closed_election_tally_links_for_trustees.append( trustee_link_element.get_attribute('href')) # She sends to each trustee an email containing their own link subject = "Link to tally the election" content_format = """\ Dear trustee, The election is now closed. Here's the link to proceed to tally: {link_for_trustee} Here's the instructions: 1. Follow the link. 2. Enter your private decryption key in the first box and click on "generate decryption factors" 3. The second box is now filled with crypto material. Please press the button "submit". Thank you again for your help, -- The election administrator.\ """ for idx, trustee_email_address in enumerate( settings.TRUSTEES_EMAIL_ADDRESSES): custom_content = content_format.format( link_for_trustee=self. closed_election_tally_links_for_trustees[idx]) self.fake_sent_emails_manager.send_email( settings.ADMINISTRATOR_EMAIL_ADDRESS, trustee_email_address, subject, custom_content) # She logs out log_out(browser) # She closes the window browser.quit()
def administrator_starts_tallying_of_election(self): browser = self.browser # Alice goes to the election page election_url = self.election_page_url # Could also be obtained with self.voters_data[self.voters_email_addresses[0]]["election_page_url"] browser.get(election_url) wait_a_bit() # She clicks on the "Administer this election" link administration_link_label = "Administer this election" administration_link_element = wait_for_an_element_with_partial_link_text_exists(browser, administration_link_label, settings.EXPLICIT_WAIT_TIMEOUT) administration_link_element.click() # She logs in as administrator log_in_as_administrator(browser, from_a_login_page=True) wait_a_bit() # She clicks on the "Close election" button close_election_button_label = "Close election" close_election_button_css_selector = build_css_selector_to_find_buttons_in_page_content_by_value(close_election_button_label) close_election_button_element = wait_for_element_exists(browser, close_election_button_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) close_election_button_element.click() wait_a_bit() # She clicks on the "Proceed to vote counting" button proceed_button_label = "Proceed to vote counting" proceed_button_css_selector = build_css_selector_to_find_buttons_in_page_content_by_value(proceed_button_label) proceed_button_element = wait_for_element_exists(browser, proceed_button_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) proceed_button_element.click() wait_a_bit() # She remembers the encrypted tally hash encrypted_tally_hash_css_selector = "#encrypted_tally_hash" encrypted_tally_hash_element = wait_for_element_exists_and_has_non_empty_content(browser, encrypted_tally_hash_css_selector) self.encrypted_tally_hash = encrypted_tally_hash_element.get_attribute('innerText') # She remembers the link to send to each trustee, so they can tally the election self.closed_election_tally_links_for_trustees = [] for idx, email_address in enumerate(settings.TRUSTEES_EMAIL_ADDRESSES): trustee_link_css_selector = "#main table tr:nth-child(" + str(idx + 3) + ") td:nth-child(3) a" trustee_link_element = wait_for_element_exists_and_has_non_empty_content(browser, trustee_link_css_selector) self.closed_election_tally_links_for_trustees.append(trustee_link_element.get_attribute('href')) # She sends to each trustee an email containing their own link subject = "Link to tally the election" content_format = """\ Dear trustee, The election is now closed. Here's the link to proceed to tally: {link_for_trustee} Here's the instructions: 1. Follow the link. 2. Enter your private decryption key in the first box and click on "generate decryption factors" 3. The second box is now filled with crypto material. Please press the button "submit". Thank you again for your help, -- The election administrator.\ """ for idx, trustee_email_address in enumerate(settings.TRUSTEES_EMAIL_ADDRESSES): custom_content = content_format.format(link_for_trustee=self.closed_election_tally_links_for_trustees[idx]) self.fake_sent_emails_manager.send_email(settings.ADMINISTRATOR_EMAIL_ADDRESS, trustee_email_address, subject, custom_content) # She logs out log_out(browser) # She closes the window browser.quit()
def trustees_do_initialization_step_1_of_3(self): # Trustees initialization step 1/3: Trustees generate election private keys. Each of the `T` (aka `NUMBER_OF_TRUSTEES`) trustees will do the following process: for idx, trustee_email_address in enumerate( settings.TRUSTEES_EMAIL_ADDRESSES): # Trustee opens link that has been sent to him by election administrator link_for_this_trustee = self.links_for_trustees[ idx] # TODO: Decide either not send trustee email at all or read trustee link from email content self.browser = initialize_browser_for_scenario_2() browser = self.browser browser.get(link_for_this_trustee) wait_a_bit() # He checks that the page content shows the same election URL as the one the administrator saw election_url_css_selector = "#main ul li" election_url_element = wait_for_element_exists_and_has_non_empty_content( browser, election_url_css_selector) election_url_content = election_url_element.get_attribute( 'innerText').strip() assert election_url_content == self.election_page_url # He clicks on the "Generate private key" button generate_button_css_selector = "#interactivity button" generate_button_expected_label = "Generate private key" generate_button_element = wait_for_element_exists_and_contains_expected_text( browser, generate_button_css_selector, generate_button_expected_label) generate_button_element.click() wait_a_bit() # He clicks on the "private key" link, to download the private key (file is saved by default as `private_key.txt`) link_css_ids = ["private_key"] link_expected_labels = ["private key"] if trustee_email_address not in self.downloaded_files_paths_per_trustee: self.downloaded_files_paths_per_trustee[ trustee_email_address] = dict() for idx2, link_css_id in enumerate(link_css_ids): link_target_filename = str(uuid4()) set_element_attribute(browser, link_css_id, 'download', link_target_filename) link_expected_label = link_expected_labels[idx2] link_element = wait_for_an_element_with_partial_link_text_exists( browser, link_expected_label) assert link_element.get_attribute('id') == link_css_id link_element.click() file_absolute_path = os.path.join( settings.BROWSER_DOWNLOAD_FOLDER, link_target_filename) # We save the filename in a class instance property, so that we can import the file afterwards (during partial decryption step) self.downloaded_files_paths_per_trustee[trustee_email_address][ link_expected_labels[idx2]] = file_absolute_path self.remember_temporary_file_to_remove_after_test( file_absolute_path) wait_a_bit() # He clicks on the "Submit" button submit_button_expected_label = "Submit" submit_button_css_selector = "#main input[type=submit][value='" + submit_button_expected_label + "']" submit_button_element = wait_for_element_exists( browser, submit_button_css_selector) submit_button_element.click() wait_a_bit() # He checks that the next page shows the expected confirmation sentence (If trustee was the last one in the list, he checks that page contains text "Now, all the certificates of the trustees have been generated. Proceed to generate your share of the decryption key.", else he checks for sentence "Waiting for the other trustees... Reload the page to check progress.") if idx == settings.NUMBER_OF_TRUSTEES - 1: expected_confirmation_label = "Now, all the certificates of the trustees have been generated. Proceed to generate your share of the decryption key." else: expected_confirmation_label = "Waiting for the other trustees... Reload the page to check progress." expected_confirmation_css_selector = "#main" wait_for_element_exists_and_contains_expected_text( browser, expected_confirmation_css_selector, expected_confirmation_label) wait_a_bit() # He closes the window browser.quit() # Administrator logs in, and selects the election by clicking on its link self.browser = initialize_browser_for_scenario_2() browser = self.browser log_in_as_administrator(browser) browser.get(self.draft_election_administration_page_url) wait_a_bit() # In the trustees section, she clicks on the "here" link setup_election_key_link_label = "here" setup_election_key_link_element = wait_for_an_element_with_partial_link_text_exists( browser, setup_election_key_link_label) setup_election_key_link_element.click() wait_a_bit() # If current trustee is the last one, she checks that in the table, the "STATE" column is now "2a" on every row. Else, she checks that in the table on the current trustee row, the "STATE" column is now "1b" (instead of "1a") if idx == settings.NUMBER_OF_TRUSTEES - 1: expected_value = "2a" verify_all_trustee_states_in_table(browser, expected_value) else: expected_value = "1b" verify_trustee_state_in_table(browser, idx, expected_value) wait_a_bit() # She closes the window browser.quit()
def administrator_invites_trustees(self): self.browser = initialize_browser_for_scenario_2() browser = self.browser log_in_as_administrator(browser) browser.get(self.draft_election_administration_page_url) wait_a_bit() # In the trustees section, she clicks on the "here" link # TODO: use a better selector: edit Belenios page to use an ID in this DOM element setup_election_key_link_label = "here" setup_election_key_link_element = wait_for_an_element_with_partial_link_text_exists(browser, setup_election_key_link_label) setup_election_key_link_element.click() wait_a_bit() # She adds two trustees (their email address), and remembers the link she will send to each trustee self.links_for_trustees = [] email_address_field_css_selector = "#main form input[type=text]" submit_button_css_selector = "#main form input[type=submit][value=Add]" for idx, email_address in enumerate(settings.TRUSTEES_EMAIL_ADDRESSES): email_address_field_element = wait_for_element_exists(browser, email_address_field_css_selector) email_address_field_element.clear() email_address_field_element.send_keys(email_address) submit_button_element = wait_for_element_exists(browser, submit_button_css_selector) submit_button_element.click() trustee_link_css_selector = "#main table tr:nth-child(" + str(idx + 3) + ") td:nth-child(3) a" trustee_link_element = wait_for_element_exists_and_has_non_empty_content(browser, trustee_link_css_selector) self.links_for_trustees.append(trustee_link_element.get_attribute('href')) wait_a_bit() # She sends to each trustee an email containing their own link subject = "Link to generate the decryption key" content_format = """\ Dear trustee, You will find below the link to generate your private decryption key, used to tally the election. {link_for_trustee} Here's the instructions: 1. click on the link 2. click on "generate a new key pair" 3. your private key will appear in another window or tab. Make sure you SAVE IT properly otherwise it will not possible to tally and the election will be canceled. 4. in the first window, click on "submit" to send the public part of your key, used encrypt the votes. For verification purposes, you should save this part (that starts with "pok" "challenge"), for example sending yourself an email. Regarding your private key, it is crucial you save it (otherwise the election will be canceled) and store it securely (if your private key is known together with the private keys of the other trustees, then vote privacy is no longer guaranteed). We suggest two options: 1. you may store the key on a USB stick and store it in a safe. 2. Or you may simply print it and store it in a safe. Of course, more cryptographic solutions are welcome as well. Thank you for your help, -- The election administrator.\ """ for idx, trustee_email_address in enumerate(settings.TRUSTEES_EMAIL_ADDRESSES): custom_content = content_format.format(link_for_trustee=self.links_for_trustees[idx]) self.fake_sent_emails_manager.send_email(settings.ADMINISTRATOR_EMAIL_ADDRESS, trustee_email_address, subject, custom_content) # Optionnaly, she logs out # log_out(browser) # She closes the window browser.quit()
def administrator_creates_election(self, nh_question=False): # # Setting up a new election (action of the administrator) browser = self.browser # Alice has been given administrator rights on an online voting app called Belenios. She goes # to check out its homepage and logs in log_in_as_administrator(browser) # She starts creation of the election: # - She clicks on the "Prepare a new election" link # (- She keeps default values on the form: Credential management is automatic (not manual), and Authentication method is Password, not CAS) # - She clicks on the "Proceed" button (this redirects to the "Preparation of election" page) # - She changes values of fields name and description of the election # - She clicks on the "Save changes button" (the one that is next to the election description field) administrator_starts_creation_of_election(browser) # She edits election's questions: # - She clicks on the "Edit questions" link, to write her own questions # - She arrives on the Questions page. She checks that the page title is correct # - She removes answer 3 # - She clicks on the "Save changes" button (this redirects to the "Preparation of election" page) administrator_edits_election_questions(browser, nh_question) # She sets election's voters: # - She clicks on the "Edit voters" link, to then type the list of voters # - She types N e-mail addresses (the list of invited voters) # - She clicks on the "Add" button to submit changes # - She clicks on "Go back to election draft" link self.voters_email_addresses = random_email_addresses_generator( settings.NUMBER_OF_INVITED_VOTERS) administrator_sets_election_voters(browser, self.voters_email_addresses) # She clicks on button "Generate on server" generate_on_server_button_label = "Generate on server" generate_on_server_button_css_selector = build_css_selector_to_find_buttons_in_page_content_by_value( generate_on_server_button_label) generate_on_server_button_element = wait_for_element_exists( browser, generate_on_server_button_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) generate_on_server_button_element.click() wait_a_bit() # (Server sends emails to voters.) She checks that server does not show any error that would happen when trying to send these emails (this can happen if sendmail is not configured) confirmation_sentence_expected_text = "Credentials have been generated and mailed!" confirmation_sentence_css_selector = "#main p" wait_for_element_exists_and_contains_expected_text( browser, confirmation_sentence_css_selector, confirmation_sentence_expected_text, settings.EXPLICIT_WAIT_TIMEOUT) # Now we do a sanity check that server has really tried to send emails. For this, we look for email addresses in the temporary file where our fake sendmail executable redirects its inputs to. """ An email sent by Belenios (using sendmail or using the fake sendmail) to a voter looks like this: Content-type: text/plain; charset="UTF-8" Content-transfer-encoding: quoted-printable From: Belenios public server <*****@*****.**> To: "*****@*****.**" <*****@*****.**> Subject: Your credential for election My test election for Scenario 1 MIME-Version: 1.0 X-Mailer: OcamlNet (ocamlnet.sourceforge.net) Date: Wed, 31 Oct 2018 15:22:27 +0100 You are listed as a voter for the election My test election for Scenario 1 You will find below your credential. To cast a vote, you will also need a password, sent in a separate email. Be careful, passwords and credentials look similar but play different roles. You will be asked to enter your credential before entering the voting booth. Login and passwords are required once your ballot is ready to be cast. Credential: yQVDQaKSAQVjdZq Page of the election: http://localhost:8001/elections/AFFNDEPnpy21bw/ Note that you are allowed to vote several times. Only the last vote counts. ---------- Vous =C3=AAtes enregistr=C3=A9(e) en tant qu=27=C3=A9lecteur(trice) pour=20= l=27=C3=A9lection My test election for Scenario 1 Veuillez trouver ci-dessous votre code de vote. Pour soumettre un bulletin, vous aurez =C3=A9galement besoin d=27un mot de passe, envoy=C3=A9= dans un e-mail s=C3=A9par=C3=A9. Soyez attentif(ve), le mot de passe et le cod= e de vote se ressemblent mais jouent des r=C3=B4les diff=C3=A9rents. Le syst=C3= =A8me vous demandera votre code de vote d=C3=A8s l=27entr=C3=A9e dans l=27isoloir=20= virtuel. Le nom d=27utilisateur et le mot de passe sont n=C3=A9cessaires lorsque votr= e bulletin est pr=C3=AAt =C3=A0 =C3=AAtre soumis. Code de vote=C2=A0: yQVDQaKSAQVjdZq Page de l=27=C3=A9lection=C2=A0: http://localhost:8001/elections/AFFNDEPn= py21bw/ Notez que vous pouvez voter plusieurs fois. Seul le dernier vote est pris en compte. --=20 """ email_address_to_look_for = self.voters_email_addresses[0] text_to_look_for = 'To: "' + email_address_to_look_for + '"' email_address_found = self.fake_sent_emails_manager.find_in_sent_emails( text_to_look_for) assert email_address_found, "Text '" + email_address_to_look_for + "' not found in fake sendmail log file " + self.fake_sent_emails_manager.log_file_path # She clicks on the "Proceed" link proceed_link_css_selector = "#generic_proceed_link" proceed_link_element = wait_for_element_exists( browser, proceed_link_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) proceed_link_element.click() wait_a_bit() # In "Authentication" section, she clicks on the "Generate and mail missing passwords" button generate_and_mail_missing_passwords_button_label = "Generate and mail missing passwords" generate_and_mail_missing_passwords_button_element = wait_for_element_exists( browser, build_css_selector_to_find_buttons_in_page_content_by_value( generate_and_mail_missing_passwords_button_label), settings.EXPLICIT_WAIT_TIMEOUT) generate_and_mail_missing_passwords_button_element.click() wait_a_bit() # She checks that the page contains expected confirmation text, instead of an error (TODO: explain in which case an error can happen, and check that it does not show) confirmation_sentence_expected_text = "Passwords have been generated and mailed!" confirmation_sentence_css_selector = "#main p" wait_for_element_exists_and_contains_expected_text( browser, confirmation_sentence_css_selector, confirmation_sentence_expected_text, settings.EXPLICIT_WAIT_TIMEOUT) # She clicks on the "Proceed" link (this redirects to the "Preparation of election" page) proceed_link_css_selector = "#generic_proceed_link" proceed_link_element = wait_for_element_exists( browser, proceed_link_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) proceed_link_element.click() wait_a_bit() self.election_page_url = administrator_validates_creation_of_election( browser) console_log("election_page_url:", self.election_page_url) self.election_id = election_page_url_to_election_id( self.election_page_url) console_log("election_id:", self.election_id) log_out(browser, self.election_id)
def administrator_regenerates_passwords_for_some_voters(self): # Alice has been contacted by some voters who say they lost their password. She wants to re-generate their passwords and have the platform send them by email. For this, she logs in as administrator. browser = self.browser log_in_as_administrator(browser) # She remembers the list of voters who contacted her and said they lost their password. For this, we pick randomly NUMBER_OF_REGENERATED_PASSWORD_VOTERS voters from all the voters. self.voters_email_addresses_who_have_lost_their_password = random.sample(self.voters_email_addresses, settings.NUMBER_OF_REGENERATED_PASSWORD_VOTERS) # She selects the election that she wants to edit browser = self.browser election_to_edit_css_selector = "#election_admin_" + str(self.election_id) election_to_edit_elements = wait_for_elements_exist(browser, election_to_edit_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) assert len(election_to_edit_elements) > 0 election_to_edit_elements[0].click() wait_a_bit() # She arrives to the election administration page. For each voter of the NUMBER_OF_REGENERATED_PASSWORD_VOTERS selected voters: for email_address in self.voters_email_addresses_who_have_lost_their_password: # She clicks on the "Regenerate and mail a password" link regenerate_and_mail_a_password_link_css_selector = "#election_regenpwd" regenerate_and_mail_a_password_link_element = wait_for_element_exists(browser, regenerate_and_mail_a_password_link_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) regenerate_and_mail_a_password_link_element.click() wait_a_bit() # She types the e-mail address of the voter in the "Username" field username_field_css_selector = "#main input[type=text]" username_field_element = wait_for_element_exists(browser, username_field_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) username_field_element.send_keys(email_address) wait_a_bit() # She clicks on the "Submit" button submit_button_label = "Submit" submit_button_element = find_button_in_page_content_by_value(browser, submit_button_label) submit_button_element.click() wait_a_bit() # She checks that the page shows a confirmation message similar to "A new password has been mailed to [email protected]" confirmation_sentence_expected_text = "A new password has been mailed to" confirmation_sentence_css_selector = "#main p" wait_for_element_exists_and_contains_expected_text(browser, confirmation_sentence_css_selector, confirmation_sentence_expected_text, settings.EXPLICIT_WAIT_TIMEOUT) # She clicks on the "Proceed" link proceed_link_css_selector = "#generic_proceed_link" proceed_link_element = wait_for_element_exists(browser, proceed_link_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) proceed_link_element.click() wait_a_bit() # She arrives back to the election administration page """ Now we do a sanity check that server has really tried to send these emails. For this, we look for email addresses in the temporary file where our fake sendmail executable redirects its inputs to. There should be 3 occurences of "To : xxx@xxx" for users who have lost their password, with respective subjects: - "Your credential for election My test election for Scenario 1" - "Your password for election My test election for Scenario 1" - "Your password for election My test election for Scenario 1" And there should be only 2 occurences for other users, with respective subjects: - "Your credential for election My test election for Scenario 1" - "Your password for election My test election for Scenario 1" """ for email_address in self.voters_email_addresses_who_have_lost_their_password: text_to_look_for = 'To: "' + email_address + '"' assert self.fake_sent_emails_manager.count_occurences_in_sent_emails(text_to_look_for) is 3 voters_email_addresses_who_have_not_lost_their_password = set(self.voters_email_addresses) - set(self.voters_email_addresses_who_have_lost_their_password) for email_address in voters_email_addresses_who_have_not_lost_their_password: text_to_look_for = 'To: "' + email_address + '"' assert self.fake_sent_emails_manager.count_occurences_in_sent_emails(text_to_look_for) is 2 log_out(browser)
def administrator_starts_tallying_of_election(self, with_threshold=None): browser = self.browser # Alice goes to the election page election_url = self.election_page_url # Could also be obtained with self.voters_data[self.voters_email_addresses[0]]["election_page_url"] browser.get(election_url) wait_a_bit() # She clicks on "en" language english_language_link_expected_label = "en" english_language_link_element = wait_for_an_element_with_link_text_exists( browser, english_language_link_expected_label, settings.EXPLICIT_WAIT_TIMEOUT) english_language_link_element.click() wait_a_bit() # She clicks on the "Administer this election" link administration_link_label = "Administer this election" administration_link_element = wait_for_an_element_with_partial_link_text_exists( browser, administration_link_label, settings.EXPLICIT_WAIT_TIMEOUT) administration_link_element.click() # She logs in as administrator log_in_as_administrator(browser, from_a_login_page=True) wait_a_bit() # She clicks on the "Close election" button close_election_button_label = "Close election" close_election_button_css_selector = build_css_selector_to_find_buttons_in_page_content_by_value( close_election_button_label) close_election_button_element = wait_for_element_exists( browser, close_election_button_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) close_election_button_element.click() wait_a_bit() # She clicks on the "Proceed to vote counting" button proceed_button_label = "Proceed to vote counting" proceed_button_css_selector = build_css_selector_to_find_buttons_in_page_content_by_value( proceed_button_label) proceed_button_element = wait_for_element_exists( browser, proceed_button_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) proceed_button_element.click() wait_a_bit() if with_threshold is not None: # She checks the presence of text "We are now waiting for trustees... At least ${U} trustee(s) must act." expected_confirmation_label = "We are now waiting for trustees... At least " + str( with_threshold) + " trustee(s) must act." expected_confirmation_css_selector = "#main" wait_for_element_exists_and_contains_expected_text( browser, expected_confirmation_css_selector, expected_confirmation_label) # She checks that in the table on every content row, the "DONE?" column is "No" elements_css_selector = "#main table tr td:nth-of-type(4)" attribute_name = "innerText" attribute_value = "No" verify_all_elements_have_attribute_value( browser, elements_css_selector, attribute_name, attribute_value, extractor=(lambda x: x[1:])) # She remembers the link to send to each trustee, so they can tally the election row_padding = 3 self.closed_election_tally_links_for_trustees = [] for idx, email_address in enumerate(settings.TRUSTEES_EMAIL_ADDRESSES): trustee_link_css_selector = "#main table tr:nth-of-type(" + str( idx + row_padding ) + ") td:nth-of-type(3) a" # First row consists in column titles. Second row is for server. trustee_link_element = wait_for_element_exists_and_has_non_empty_content( browser, trustee_link_css_selector) self.closed_election_tally_links_for_trustees.append( trustee_link_element.get_attribute('href')) # She sends to each trustee an email containing their own link subject = "Link to tally the election" content_format = """\ Dear trustee, The election is now closed. Here's the link to proceed to tally: {link_for_trustee} Here's the instructions: 1. Follow the link. 2. Enter your private decryption key in the first box and click on "generate decryption factors" 3. The second box is now filled with crypto material. Please press the button "submit". Thank you again for your help, -- The election administrator.\ """ for idx, trustee_email_address in enumerate( settings.TRUSTEES_EMAIL_ADDRESSES): custom_content = content_format.format( link_for_trustee=self. closed_election_tally_links_for_trustees[idx]) self.fake_sent_emails_manager.send_email( settings.ADMINISTRATOR_EMAIL_ADDRESS, trustee_email_address, subject, custom_content) # She logs out log_out(browser) # She closes the window browser.quit()
def trustees_do_initialization_step_2_of_3(self): # Trustees initialization step 2/3: Trustees generate their share of the decryption key. Each of the `T` (aka `NUMBER_OF_TRUSTEES`) trustees will do the following process: for idx, trustee_email_address in enumerate( settings.TRUSTEES_EMAIL_ADDRESSES): # Trustee opens link that has been sent to him by election administrator link_for_this_trustee = self.links_for_trustees[ idx] # TODO: Decide either not send trustee email at all or read trustee link from email content self.browser = initialize_browser_for_scenario_2() browser = self.browser browser.get(link_for_this_trustee) wait_a_bit() # He checks that the page content shows the same election URL as the one the administrator saw election_url_css_selector = "#main ul li" election_url_element = wait_for_element_exists_and_has_non_empty_content( browser, election_url_css_selector) election_url_content = election_url_element.get_attribute( 'innerText').strip() assert election_url_content == self.election_page_url # He checks the presence of text "Now, all the certificates of the trustees have been generated. Proceed to generate your share of the decryption key." expected_confirmation_label = "Now, all the certificates of the trustees have been generated. Proceed to generate your share of the decryption key." expected_confirmation_css_selector = "#main" wait_for_element_exists_and_contains_expected_text( browser, expected_confirmation_css_selector, expected_confirmation_label) # In field next to "Enter your private key:", he types the content of the `private_key.txt` file he downloaded private_key_storage_label = "private key" private_key_file = self.downloaded_files_paths_per_trustee[ trustee_email_address][private_key_storage_label] private_key_css_selector = "#compute_private_key" private_key_element = wait_for_element_exists( browser, private_key_css_selector) private_key_element.clear() with open(private_key_file) as myfile: private_key_element.send_keys(myfile.read()) wait_a_bit() # He clicks on the "Proceed" button proceed_button_css_selector = "#compute_button" proceed_button_element = wait_for_element_exists( browser, proceed_button_css_selector) proceed_button_element.click() # He waits until the text field next to "Data:" contains text, and clicks on the "Submit" button data_field_css_selector = "#compute_data" data_field_expected_non_empty_attribute = "value" wait_for_element_exists_and_has_non_empty_attribute( browser, data_field_css_selector, data_field_expected_non_empty_attribute) submit_button_expected_label = "Submit" submit_button_css_selector = "#compute_form input[type=submit][value=" + submit_button_expected_label + "]" submit_button_element = wait_for_element_exists( browser, submit_button_css_selector) submit_button_element.click() wait_a_bit() # If he is not the last trustee in the list, he checks that the next page contains text "Waiting for the other trustees... Reload the page to check progress.". Else, he checks that the next page contains text "Now, all the trustees have generated their secret shares. Proceed to the final checks so that the election can be validated." if idx == settings.NUMBER_OF_TRUSTEES - 1: expected_confirmation_label = "Now, all the trustees have generated their secret shares. Proceed to the final checks so that the election can be validated." else: expected_confirmation_label = "Waiting for the other trustees... Reload the page to check progress." expected_confirmation_css_selector = "#main" wait_for_element_exists_and_contains_expected_text( browser, expected_confirmation_css_selector, expected_confirmation_label) wait_a_bit() # He closes the window browser.quit() # Administrator logs in, and selects the election by clicking on its link self.browser = initialize_browser_for_scenario_2() browser = self.browser log_in_as_administrator(browser) browser.get(self.draft_election_administration_page_url) wait_a_bit() # In the trustees section, she clicks on the "here" link setup_election_key_link_label = "here" setup_election_key_link_element = wait_for_an_element_with_partial_link_text_exists( browser, setup_election_key_link_label) setup_election_key_link_element.click() wait_a_bit() # If current trustee is the last one, she checks that in the table, the "STATE" column is now "3a" on every row. Else, she checks that in the table on the current trustee row, the "STATE" column is now "2b" (instead of "2a") if idx == settings.NUMBER_OF_TRUSTEES - 1: expected_value = "3a" verify_all_trustee_states_in_table(browser, expected_value) else: expected_value = "2b" verify_trustee_state_in_table(browser, idx, expected_value) wait_a_bit() # She closes the window browser.quit()
def trustees_do_initialization_step_3_of_3(self): # Trustees initialization step 3/3: Trustees do the final checks so that the election can be validated. Each of the `T` (aka `NUMBER_OF_TRUSTEES`) trustees will do the following process: for idx, trustee_email_address in enumerate( settings.TRUSTEES_EMAIL_ADDRESSES): # Trustee opens link that has been sent to him by election administrator link_for_this_trustee = self.links_for_trustees[ idx] # TODO: Decide either not send trustee email at all or read trustee link from email content self.browser = initialize_browser_for_scenario_2() browser = self.browser browser.get(link_for_this_trustee) wait_a_bit() # He checks that the page content shows the same election URL as the one the administrator saw election_url_css_selector = "#main ul li" election_url_element = wait_for_element_exists_and_has_non_empty_content( browser, election_url_css_selector) election_url_content = election_url_element.get_attribute( 'innerText').strip() assert election_url_content == self.election_page_url # He checks the presence of text "Step 3/3" expected_confirmation_label = "Step 3/3" expected_confirmation_css_selector = "#main" wait_for_element_exists_and_contains_expected_text( browser, expected_confirmation_css_selector, expected_confirmation_label) # In field next to "Enter your private key:", he types the content of the `private_key.txt` file he downloaded private_key_storage_label = "private key" private_key_file = self.downloaded_files_paths_per_trustee[ trustee_email_address][private_key_storage_label] private_key_css_selector = "#compute_private_key" private_key_element = wait_for_element_exists( browser, private_key_css_selector) private_key_element.clear() with open(private_key_file) as myfile: private_key_element.send_keys(myfile.read()) wait_a_bit() # He clicks on the "Proceed" button proceed_button_css_selector = "#compute_button" proceed_button_element = wait_for_element_exists( browser, proceed_button_css_selector) proceed_button_element.click() # He waits until the text field next to "Data:" contains text, and clicks on the "Submit" button data_field_css_selector = "#compute_data" data_field_expected_non_empty_attribute = "value" wait_for_element_exists_and_has_non_empty_attribute( browser, data_field_css_selector, data_field_expected_non_empty_attribute) submit_button_expected_label = "Submit" submit_button_css_selector = "#compute_form input[type=submit][value=" + submit_button_expected_label + "]" submit_button_element = wait_for_element_exists( browser, submit_button_css_selector) submit_button_element.click() wait_a_bit() # He checks that the next page contains text "Your job in the key establishment protocol is done!" expected_confirmation_label = "Your job in the key establishment protocol is done!" expected_confirmation_css_selector = "#main" wait_for_element_exists_and_contains_expected_text( browser, expected_confirmation_css_selector, expected_confirmation_label) wait_a_bit() # He clicks on the "public key" link and downloads the file (file is saved by default as `public_key.json`) link_css_ids = ["public_key"] link_expected_labels = ["public key"] if trustee_email_address not in self.downloaded_files_paths_per_trustee: self.downloaded_files_paths_per_trustee[ trustee_email_address] = dict() for idx2, link_css_id in enumerate(link_css_ids): link_target_filename = str(uuid4()) set_element_attribute(browser, link_css_id, 'download', link_target_filename) link_expected_label = link_expected_labels[idx2] link_element = wait_for_an_element_with_partial_link_text_exists( browser, link_expected_label) assert link_element.get_attribute('id') == link_css_id link_element.click() file_absolute_path = os.path.join( settings.BROWSER_DOWNLOAD_FOLDER, link_target_filename) # We save the filename in a class instance property, so that we can import the file afterwards (during partial decryption step) self.downloaded_files_paths_per_trustee[trustee_email_address][ link_expected_labels[idx2]] = file_absolute_path self.remember_temporary_file_to_remove_after_test( file_absolute_path) wait_a_bit() # He closes the window browser.quit() # Administrator logs in, and selects the election by clicking on its link self.browser = initialize_browser_for_scenario_2() browser = self.browser log_in_as_administrator(browser) browser.get(self.draft_election_administration_page_url) wait_a_bit() # In the trustees section, she clicks on the "here" link setup_election_key_link_label = "here" setup_election_key_link_element = wait_for_an_element_with_partial_link_text_exists( browser, setup_election_key_link_label) setup_election_key_link_element.click() wait_a_bit() # If current trustee is the last one, she checks that in the table, the "STATE" column is now "done" on every row. Else, she checks that in the table on the current trustee row, the "STATE" column is now "3b" (instead of "3a") if idx == settings.NUMBER_OF_TRUSTEES - 1: expected_value = "done" verify_all_trustee_states_in_table(browser, expected_value) else: expected_value = "3b" verify_trustee_state_in_table(browser, idx, expected_value) wait_a_bit() # She closes the window browser.quit()
def administrator_starts_creation_of_manual_election(self): # # Setting up a new election (action of the administrator) browser = self.browser # Alice has been given administrator rights on an online voting app called Belenios. She goes # to check out its homepage and logs in log_in_as_administrator(browser) # She starts creation of the election: # - She clicks on the "Prepare a new election" link # - She picks the Credential management method: manual # (- She keeps default value for Authentication method: it is Password, not CAS) # - She clicks on the "Proceed" button (this redirects to the "Preparation of election" page) # - In the "Name and description of the election" section, she changes values of fields name and description of the election # - She clicks on the "Save changes button" (the one that is next to the election description field) # - In "Contact" section, she changes the value of "contact" field # - She clicks on the "Save changes" button (the one that is in the "Contact" section) administrator_starts_creation_of_election(browser, True) # She remembers the URL of the draft election administration page self.draft_election_administration_page_url = browser.current_url # She edits election's questions: # - She clicks on the "Edit questions" link, to write her own questions # - She arrives on the Questions page. She checks that the page title is correct # - She removes answer 3 # - She clicks on the "Save changes" button (this redirects to the "Preparation of election" page) administrator_edits_election_questions(browser) # She sets election's voters: # - She clicks on the "Edit voters" link, to then type the list of voters # - She types N e-mail addresses (the list of invited voters) # - She clicks on the "Add" button to submit changes # - She clicks on "Return to draft page" link self.voters_email_addresses = random_email_addresses_generator( settings.NUMBER_OF_INVITED_VOTERS) administrator_sets_election_voters(browser, self.voters_email_addresses) # In "Authentication" section, she clicks on the "Generate and mail missing passwords" button generate_and_mail_missing_passwords_button_label = "Generate and mail missing passwords" generate_and_mail_missing_passwords_button_css_selector = "#main input[type=submit][value='" + generate_and_mail_missing_passwords_button_label + "']" generate_and_mail_missing_passwords_button_element = wait_for_element_exists( browser, generate_and_mail_missing_passwords_button_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) generate_and_mail_missing_passwords_button_element.click( ) # FIXME: This click does not get triggered when we have maximized the browser window wait_a_bit() # She checks that the page contains expected confirmation text, instead of an error (TODO: explain in which case an error can happen, and check that it does not show) confirmation_sentence_expected_text = "Passwords have been generated and mailed!" confirmation_sentence_css_selector = "#main p" wait_for_element_exists_and_contains_expected_text( browser, confirmation_sentence_css_selector, confirmation_sentence_expected_text, settings.EXPLICIT_WAIT_TIMEOUT) # She clicks on the "Proceed" link (this redirects to the "Preparation of election" page) proceed_link_expected_label = "Proceed" proceed_link_css_selector = "#main a" proceed_link_element = wait_for_element_exists_and_contains_expected_text( browser, proceed_link_css_selector, proceed_link_expected_label, settings.EXPLICIT_WAIT_TIMEOUT) proceed_link_element.click() wait_a_bit() # In "Credentials" section, she clicks on "Credential management" link credential_management_expected_label = "Credential management" credential_management_link_element = wait_for_an_element_with_partial_link_text_exists( browser, credential_management_expected_label) credential_management_link_element.click() wait_a_bit() # She fills in her public name, then clicks on "Set" credential_authority_css_selector = "#main form input[name=__co_eliom_name]" credential_authority_element = wait_for_element_exists( browser, credential_authority_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) credential_authority_element.clear() credential_authority_element.send_keys("Cecily") credential_authority_set_css_selector = "#main form input[type=submit]" credential_authority_set_element = browser.find_element_by_css_selector( credential_authority_set_css_selector) credential_authority_set_element.click() wait_a_bit() # She clicks on the "Proceed" link proceed_link_expected_label = "Proceed" proceed_link_css_selector = "#main a" proceed_link_element = wait_for_element_exists_and_contains_expected_text( browser, proceed_link_css_selector, proceed_link_expected_label, settings.EXPLICIT_WAIT_TIMEOUT) proceed_link_element.click() wait_a_bit() # She remembers the link displayed link_for_credential_authority_css_selector = "#credential_authority_link" link_for_credential_authority_element = wait_for_element_exists_and_has_non_empty_content( browser, link_for_credential_authority_css_selector) link_label = link_for_credential_authority_element.get_attribute( 'innerText').strip() self.credential_authority_link = link_label # She sends the remembered link to the credential authority by email (actually we don't need to send anything because we will act as the credential authority) # Optionnaly, she logs out # log_out(browser) # She closes the browser window browser.quit()
def administrator_regenerates_passwords_for_some_voters(self): # Alice has been contacted by some voters who say they lost their password. She wants to re-generate their passwords and have the platform send them by email. For this, she logs in as administrator. browser = self.browser log_in_as_administrator(browser) # She remembers the list of voters who contacted her and said they lost their password. For this, we pick randomly NUMBER_OF_REGENERATED_PASSWORD_VOTERS voters from all the voters. self.voters_email_addresses_who_have_lost_their_password = random.sample( self.voters_email_addresses, settings.NUMBER_OF_REGENERATED_PASSWORD_VOTERS) # She selects the election that she wants to edit browser = self.browser election_to_edit_css_selector = "#election_admin_" + str( self.election_id) election_to_edit_elements = wait_for_elements_exist( browser, election_to_edit_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) assert len(election_to_edit_elements) > 0 election_to_edit_elements[0].click() wait_a_bit() # She arrives to the election administration page. For each voter of the NUMBER_OF_REGENERATED_PASSWORD_VOTERS selected voters: for email_address in self.voters_email_addresses_who_have_lost_their_password: # She clicks on the "Regenerate and mail a password" link regenerate_and_mail_a_password_link_css_selector = "#election_regenpwd" regenerate_and_mail_a_password_link_element = wait_for_element_exists( browser, regenerate_and_mail_a_password_link_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) regenerate_and_mail_a_password_link_element.click() wait_a_bit() # She types the e-mail address of the voter in the "Username" field username_field_css_selector = "#main input[type=text]" username_field_element = wait_for_element_exists( browser, username_field_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) username_field_element.send_keys(email_address) wait_a_bit() # She clicks on the "Submit" button submit_button_label = "Submit" submit_button_element = find_button_in_page_content_by_value( browser, submit_button_label) submit_button_element.click() wait_a_bit() # She checks that the page shows a confirmation message similar to "A new password has been mailed to [email protected]" confirmation_sentence_expected_text = "A new password has been mailed to" confirmation_sentence_css_selector = "#main p" wait_for_element_exists_and_contains_expected_text( browser, confirmation_sentence_css_selector, confirmation_sentence_expected_text, settings.EXPLICIT_WAIT_TIMEOUT) # She clicks on the "Proceed" link proceed_link_css_selector = "#generic_proceed_link" proceed_link_element = wait_for_element_exists( browser, proceed_link_css_selector, settings.EXPLICIT_WAIT_TIMEOUT) proceed_link_element.click() wait_a_bit() # She arrives back to the election administration page """ Now we do a sanity check that server has really tried to send these emails. For this, we look for email addresses in the temporary file where our fake sendmail executable redirects its inputs to. There should be 3 occurences of "To : xxx@xxx" for users who have lost their password, with respective subjects: - "Your credential for election My test election for Scenario 1" - "Your password for election My test election for Scenario 1" - "Your password for election My test election for Scenario 1" And there should be only 2 occurences for other users, with respective subjects: - "Your credential for election My test election for Scenario 1" - "Your password for election My test election for Scenario 1" """ for email_address in self.voters_email_addresses_who_have_lost_their_password: text_to_look_for = 'To: "' + email_address + '"' assert self.fake_sent_emails_manager.count_occurences_in_sent_emails( text_to_look_for) is 3 voters_email_addresses_who_have_not_lost_their_password = set( self.voters_email_addresses) - set( self.voters_email_addresses_who_have_lost_their_password) for email_address in voters_email_addresses_who_have_not_lost_their_password: text_to_look_for = 'To: "' + email_address + '"' assert self.fake_sent_emails_manager.count_occurences_in_sent_emails( text_to_look_for) is 2 log_out(browser, self.election_id)