示例#1
0
    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()
示例#2
0
    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()
示例#3
0
        def verify_page_title(browser):
            console_log(
                "#### Analyzing contents of page returned after having clicked on Submit button, expecting title to be either 'Ill-formed ballot' or 'Password login'"
            )
            page_title_css_selector = "#header h1"
            page_title_element = wait_for_element_exists(
                browser, page_title_css_selector)
            page_title_label = page_title_element.get_attribute(
                'innerText'
            )  # Here we sometimes get a stale element. This is why we run this inside a loop with several attempts

            page_content_css_selector = "#main"
            page_content_element = wait_for_element_exists(
                browser, page_content_css_selector)
            page_content_label = page_content_element.get_attribute(
                'innerText')

            console_log("#### Page title was", page_title_label,
                        "and page content was", page_content_label)
            if page_title_label == "Error":
                assert page_content_label == "Ill-formed ballot"
                return page_title_label
            elif page_title_label == "Log in with password":
                return page_title_label
            else:
                # This case happens sometimes, because Selenium has not yet replaced its DOM with the new DOM of the page (maybe because server has not responded yet or page loading is not yet complete).
                # In this situation, data is still: ('Unexpected page content', 'USERNAME:\t\nPASSWORD:\t')
                # Or: ('Unexpected page content', 'My test election for Scenario 1', 'Username:Password:')
                raise Exception("Unexpected page content", page_title_label,
                                page_content_label)
示例#4
0
    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()
示例#5
0
    def submit_prepared_ballot(self, ballot):
        browser = self.browser
        timeout = settings.EXPLICIT_WAIT_TIMEOUT
        console_log("#### Going to election page")
        election_page_url = election_id_to_election_home_page_url(self.election_id)
        browser.get(election_page_url)

        console_log("#### Clicking on 'en' language link")
        election_home_page = ElectionHomePage(browser, timeout)
        election_home_page.click_on_language_link("en")

        console_log("#### Clicking on 'Advanced mode' link")
        election_home_page.click_on_advanced_mode_link()

        console_log("#### Filling ballot field with text representation of ballot and clicking on Submit button")
        field_css_selector = "form[action=submit-ballot] textarea[name=encrypted_vote]"
        field_element = wait_for_element_exists(browser, field_css_selector, timeout)
        field_element.clear()
        field_element.send_keys(ballot)
        field_element.submit()

        wait_a_bit()

        @try_several_times(5)
        def verify_page_title(browser):
            console_log("#### Analyzing contents of page returned after having clicked on Submit button, expecting title to be either 'Ill-formed ballot' or 'Password login'")
            page_title_css_selector = "#header h1"
            page_title_element = wait_for_element_exists(browser, page_title_css_selector)
            page_title_label = page_title_element.get_attribute('innerText') # Here we sometimes get a stale element. This is why we run this inside a loop with several attempts

            page_content_css_selector = "#main"
            page_content_element = wait_for_element_exists(browser, page_content_css_selector)
            page_content_label = page_content_element.get_attribute('innerText')

            console_log("#### Page title was", page_title_label, "and page content was", page_content_label)
            if page_title_label == "Error":
                assert page_content_label == "Ill-formed ballot"
                return page_title_label
            elif page_title_label == "Authenticate with password":
                return page_title_label
            else:
                # This case happens sometimes, because Selenium has not yet replaced its DOM with the new DOM of the page (maybe because server has not responded yet or page loading is not yet complete).
                # In this situation, data is still: ('Unexpected page content', 'USERNAME:\t\nPASSWORD:\t')
                # Or: ('Unexpected page content', 'My test election for Scenario 1', 'Username:Password:')
                raise Exception("Unexpected page content", page_title_label, page_content_label)

        try:
            page_title = verify_page_title(browser)
            assert page_title in ["Error", "Authenticate with password"]
            return page_title
        except Exception as e:
            console_log("Could not locate expected element. Exception received:", e)
            # browser.quit()
            # self.browser = initialize_browser()
            raise e
示例#6
0
 def download_all_sent_emails(self, target_fake_sent_emails_manager=None):
     from urllib.parse import urljoin
     import urllib.request
     if not target_fake_sent_emails_manager:
         import tempfile
         (file_handle, log_file_path) = tempfile.mkstemp(text=True)
         target_fake_sent_emails_manager = FakeSentEmailsManager(log_file_path)
     distant_fake_emails_file_url = urljoin(settings.SERVER_URL, settings.FAKE_SENT_EMAILS_FILE_RELATIVE_URL) # TODO: maybe we should build this URL by picking link value in alert banner on distant server home page
     console_log("distant_fake_emails_file_url:", distant_fake_emails_file_url)
     urllib.request.urlretrieve(distant_fake_emails_file_url, target_fake_sent_emails_manager.log_file_path)
     console_log("#### Distant fake sent emails have been saved in:", target_fake_sent_emails_manager.log_file_path)
     return target_fake_sent_emails_manager
示例#7
0
    def some_voters_cast_their_vote(self, voters):
        """
        :param voters: list of dict. Each element contains information about a voter (their e-mail address, the planned answers to each question they will cast)
        """
        browser = self.browser
        voters_count = len(voters)
        for index, voter in enumerate(voters):
            console_log("#### Current voter casting their vote in current batch: " + str(index + 1) + "/" + str(voters_count))

            self.one_voter_votes(voter)
            wait_a_bit()

            """
            Next screen looks like this:
            Your ballot for My test election for Scenario 1 has been accepted. Your smart ballot tracker is ISXe/rCNCVa9XcVeFgKglbpgo5SoZs4svT6dPbR5b6M. You can check its presence in the {ballot box} anytime during the election. A confirmation e-mail has been sent to you.

            {Go back to election}

            Where {xxx} is a link
            """

            # He clicks on the "ballot box" link
            ballot_box_link_label = "ballot box"
            ballot_box_link_element = wait_for_an_element_with_partial_link_text_exists(browser, ballot_box_link_label, settings.EXPLICIT_WAIT_TIMEOUT)
            ballot_box_link_element.click()

            wait_a_bit()

            # He checks that his smart ballot tracker appears in the list
            all_smart_ballot_trackers_css_selector = "#main ul li a"
            all_smart_ballot_trackers_elements = wait_for_elements_exist(browser, all_smart_ballot_trackers_css_selector, settings.EXPLICIT_WAIT_TIMEOUT)
            assert len(all_smart_ballot_trackers_elements)
            matches = [element for element in all_smart_ballot_trackers_elements if element.get_attribute('innerText') == voter["smart_ballot_tracker"]]
            assert len(matches) is 1


            self.voters_email_addresses_who_have_voted[voter["email_address"]] = True

            # In a following pass, he checks his mailbox to find a new email with confirmation of his vote, and verifies the value of the smart ballot tracker written in this email is the same as the one he noted. This verification is done in a separated pass because of an optimization, so that we only re-read and re-populate the sendmail_fake text file once for all users.

            # He closes the window (there is no log-out link, because user is not logged in: credentials are not remembered)
            # It is not really mandatory for the test to close the window. Re-opening a browser takes much more time, compared to just navigating to another URL. So actually to save execution time, we choose to close the window only sometimes, randomly.
            if random.randint(0, 10) <= 3:
                browser.quit()
                self.browser = initialize_browser()
                browser = self.browser

        # Start another pass, where we re-read and re-populate the sendmail_fake text file once for all users.
        voters = repopulate_vote_confirmations_for_voters_from_sent_emails(self.fake_sent_emails_manager, voters, settings.ELECTION_TITLE)
        for voter in voters:
            # He checks his mailbox to find a new email with confirmation of his vote, and verifies the value of the smart ballot tracker written in this email is the same as the one he noted.
            assert voter["smart_ballot_tracker"] == voter["smart_ballot_tracker_in_vote_confirmation_email"], "Ballot tracker read in vote confirmation email (" + voter["smart_ballot_tracker"] + ") is not the same as the one read on the vote confirmation page (" + voter["smart_ballot_tracker_in_vote_confirmation_email"] + ")"
示例#8
0
    def one_voter_casts_after_the_election_is_closed(self, voter):
        browser = self.browser
        console_log("#### Current voter casting their vote after the election is closed")
        self.one_voter_votes(voter, direct=True)
        wait_a_bit()

        """
        Next screen looks like this:
        Your ballot for Test vote after close is rejected, because the election is closed.

        {Go back to election}

        Where {xxx} is a link
        """

        # He checks that "the election is closed" is present
        wait_for_element_exists_and_contains_expected_text(browser, "#main p", "the election is closed", settings.EXPLICIT_WAIT_TIMEOUT)
示例#9
0
    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?
示例#10
0
    def one_voter_casts_after_the_election_is_closed(self, voter):
        browser = self.browser
        console_log(
            "#### Current voter casting their vote after the election is closed"
        )
        self.one_voter_votes(voter, direct=True)
        wait_a_bit()
        """
        Next screen looks like this:
        Your ballot for Test vote after close is rejected, because the election is closed.

        {Go back to election}

        Where {xxx} is a link
        """

        # He checks that "the election is closed" is present
        wait_for_element_exists_and_contains_expected_text(
            browser, "#main p", "the election is closed",
            settings.EXPLICIT_WAIT_TIMEOUT)
示例#11
0
 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)
示例#12
0
    def credential_authority_sends_locally_generated_credentials_to_server(
            self):
        # Cecily, the Credential Authority, receives the email sent by Alice, and opens the link in it
        self.browser = initialize_browser_for_scenario_2()
        browser = self.browser
        browser.get(self.credential_authority_link)

        wait_a_bit()

        # She remembers what the link to the election will be, so that she will be able to send it to voters by email with their private credential
        # TODO: use a better selector: edit Belenios page to use an ID in this DOM element
        future_election_link_css_selector = "#main ul li"
        future_election_link_element = wait_for_element_exists_and_has_non_empty_content(
            browser, future_election_link_css_selector)
        self.election_page_url = future_election_link_element.get_attribute(
            'innerText').strip()

        # She executes local (not server's) CLI belenios-tool to generate a number of credentials corresponding to the number of voters. This creates some local files.
        console_log("#### Starting step: belenios_tool_generate_credentials")
        self.credential_file_id = belenios_tool_generate_credentials(
            self.election_id)
        console_log("#### Step complete: belenios_tool_generate_credentials")
        console_log("#### Credential file id:", self.credential_file_id)

        # She uploads the file that corresponds to the public part of the genereated credentials. For this, she clicks on the 'Browse' button and selects the file with `.pubcreds` extension
        browse_button_css_selector = "form input[name=public_creds][type=file]"
        browse_button_element = wait_for_element_exists(
            browser, browse_button_css_selector)
        path_of_file_to_upload = self.credential_file_id + ".pubcreds"
        browse_button_element.clear()
        browse_button_element.send_keys(path_of_file_to_upload)

        # She clicks on 'Submit' button
        submit_button_css_selector = "form input[type=submit][value=Submit]"
        submit_button_element = wait_for_element_exists(
            browser, submit_button_css_selector)
        submit_button_element.click()

        wait_a_bit()

        # She checks that page contains text "Credentials have been received and checked!"
        page_content_css_selector = "#main div"
        page_content_expected_content = "Credentials have been received and checked!"
        wait_for_element_exists_and_contains_expected_text(
            browser, page_content_css_selector, page_content_expected_content,
            settings.EXPLICIT_WAIT_TIMEOUT)

        # We act like if credential authority had also sent each private credential to a different voter. For ease of parsing, we write these emails into the same text file as Belenios server's.
        self.credential_authority_sends_credentials_to_voters_from_credentials_file(
            self.credential_file_id + ".privcreds",
            self.voters_email_addresses)

        # She closes the browser window
        browser.quit()
示例#13
0
 def test_vote_with_prepared_ballots(self):
     # Generate ballot for each voter
     console_log("### Starting step: cast_all_votes_from_csv")
     self.cast_all_votes_from_csv()
     console_log("### Step complete: cast_all_votes_from_csv")
示例#14
0
    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 = "Log in 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
示例#15
0
            page_title = verify_page_title(browser)
            assert page_title in ["Error", "Log in with password"]
            return page_title
        except Exception as e:
            console_log(
                "Could not locate expected element. Exception received:", e)
            # browser.quit()
            # self.browser = initialize_browser()
            raise e


if __name__ == "__main__":
    random_seed = os.getenv('RANDOM_SEED', None)
    if not random_seed:
        random_seed = random.randrange(sys.maxsize)
    console_log("Python random seed being used:", random_seed)
    random.seed(random_seed)

    settings.SERVER_URL = os.getenv('SERVER_URL', settings.SERVER_URL)
    if os.getenv('START_SERVER', None):
        settings.START_SERVER = bool(strtobool(os.getenv('START_SERVER')))

    if os.getenv('USE_HEADLESS_BROWSER', None):
        settings.USE_HEADLESS_BROWSER = bool(
            strtobool(os.getenv('USE_HEADLESS_BROWSER')))

    settings.ELECTION_ID = os.getenv('ELECTION_ID', None) or None
    settings.VOTER_USERNAME = os.getenv('VOTER_USERNAME', None) or None
    settings.VOTER_PASSWORD = os.getenv('VOTER_PASSWORD', None) or None
    settings.VOTER_CREDENTIAL = os.getenv('VOTER_CREDENTIAL', None) or None
    settings.FAKE_SENT_EMAILS_FILE_RELATIVE_URL = os.getenv(
示例#16
0
    def test_scenario_4_manual_vote_with_threshold(self):
        console_log(
            "### Running test method BeleniosTestElectionScenario4::test_scenario_4_manual_vote_with_threshold()"
        )
        console_log(
            "### Starting step: administrator_starts_creation_of_manual_election"
        )
        self.administrator_starts_creation_of_manual_election()
        console_log(
            "### Step complete: administrator_starts_creation_of_manual_election"
        )

        console_log(
            "### Starting step: credential_authority_sends_credentials_to_voters"
        )
        self.credential_authority_sends_credentials_to_voters()
        console_log(
            "### Step complete: credential_authority_sends_credentials_to_voters"
        )

        console_log(
            "### Starting step: administrator_invites_trustees_and_sets_threshold"
        )
        self.administrator_invites_trustees_and_sets_threshold()
        console_log(
            "### Step complete: administrator_invites_trustees_and_sets_threshold"
        )

        console_log(
            "### Starting step: trustees_do_initialization_step_1_of_3")
        self.trustees_do_initialization_step_1_of_3()
        console_log(
            "### Step complete: trustees_do_initialization_step_1_of_3")

        console_log(
            "### Starting step: trustees_do_initialization_step_2_of_3")
        self.trustees_do_initialization_step_2_of_3()
        console_log(
            "### Step complete: trustees_do_initialization_step_2_of_3")

        console_log(
            "### Starting step: trustees_do_initialization_step_3_of_3")
        self.trustees_do_initialization_step_3_of_3()
        console_log(
            "### Step complete: trustees_do_initialization_step_3_of_3")

        console_log(
            "### Starting step: administrator_completes_creation_of_election")
        self.administrator_completes_creation_of_election()
        console_log(
            "### Step complete: administrator_completes_creation_of_election")

        console_log(
            "### Starting step: verify_election_consistency using `belenios_tool verify` (0)"
        )
        verify_election_consistency(self.election_id)
        console_log(
            "### Step complete: verify_election_consistency using `belenios_tool verify` (0)"
        )

        console_log("### Starting step: all_voters_vote_in_sequences")
        self.all_voters_vote_in_sequences()
        console_log("### Step complete: all_voters_vote_in_sequences")

        console_log(
            "### Starting step: verify_election_consistency using `belenios_tool verify` (1)"
        )
        verify_election_consistency(self.election_id)
        console_log(
            "### Step complete: verify_election_consistency using `belenios_tool verify` (1)"
        )

        console_log("### Starting step: create_election_data_snapshot (0)")
        snapshot_folder = create_election_data_snapshot(self.election_id)
        console_log("### Step complete: create_election_data_snapshot (0)")

        try:
            console_log("### Starting step: some_voters_revote")
            self.some_voters_revote()
            console_log("### Step complete: some_voters_revote")

            console_log(
                "### Starting step: verify_election_consistency using `belenios_tool verify-diff` (0)"
            )
            verify_election_consistency(self.election_id, snapshot_folder)
        finally:
            delete_election_data_snapshot(snapshot_folder)
        console_log(
            "### Step complete: verify_election_consistency using `belenios_tool verify-diff` (0)"
        )

        console_log(
            "### Starting step: verify_election_consistency using `belenios_tool verify` (2)"
        )
        verify_election_consistency(self.election_id)
        console_log(
            "### Step complete: verify_election_consistency using `belenios_tool verify` (2)"
        )

        console_log(
            "### Starting step: administrator_starts_tallying_of_election")
        self.administrator_starts_tallying_of_election(
            settings.TRUSTEES_THRESHOLD_VALUE)
        console_log(
            "### Step complete: administrator_starts_tallying_of_election")

        console_log("### Starting step: trustees_do_partial_decryption")
        self.trustees_do_partial_decryption(settings.TRUSTEES_THRESHOLD_VALUE)
        console_log("### Step complete: trustees_do_partial_decryption")

        console_log(
            "### Starting step: administrator_finishes_tallying_of_election")
        self.administrator_finishes_tallying_of_election(
            settings.TRUSTEES_THRESHOLD_VALUE)
        console_log(
            "### Step complete: administrator_finishes_tallying_of_election")

        console_log(
            "### Starting step: verify_election_consistency using `belenios_tool verify` (3)"
        )
        verify_election_consistency(self.election_id)
        console_log(
            "### Step complete: verify_election_consistency using `belenios_tool verify` (3)"
        )
示例#17
0
            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?


if __name__ == "__main__":
    if not hasattr(settings, "LOGIN_MODE"):
        settings.LOGIN_MODE = "local"
    if not hasattr(settings, "START_SERVER"):
        settings.START_SERVER = True

    random_seed = os.getenv('RANDOM_SEED', None)
    if not random_seed:
        random_seed = random.randrange(sys.maxsize)
    console_log("Python random seed being used:", random_seed)
    random.seed(random_seed)

    settings.SERVER_URL = os.getenv('SERVER_URL', settings.SERVER_URL)
    if os.getenv('START_SERVER', None):
        settings.START_SERVER = bool(strtobool(os.getenv('START_SERVER')))

    if os.getenv('USE_HEADLESS_BROWSER', None):
        settings.USE_HEADLESS_BROWSER = bool(
            strtobool(os.getenv('USE_HEADLESS_BROWSER')))

    settings.WAIT_TIME_BETWEEN_EACH_STEP = float(
        os.getenv('WAIT_TIME_BETWEEN_EACH_STEP',
                  settings.WAIT_TIME_BETWEEN_EACH_STEP)
    )  # Do not set a value below 0.02 seconds, otherwise hypothesis test becomes flaky.
    settings.EXPLICIT_WAIT_TIMEOUT = int(
示例#18
0
            "### Step complete: administrator_finishes_tallying_of_election")

        console_log(
            "### Starting step: verify_election_consistency using `belenios_tool verify` (3)"
        )
        verify_election_consistency(self.election_id)
        console_log(
            "### Step complete: verify_election_consistency using `belenios_tool verify` (3)"
        )


if __name__ == "__main__":
    random_seed = os.getenv('RANDOM_SEED', None)
    if not random_seed:
        random_seed = random.randrange(sys.maxsize)
    console_log("Python random seed being used:", random_seed)
    random.seed(random_seed)

    if os.getenv('USE_HEADLESS_BROWSER', None):
        settings.USE_HEADLESS_BROWSER = bool(
            strtobool(os.getenv('USE_HEADLESS_BROWSER')))

    settings.SENT_EMAILS_TEXT_FILE_ABSOLUTE_PATH = os.getenv(
        'SENT_EMAILS_TEXT_FILE_ABSOLUTE_PATH',
        settings.SENT_EMAILS_TEXT_FILE_ABSOLUTE_PATH)
    settings.WAIT_TIME_BETWEEN_EACH_STEP = float(
        os.getenv('WAIT_TIME_BETWEEN_EACH_STEP',
                  settings.WAIT_TIME_BETWEEN_EACH_STEP))
    settings.EXPLICIT_WAIT_TIMEOUT = int(
        os.getenv('EXPLICIT_WAIT_TIMEOUT', settings.EXPLICIT_WAIT_TIMEOUT))
    settings.NUMBER_OF_INVITED_VOTERS = int(
示例#19
0
    def test_scenario_2_manual_vote(self):
        console_log(
            "### Starting step: administrator_starts_creation_of_manual_election"
        )
        self.administrator_starts_creation_of_manual_election()
        console_log(
            "### Step complete: administrator_starts_creation_of_manual_election"
        )

        console_log(
            "### Starting step: credential_authority_sends_credentials_to_voters"
        )
        self.credential_authority_sends_credentials_to_voters()
        console_log(
            "### Step complete: credential_authority_sends_credentials_to_voters"
        )

        console_log("### Starting step: administrator_invites_trustees")
        self.administrator_invites_trustees()
        console_log("### Step complete: administrator_invites_trustees")

        console_log(
            "### Starting step: trustees_generate_election_private_keys")
        self.trustees_generate_election_private_keys()
        console_log(
            "### Step complete: trustees_generate_election_private_keys")

        console_log(
            "### Starting step: administrator_completes_creation_of_election")
        self.administrator_completes_creation_of_election()
        console_log(
            "### Step complete: administrator_completes_creation_of_election")

        console_log(
            "### Starting step: verify_election_consistency using `belenios_tool verify` (0)"
        )
        verify_election_consistency(self.election_id)
        console_log(
            "### Step complete: verify_election_consistency using `belenios_tool verify` (0)"
        )

        console_log("### Starting step: all_voters_vote_in_sequences")
        self.all_voters_vote_in_sequences()
        console_log("### Step complete: all_voters_vote_in_sequences")

        console_log(
            "### Starting step: verify_election_consistency using `belenios_tool verify` (1)"
        )
        verify_election_consistency(self.election_id)
        console_log(
            "### Step complete: verify_election_consistency using `belenios_tool verify` (1)"
        )

        console_log("### Starting step: create_election_data_snapshot (0)")
        snapshot_folder = create_election_data_snapshot(self.election_id)
        console_log("### Step complete: create_election_data_snapshot (0)")

        try:
            console_log("### Starting step: some_voters_revote")
            self.some_voters_revote()
            console_log("### Step complete: some_voters_revote")

            console_log(
                "### Starting step: verify_election_consistency using `belenios_tool verify-diff` (0)"
            )
            verify_election_consistency(self.election_id, snapshot_folder)
        finally:
            delete_election_data_snapshot(snapshot_folder)
        console_log(
            "### Step complete: verify_election_consistency using `belenios_tool verify-diff` (0)"
        )

        console_log(
            "### Starting step: verify_election_consistency using `belenios_tool verify` (2)"
        )
        verify_election_consistency(self.election_id)
        console_log(
            "### Step complete: verify_election_consistency using `belenios_tool verify` (2)"
        )

        console_log(
            "### Starting step: administrator_starts_tallying_of_election")
        self.administrator_starts_tallying_of_election()
        console_log(
            "### Step complete: administrator_starts_tallying_of_election")

        console_log("### Starting step: trustees_do_partial_decryption")
        self.trustees_do_partial_decryption()
        console_log("### Step complete: trustees_do_partial_decryption")

        console_log(
            "### Starting step: administrator_finishes_tallying_of_election")
        self.administrator_finishes_tallying_of_election()
        console_log(
            "### Step complete: administrator_finishes_tallying_of_election")

        console_log(
            "### Starting step: verify_election_consistency using `belenios_tool verify` (3)"
        )
        verify_election_consistency(self.election_id)
        console_log(
            "### Step complete: verify_election_consistency using `belenios_tool verify` (3)"
        )
示例#20
0
    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)
示例#21
0
    def some_voters_vote_in_sequences(self, voters=None, start_index=0, end_index=None, verify_every_x_votes=5):
        """
        Iterates over `voters` from index `start_index` to `end_index`, cast their vote, and checks vote data consistency for every batch of `verify_every_x_votes` votes (using `belenios_tool verify-diff` and a snapshot of election data copied in previous batch).
        """
        if start_index < 0:
            raise Exception("start_index cannot be below 0")
        current_start_index = start_index
        if end_index is None:
            end_index = settings.NUMBER_OF_VOTING_VOTERS
        elif end_index > settings.NUMBER_OF_VOTING_VOTERS:
            raise Exception("end_index cannot exceeed NUMBER_OF_VOTING_VOTERS")

        if voters is None:
            voters = self.voters_email_addresses
        voters_who_will_vote_now = voters[start_index:end_index]
        voters_who_will_vote_now_data = populate_credential_and_password_for_voters_from_sent_emails(self.fake_sent_emails_manager, voters_who_will_vote_now, settings.ELECTION_TITLE)
        voters_who_will_vote_now_data = populate_random_votes_for_voters(voters_who_will_vote_now_data)
        self.update_voters_data(voters_who_will_vote_now_data)
        snapshot_folder = None

        while current_start_index < end_index:
            increment = verify_every_x_votes # could be randomized
            current_end_index = current_start_index + increment
            if current_end_index > end_index:
                current_end_index = end_index

            if current_start_index > 0:
                console_log("#### Starting substep: create_election_data_snapshot")
                snapshot_folder = create_election_data_snapshot(self.election_id)
                console_log("#### Substep complete: create_election_data_snapshot")

            try:
                console_log("#### A batch of " + str(current_end_index - current_start_index) + " voters, indexed " + str(current_start_index) + " to " + str(current_end_index - 1) + " are now going to vote")
                self.some_voters_cast_their_vote(voters_who_will_vote_now_data[current_start_index:current_end_index])
                console_log("#### A batch of " + str(current_end_index - current_start_index) + " voters, indexed " + str(current_start_index) + " to " + str(current_end_index - 1) + " have now voted")

                if current_start_index > 0:
                    console_log("#### Starting substep: verify_election_consistency using `belenios_tool verify-diff` (for a batch of votes)")
                    verify_election_consistency(self.election_id, snapshot_folder)
                    console_log("#### Substep complete: verify_election_consistency using `belenios_tool verify-diff` (for a batch of votes)")
            finally:
                if current_start_index > 0:
                    console_log("#### Starting substep: delete_election_data_snapshot")
                    delete_election_data_snapshot(snapshot_folder)
                    console_log("#### Substep complete: delete_election_data_snapshot")

            current_start_index += increment
示例#22
0
    def test_load_testing_set_up(self):
        # Create (if it does not exist) folder that will contain all files created by this script
        os.makedirs(settings.GENERATED_FILES_DESTINATION_FOLDER, exist_ok=True)

        # Download server's sent emails text file, so that we know up to which line number we have to ignore its contents (this is its last line)
        temporary_fake_sent_emails_manager = None
        try:
            temporary_fake_sent_emails_manager = self.download_all_sent_emails(
            )
            self.fake_sent_emails_initial_lines_count = temporary_fake_sent_emails_manager.count_lines(
            )
            console_log(
                "### Initial lines count of server's fake sent emails file:",
                self.fake_sent_emails_initial_lines_count)
        finally:
            if temporary_fake_sent_emails_manager:
                temporary_fake_sent_emails_manager.uninstall_fake_sendmail_log_file(
                )

        console_log(
            "### Running test method BeleniosLoadTestingSetUp::test_load_testing_set_up()"
        )
        console_log(
            "### Starting step: administrator_starts_creation_of_manual_election"
        )
        self.administrator_starts_creation_of_manual_election()
        console_log(
            "### Step complete: administrator_starts_creation_of_manual_election"
        )

        self.election_id = admin_election_draft_page_url_to_election_id(
            self.draft_election_administration_page_url)

        console_log(
            "### Starting step: credential_authority_sends_locally_generated_credentials_to_server"
        )
        self.credential_authority_sends_locally_generated_credentials_to_server(
        )
        console_log(
            "### Step complete: credential_authority_sends_locally_generated_credentials_to_server"
        )

        console_log("### Starting step: administrator_invites_trustees")
        self.administrator_invites_trustees()
        console_log("### Step complete: administrator_invites_trustees")

        console_log(
            "### Starting step: trustees_generate_election_private_keys")
        self.trustees_generate_election_private_keys()
        console_log(
            "### Step complete: trustees_generate_election_private_keys")

        console_log(
            "### Starting step: administrator_completes_creation_of_election")
        self.administrator_completes_creation_of_election()
        console_log(
            "### Step complete: administrator_completes_creation_of_election")

        if settings.SERVER_URL == "http://localhost:8001":
            console_log(
                "### Starting step: verify_election_consistency using `belenios_tool verify`"
            )
            verify_election_consistency(self.election_id)
            console_log(
                "### Step complete: verify_election_consistency using `belenios_tool verify`"
            )

        console_log("### Starting step: download_all_sent_emails")
        self.distant_fake_sent_emails_manager = self.download_all_sent_emails()
        console_log("### Step complete: download_all_sent_emails")

        # Concatenate (distant) Belenios server's sent emails file (starting after line `fake_sent_emails_initial_lines_count`) and local credential authority's sent emails file into file `self.distant_fake_sent_emails_manager.log_file_path`, so that `self.generate_vote_ballots()` can parse it and find all information it needs.
        import subprocess
        import tempfile
        (file_handle, log_file_path) = tempfile.mkstemp(text=True)
        with open(log_file_path, 'w') as f:
            subprocess.run([
                "tail", "-n",
                "+" + str(self.fake_sent_emails_initial_lines_count + 1),
                self.distant_fake_sent_emails_manager.log_file_path
            ],
                           stdout=f)
            subprocess.run(
                ["cat", self.fake_sent_emails_manager.log_file_path], stdout=f)
        subprocess.run([
            "cp", log_file_path,
            self.distant_fake_sent_emails_manager.log_file_path
        ])
        subprocess.run(["rm", "-f", log_file_path])

        # Generate ballot for each voter
        console_log("### Starting step: generate_vote_ballots")
        self.generate_vote_ballots()
        console_log("### Step complete: generate_vote_ballots")

        # Export a CSV file to be imported by the jmeter load testing script. It contains fields 'voter_email_address', 'voter_password', 'voter_credential', 'voter_encrypted_ballot_file_name', 'election_page_url'
        console_log("### Starting step: export_all_votes_csv")
        self.export_all_votes_csv()
        console_log("### Step complete: export_all_votes_csv")
示例#23
0
    def test_scenario_1_simple_vote(self):
        console_log("### Starting step: administrator_creates_election")
        self.administrator_creates_election()
        console_log("### Step complete: administrator_creates_election")

        console_log("### Starting step: administrator_regenerates_passwords_for_some_voters")
        self.administrator_regenerates_passwords_for_some_voters()
        console_log("### Step complete: administrator_regenerates_passwords_for_some_voters")

        console_log("### Starting step: verify_election_consistency using `belenios_tool verify` (0)")
        verify_election_consistency(self.election_id)
        console_log("### Step complete: verify_election_consistency using `belenios_tool verify` (0)")

        console_log("### Starting step: all_voters_vote_in_sequences")
        self.all_voters_vote_in_sequences()
        console_log("### Step complete: all_voters_vote_in_sequences")

        console_log("### Starting step: verify_election_consistency using `belenios_tool verify` (1)")
        verify_election_consistency(self.election_id)
        console_log("### Step complete: verify_election_consistency using `belenios_tool verify` (1)")

        console_log("### Starting step: create_election_data_snapshot (0)")
        snapshot_folder = create_election_data_snapshot(self.election_id)
        console_log("### Step complete: create_election_data_snapshot (0)")

        try:
            console_log("### Starting step: some_voters_revote")
            self.some_voters_revote()
            console_log("### Step complete: some_voters_revote")

            console_log("### Starting step: verify_election_consistency using `belenios_tool verify-diff` (0)")
            verify_election_consistency(self.election_id, snapshot_folder)
        finally:
            delete_election_data_snapshot(snapshot_folder)
        console_log("### Step complete: verify_election_consistency using `belenios_tool verify-diff` (0)")

        console_log("### Starting step: verify_election_consistency using `belenios_tool verify` (2)")
        verify_election_consistency(self.election_id)
        console_log("### Step complete: verify_election_consistency using `belenios_tool verify` (2)")

        console_log("### Starting step: administrator_does_tallying_of_election")
        self.administrator_does_tallying_of_election()
        console_log("### Step complete: administrator_does_tallying_of_election")

        console_log("### Starting step: verify_election_consistency using `belenios_tool verify` (3)")
        verify_election_consistency(self.election_id)
        console_log("### Step complete: verify_election_consistency using `belenios_tool verify` (3)")

        console_log("### Starting step: voter votes after the election is closed")
        self.one_voter_revotes_after_the_election_is_closed()
        console_log("### Step complete: voter votes after the election is closed")
示例#24
0
    def some_voters_cast_their_vote(self, voters):
        """
        :param voters: list of dict. Each element contains information about a voter (their e-mail address, the planned answers to each question they will cast)
        """
        browser = self.browser
        voters_count = len(voters)
        for index, voter in enumerate(voters):
            console_log(
                "#### Current voter casting their vote in current batch: " +
                str(index + 1) + "/" + str(voters_count))

            self.one_voter_votes(voter)
            wait_a_bit()
            """
            Next screen looks like this:
            Your ballot for My test election for Scenario 1 has been accepted. Your smart ballot tracker is ISXe/rCNCVa9XcVeFgKglbpgo5SoZs4svT6dPbR5b6M. You can check its presence in the {ballot box} anytime during the election. A confirmation e-mail has been sent to you.

            {Go back to election}

            Where {xxx} is a link
            """

            # He clicks on the "ballot box" link
            ballot_box_link_label = "ballot box"
            ballot_box_link_element = wait_for_an_element_with_partial_link_text_exists(
                browser, ballot_box_link_label, settings.EXPLICIT_WAIT_TIMEOUT)
            ballot_box_link_element.click()

            wait_a_bit()

            # He checks that his smart ballot tracker appears in the list
            all_smart_ballot_trackers_css_selector = "#main ul li a"
            all_smart_ballot_trackers_elements = wait_for_elements_exist(
                browser, all_smart_ballot_trackers_css_selector,
                settings.EXPLICIT_WAIT_TIMEOUT)
            assert len(all_smart_ballot_trackers_elements)
            matches = [
                element for element in all_smart_ballot_trackers_elements
                if element.get_attribute('innerText') ==
                voter["smart_ballot_tracker"]
            ]
            assert len(matches) is 1

            self.voters_email_addresses_who_have_voted[
                voter["email_address"]] = True

            # In a following pass, he checks his mailbox to find a new email with confirmation of his vote, and verifies the value of the smart ballot tracker written in this email is the same as the one he noted. This verification is done in a separated pass because of an optimization, so that we only re-read and re-populate the sendmail_fake text file once for all users.

            # He closes the window (there is no log-out link, because user is not logged in: credentials are not remembered)
            # It is not really mandatory for the test to close the window. Re-opening a browser takes much more time, compared to just navigating to another URL. So actually to save execution time, we choose to close the window only sometimes, randomly.
            if random.randint(0, 10) <= 3:
                browser.quit()
                self.browser = initialize_browser()
                browser = self.browser

        # Start another pass, where we re-read and re-populate the sendmail_fake text file once for all users.
        voters = repopulate_vote_confirmations_for_voters_from_sent_emails(
            self.fake_sent_emails_manager, voters, settings.ELECTION_TITLE)
        for voter in voters:
            # He checks his mailbox to find a new email with confirmation of his vote, and verifies the value of the smart ballot tracker written in this email is the same as the one he noted.
            assert voter["smart_ballot_tracker"] == voter[
                "smart_ballot_tracker_in_vote_confirmation_email"], "Ballot tracker read in vote confirmation email (" + voter[
                    "smart_ballot_tracker"] + ") is not the same as the one read on the vote confirmation page (" + voter[
                        "smart_ballot_tracker_in_vote_confirmation_email"] + ")"
示例#25
0
    def setUp(self):
        self.fake_sent_emails_manager = FakeSentEmailsManager(
            settings.SENT_EMAILS_TEXT_FILE_ABSOLUTE_PATH)
        self.fake_sent_emails_manager.install_fake_sendmail_log_file()
        if settings.START_SERVER:
            if settings.CLEAN_UP_POLICY == settings.CLEAN_UP_POLICIES.REMOVE_DATABASE:
                remove_database_folder()
            elif settings.CLEAN_UP_POLICY == settings.CLEAN_UP_POLICIES.REMOVE_ELECTION or settings.CLEAN_UP_POLICY == settings.CLEAN_UP_POLICIES.DO_NOTHING:
                pass
            self.server = initialize_server()
        self.browser = initialize_browser()
        if settings.ELECTION_ID:
            self.election_id = settings.ELECTION_ID
        else:
            # Download server's sent emails text file, so that we know up to which line number we have to ignore its contents (this is its last line)
            temporary_fake_sent_emails_manager = None
            try:
                temporary_fake_sent_emails_manager = self.download_all_sent_emails(
                )
                self.fake_sent_emails_initial_lines_count = temporary_fake_sent_emails_manager.count_lines(
                )
                console_log(
                    "### Initial lines count of server's fake sent emails file:",
                    self.fake_sent_emails_initial_lines_count)
            finally:
                if temporary_fake_sent_emails_manager:
                    temporary_fake_sent_emails_manager.uninstall_fake_sendmail_log_file(
                    )

            self.administrator_creates_election()

            console_log("### Starting step: download_all_sent_emails")
            self.distant_fake_sent_emails_manager = self.download_all_sent_emails(
            )
            console_log("### Step complete: download_all_sent_emails")

            # Concatenate (distant) Belenios server's sent emails file (starting after line `fake_sent_emails_initial_lines_count`) and local credential authority's sent emails file into file `self.distant_fake_sent_emails_manager.log_file_path`, so that `self.generate_vote_ballots()` can parse it and find all information it needs.
            import subprocess
            import tempfile
            (file_handle, log_file_path) = tempfile.mkstemp(text=True)
            with open(log_file_path, 'w') as f:
                subprocess.run([
                    "tail", "-n",
                    "+" + str(self.fake_sent_emails_initial_lines_count + 1),
                    self.distant_fake_sent_emails_manager.log_file_path
                ],
                               stdout=f)
                subprocess.run(
                    ["cat", self.fake_sent_emails_manager.log_file_path],
                    stdout=f)
            subprocess.run([
                "cp", log_file_path,
                self.distant_fake_sent_emails_manager.log_file_path
            ])
            subprocess.run(["rm", "-f", log_file_path])

            invited_voters_who_will_vote = random.sample(
                self.voters_email_addresses, settings.NUMBER_OF_VOTING_VOTERS)
            invited_voters_who_will_vote_data = populate_credential_and_password_for_voters_from_sent_emails(
                self.distant_fake_sent_emails_manager,
                invited_voters_who_will_vote, settings.ELECTION_TITLE)
            invited_voters_who_will_vote_data = populate_random_votes_for_voters(
                invited_voters_who_will_vote_data)
            self.update_voters_data(invited_voters_who_will_vote_data)

            selected_voter = invited_voters_who_will_vote_data[0]
            settings.VOTER_USERNAME = selected_voter["username"]
            settings.VOTER_PASSWORD = selected_voter["password"]
            settings.VOTER_CREDENTIAL = selected_voter["credential"]
        console_log("Going to vote using VOTER_USERNAME:"******"Going to vote using VOTER_PASSWORD:"******"Going to vote using VOTER_CREDENTIAL:",
                    settings.VOTER_CREDENTIAL)
示例#26
0
    def some_voters_vote_in_sequences(self,
                                      voters=None,
                                      start_index=0,
                                      end_index=None,
                                      verify_every_x_votes=5):
        """
        Iterates over `voters` from index `start_index` to `end_index`, cast their vote, and checks vote data consistency for every batch of `verify_every_x_votes` votes (using `belenios_tool verify-diff` and a snapshot of election data copied in previous batch).
        """
        if start_index < 0:
            raise Exception("start_index cannot be below 0")
        current_start_index = start_index
        if end_index is None:
            end_index = settings.NUMBER_OF_VOTING_VOTERS
        elif end_index > settings.NUMBER_OF_VOTING_VOTERS:
            raise Exception("end_index cannot exceeed NUMBER_OF_VOTING_VOTERS")

        if voters is None:
            voters = self.voters_email_addresses
        voters_who_will_vote_now = voters[start_index:end_index]
        voters_who_will_vote_now_data = populate_credential_and_password_for_voters_from_sent_emails(
            self.fake_sent_emails_manager, voters_who_will_vote_now,
            settings.ELECTION_TITLE)
        voters_who_will_vote_now_data = populate_random_votes_for_voters(
            voters_who_will_vote_now_data)
        self.update_voters_data(voters_who_will_vote_now_data)
        snapshot_folder = None

        while current_start_index < end_index:
            increment = verify_every_x_votes  # could be randomized
            current_end_index = current_start_index + increment
            if current_end_index > end_index:
                current_end_index = end_index

            if current_start_index > 0:
                console_log(
                    "#### Starting substep: create_election_data_snapshot")
                snapshot_folder = create_election_data_snapshot(
                    self.election_id)
                console_log(
                    "#### Substep complete: create_election_data_snapshot")

            try:
                console_log("#### A batch of " +
                            str(current_end_index - current_start_index) +
                            " voters, indexed " + str(current_start_index) +
                            " to " + str(current_end_index - 1) +
                            " are now going to vote")
                self.some_voters_cast_their_vote(voters_who_will_vote_now_data[
                    current_start_index:current_end_index])
                console_log("#### A batch of " +
                            str(current_end_index - current_start_index) +
                            " voters, indexed " + str(current_start_index) +
                            " to " + str(current_end_index - 1) +
                            " have now voted")

                if current_start_index > 0:
                    console_log(
                        "#### Starting substep: verify_election_consistency using `belenios_tool verify-diff` (for a batch of votes)"
                    )
                    verify_election_consistency(self.election_id,
                                                snapshot_folder)
                    console_log(
                        "#### Substep complete: verify_election_consistency using `belenios_tool verify-diff` (for a batch of votes)"
                    )
            finally:
                if current_start_index > 0:
                    console_log(
                        "#### Starting substep: delete_election_data_snapshot")
                    delete_election_data_snapshot(snapshot_folder)
                    console_log(
                        "#### Substep complete: delete_election_data_snapshot")

            current_start_index += increment
示例#27
0
    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)
示例#28
0
        console_log("### Step complete: trustees_do_partial_decryption")

        console_log("### Starting step: administrator_finishes_tallying_of_election")
        self.administrator_finishes_tallying_of_election()
        console_log("### Step complete: administrator_finishes_tallying_of_election")

        console_log("### Starting step: verify_election_consistency using `belenios_tool verify` (3)")
        verify_election_consistency(self.election_id)
        console_log("### Step complete: verify_election_consistency using `belenios_tool verify` (3)")


if __name__ == "__main__":
    random_seed = os.getenv('RANDOM_SEED', None)
    if not random_seed:
        random_seed = random.randrange(sys.maxsize)
    console_log("Python random seed being used:", random_seed)
    random.seed(random_seed)

    if os.getenv('USE_HEADLESS_BROWSER', None):
        settings.USE_HEADLESS_BROWSER = bool(strtobool(os.getenv('USE_HEADLESS_BROWSER')))

    settings.SENT_EMAILS_TEXT_FILE_ABSOLUTE_PATH = os.getenv('SENT_EMAILS_TEXT_FILE_ABSOLUTE_PATH', settings.SENT_EMAILS_TEXT_FILE_ABSOLUTE_PATH)
    settings.WAIT_TIME_BETWEEN_EACH_STEP = float(os.getenv('WAIT_TIME_BETWEEN_EACH_STEP', settings.WAIT_TIME_BETWEEN_EACH_STEP))
    settings.EXPLICIT_WAIT_TIMEOUT = int(os.getenv('EXPLICIT_WAIT_TIMEOUT', settings.EXPLICIT_WAIT_TIMEOUT))
    settings.NUMBER_OF_INVITED_VOTERS = int(os.getenv('NUMBER_OF_INVITED_VOTERS', settings.NUMBER_OF_INVITED_VOTERS))
    settings.NUMBER_OF_VOTING_VOTERS = int(os.getenv('NUMBER_OF_VOTING_VOTERS', settings.NUMBER_OF_VOTING_VOTERS))
    settings.NUMBER_OF_REVOTING_VOTERS = int(os.getenv('NUMBER_OF_REVOTING_VOTERS', settings.NUMBER_OF_REVOTING_VOTERS))
    settings.NUMBER_OF_REGENERATED_PASSWORD_VOTERS = int(os.getenv('NUMBER_OF_REGENERATED_PASSWORD_VOTERS', settings.NUMBER_OF_REGENERATED_PASSWORD_VOTERS))
    settings.ADMINISTRATOR_USERNAME = os.getenv('ADMINISTRATOR_USERNAME', settings.ADMINISTRATOR_USERNAME)
    settings.ADMINISTRATOR_PASSWORD = os.getenv('ADMINISTRATOR_PASSWORD', settings.ADMINISTRATOR_PASSWORD)
    settings.ELECTION_TITLE = os.getenv('ELECTION_TITLE', settings.ELECTION_TITLE)
示例#29
0
        self.cast_all_votes_from_csv()
        console_log("### Step complete: cast_all_votes_from_csv")


if __name__ == "__main__":
    if os.getenv('USE_HEADLESS_BROWSER', None):
        settings.USE_HEADLESS_BROWSER = bool(
            strtobool(os.getenv('USE_HEADLESS_BROWSER')))

    settings.WAIT_TIME_BETWEEN_EACH_STEP = float(
        os.getenv('WAIT_TIME_BETWEEN_EACH_STEP',
                  settings.WAIT_TIME_BETWEEN_EACH_STEP))
    settings.EXPLICIT_WAIT_TIMEOUT = int(
        os.getenv('EXPLICIT_WAIT_TIMEOUT', settings.EXPLICIT_WAIT_TIMEOUT))

    settings.GENERATED_FILES_DESTINATION_FOLDER = os.getenv(
        'GENERATED_FILES_DESTINATION_FOLDER',
        settings.GENERATED_FILES_DESTINATION_FOLDER)

    settings.SKIP_ROWS_IN_CSV_FILE = int(os.getenv('SKIP_ROWS_IN_CSV_FILE', 0))

    console_log("USE_HEADLESS_BROWSER:", settings.USE_HEADLESS_BROWSER)
    console_log("WAIT_TIME_BETWEEN_EACH_STEP:",
                settings.WAIT_TIME_BETWEEN_EACH_STEP)
    console_log("EXPLICIT_WAIT_TIMEOUT:", settings.EXPLICIT_WAIT_TIMEOUT)
    console_log("GENERATED_FILES_DESTINATION_FOLDER:",
                settings.GENERATED_FILES_DESTINATION_FOLDER)
    console_log("SKIP_ROWS_IN_CSV_FILE:", settings.SKIP_ROWS_IN_CSV_FILE)

    unittest.main()
示例#30
0
    def test_scenario_2_manual_vote(self):
        console_log("### Starting step: administrator_starts_creation_of_manual_election")
        self.administrator_starts_creation_of_manual_election()
        console_log("### Step complete: administrator_starts_creation_of_manual_election")

        console_log("### Starting step: credential_authority_sends_credentials_to_voters")
        self.credential_authority_sends_credentials_to_voters()
        console_log("### Step complete: credential_authority_sends_credentials_to_voters")

        console_log("### Starting step: administrator_invites_trustees")
        self.administrator_invites_trustees()
        console_log("### Step complete: administrator_invites_trustees")

        console_log("### Starting step: trustees_generate_election_private_keys")
        self.trustees_generate_election_private_keys()
        console_log("### Step complete: trustees_generate_election_private_keys")

        console_log("### Starting step: administrator_completes_creation_of_election")
        self.administrator_completes_creation_of_election()
        console_log("### Step complete: administrator_completes_creation_of_election")

        console_log("### Starting step: verify_election_consistency using `belenios_tool verify` (0)")
        verify_election_consistency(self.election_id)
        console_log("### Step complete: verify_election_consistency using `belenios_tool verify` (0)")

        console_log("### Starting step: all_voters_vote_in_sequences")
        self.all_voters_vote_in_sequences()
        console_log("### Step complete: all_voters_vote_in_sequences")

        console_log("### Starting step: verify_election_consistency using `belenios_tool verify` (1)")
        verify_election_consistency(self.election_id)
        console_log("### Step complete: verify_election_consistency using `belenios_tool verify` (1)")

        console_log("### Starting step: create_election_data_snapshot (0)")
        snapshot_folder = create_election_data_snapshot(self.election_id)
        console_log("### Step complete: create_election_data_snapshot (0)")

        try:
            console_log("### Starting step: some_voters_revote")
            self.some_voters_revote()
            console_log("### Step complete: some_voters_revote")

            console_log("### Starting step: verify_election_consistency using `belenios_tool verify-diff` (0)")
            verify_election_consistency(self.election_id, snapshot_folder)
        finally:
            delete_election_data_snapshot(snapshot_folder)
        console_log("### Step complete: verify_election_consistency using `belenios_tool verify-diff` (0)")

        console_log("### Starting step: verify_election_consistency using `belenios_tool verify` (2)")
        verify_election_consistency(self.election_id)
        console_log("### Step complete: verify_election_consistency using `belenios_tool verify` (2)")

        console_log("### Starting step: administrator_starts_tallying_of_election")
        self.administrator_starts_tallying_of_election()
        console_log("### Step complete: administrator_starts_tallying_of_election")

        console_log("### Starting step: trustees_do_partial_decryption")
        self.trustees_do_partial_decryption()
        console_log("### Step complete: trustees_do_partial_decryption")

        console_log("### Starting step: administrator_finishes_tallying_of_election")
        self.administrator_finishes_tallying_of_election()
        console_log("### Step complete: administrator_finishes_tallying_of_election")

        console_log("### Starting step: verify_election_consistency using `belenios_tool verify` (3)")
        verify_election_consistency(self.election_id)
        console_log("### Step complete: verify_election_consistency using `belenios_tool verify` (3)")
示例#31
0
        console_log("### Starting step: verify_election_consistency using `belenios_tool verify` (3)")
        verify_election_consistency(self.election_id)
        console_log("### Step complete: verify_election_consistency using `belenios_tool verify` (3)")

        console_log("### Starting step: voter votes after the election is closed")
        self.one_voter_revotes_after_the_election_is_closed()
        console_log("### Step complete: voter votes after the election is closed")



if __name__ == "__main__":
    random_seed = os.getenv('RANDOM_SEED', None)
    if not random_seed:
        random_seed = random.randrange(sys.maxsize)
    console_log("Python random seed being used:", random_seed)
    random.seed(random_seed)

    if os.getenv('USE_HEADLESS_BROWSER', None):
        settings.USE_HEADLESS_BROWSER = bool(strtobool(os.getenv('USE_HEADLESS_BROWSER')))

    settings.SENT_EMAILS_TEXT_FILE_ABSOLUTE_PATH = os.getenv('SENT_EMAILS_TEXT_FILE_ABSOLUTE_PATH', settings.SENT_EMAILS_TEXT_FILE_ABSOLUTE_PATH)
    settings.WAIT_TIME_BETWEEN_EACH_STEP = float(os.getenv('WAIT_TIME_BETWEEN_EACH_STEP', settings.WAIT_TIME_BETWEEN_EACH_STEP))
    settings.EXPLICIT_WAIT_TIMEOUT = int(os.getenv('EXPLICIT_WAIT_TIMEOUT', settings.EXPLICIT_WAIT_TIMEOUT))
    settings.NUMBER_OF_INVITED_VOTERS = int(os.getenv('NUMBER_OF_INVITED_VOTERS', settings.NUMBER_OF_INVITED_VOTERS))
    settings.NUMBER_OF_VOTING_VOTERS = int(os.getenv('NUMBER_OF_VOTING_VOTERS', settings.NUMBER_OF_VOTING_VOTERS))
    settings.NUMBER_OF_REVOTING_VOTERS = int(os.getenv('NUMBER_OF_REVOTING_VOTERS', settings.NUMBER_OF_REVOTING_VOTERS))
    settings.NUMBER_OF_REGENERATED_PASSWORD_VOTERS = int(os.getenv('NUMBER_OF_REGENERATED_PASSWORD_VOTERS', settings.NUMBER_OF_REGENERATED_PASSWORD_VOTERS))
    settings.ADMINISTRATOR_USERNAME = os.getenv('ADMINISTRATOR_USERNAME', settings.ADMINISTRATOR_USERNAME)
    settings.ADMINISTRATOR_PASSWORD = os.getenv('ADMINISTRATOR_PASSWORD', settings.ADMINISTRATOR_PASSWORD)
    settings.ELECTION_TITLE = os.getenv('ELECTION_TITLE', settings.ELECTION_TITLE)