22. Appendix 4: Bill Split Python Test Activity Script examples

The complete Test Action Scripts for the SplitMe Bill Split Test activity with selenium user interface tests and excel spreadsheet oracle and assertion checking are shown below

22.1. Test Interface File

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106

# ------------------------------------------------------------------------------
# Imports
# ------------------------------------------------------------------------------
import sys
from lib.Apps.selenium_utils import SeleniumTestInterface

# Class: TestInterface
# ==============================================================================
class TestInterface(SeleniumTestInterface):
    '''
    TestInterface: 
    A user defined interface to the SplitMe web App  https://splitme.net/en
    '''

    # --------------------------------------------------------------------------
    def __init__(self, **kwargs):
        '''
        __init__: Instance initialisation and connect to server
        '''
        super(TestInterface, self).__init__(**kwargs)

    # --------------------------------------------------------------------------
    def user_definitions(self):
        '''
        user_definitions: This is where the user enters specific details
         - We recommend you use the TruePath web app to get your XPATH web elements 

         - Supported browsers are: Chrome, Edge, Firefox, Ie, Opera, Safari
        '''

        self.browser = "Chrome"
        self.start_url = 'https://splitme.net/en'
        self.start_xpath_string = None
        self.timeout = 5        # Maximum time to wait for an element to render
        self.verbosity = 5      # How much output 0-10, 0 is least, 10 is most
        self.controls = {

            # 'Key':  '//*    ' Reusable key and XPath notation for searching the DOM for all elements

            #  New_Account_Step1
            # ============================================================================
            # Find a span element with the exact text "Try SplitMe"
            'Try_SplitMe': '//span[text()="Try SplitMe"]',
            # Find a span element with the exact text "Web Version"
            'Web_Version': '//span[text()="Web Version"]',
            # Find a div element with a class attribute = "testAccountListMore"
            'testAccountListMore': '//div[@class="testAccountListMore"]',
            # Find a div element with the exact text "New account"
            'New_account': '//div[text()="New account"]',

            #  CreateAccount_Step2
            # ============================================================================
            # Find an input element with an id attribute that contains the text "undefined-egSummerholidays-Name" 
            'New_Account_Name': '//input[contains(@id,"undefined-egSummerholidays-Name")]',
            # Find a div element with the exact text "Add a new person"
            'add_participant': '//div[text()="Add a new person"]',
            # Find the text that contains "Nameofnewperson" by searching all id attributes in the DOM  
            'new_participant_name': '//*[contains(@id,"Nameofthenewperson")]',
            # Find a span element with the exact text "Save"
            'Save': '//span[text()="Save"]',
            
            #  Go to Main Screen
            # ============================================================================
            # Find the span element that contains the dynamic text "<text>" by searching in the DOM  
            'Account': '//span[contains(text(), "<text>")]',

            #  Main Screen
            # ============================================================================
            # Find a div element with the exact text "Expenses"
            'expenses': '//div[text()="Expenses"]',
            # Find a div element with the exact text "Balance"
            'balance': '//div[text()="Balance"]',
            # Find a div element with the exact text "Debts"
            'debts': '//div[text()="Debts"]',

            #  Expense Screen
            # ============================================================================
            # Find a class attribute = c2 with a child element called button
            'add_an_expense': '//*[@class="c2"]/button',
            # Find the text that contains "egBurgers-Description" by searching all id attributes in the DOM
            'expense_what': '//*[contains(@id,"egBurgers-Description")]',
            # Find the text that contains "undefined-000-undefined" by searching all id attributes in the DOM
            'expense_amount': '//*[contains(@id,"undefined-000-undefined")]',
            # Find the text that contains "Belongtotheaccount" by searching all id attributes in the DOM
            'expense_account':  '//*[contains(@id,"Belongtotheaccount")]',
            # Find the input element that has text that contains "Paidby" by searching all id attributes in input elements in the DOM
            'expense_who': '//input[contains(@id,"Paidby")]',
            # Chained XPath (multiple relative Xpath declarations) Find the exact text "Me" in any class attribute = "testExpenseAddPaidByDialog"
            'Paid_Me': '//*[@class="testExpenseAddPaidByDialog"]//*[text()="Me"]',
            # Find a div element that contains the dynamic text "<text>" and then find the name attribute called paidfor  
            'expense_nonparticipant': '//div[text()="<text>"]/..//*[@name="paidFor"]',
            # Find an input element that contains the text "undefined-Date-undefined" by searching id attributes in the DOM
            'expense_date': '//input[contains(@id,"undefined-Date-undefined")]',
            # Find a span element with the exact text "OK"
            'set_date': '//span[text()="OK"]',
            # Find a span element with the exact text "Save"
            'expense_save': '//span[text()="Save"]',
            
            # Balance Screen
            # ============================================================================
            'balance_by_amount': '//*[contains(text(), "<text>")]',
        }

# ============================== END OF FILE ===================================
# ==============================================================================

22.2. Imports

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import xlsxwriter
import openpyxl
import xlwings
import unittest
import unicodedata
import time
import psutil
import applitools.eyes
from selenium import webdriver
from selenium.webdriver.common.keys import Keys 
from applitools.eyes import Eyes

22.3. Activity_Start

1
2
3
4
5
6
7
8
9
print ('This test is started with seed value = ', self.get_seed())
self.account_count = 0

self.assertions = unittest.TestCase('__init__')

self.driver = webdriver.Chrome()
print('Driver: {}'.format(self.driver))

self.test_if.create_interface('SUT', TestInterface, driver=self.driver)

22.4. New_Account

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# Call Vitaq library method 'gen()' to intelligently generate values for 
# the user test data needed for the New Account window.
self.variables.new_account_title.gen()
self.parent.new_account_name = self.variables.new_account_title.get_value() \
                               +str(self.parent.get_seed())+str(self.parent.account_count)

# Selenium commands to perform test operations on the SplitMe web app. 
# The splitme test interface file will open the app ready for testing
# Click on Try SplitMe then Web version and select New Account in SplitMe webapp
self.parent.test_if.perform_action('click', 'Try_SplitMe')
self.parent.test_if.perform_action('click', 'Web_Version')
time.sleep(2)
self.parent.test_if.perform_action('click', 'testAccountListMore')
time.sleep(2)
self.parent.test_if.perform_action('click', 'New_account')
time.sleep(2)

# Call Vitaq test data generator to intelligently generate a value for the Account owner name
self.variables.new_account_your_name.gen()

#  Create a Python list to use in downstream script logic to make sure that 
#  Vitaq does not auto-generate a participant name that is the same as the account owner
self.parent.disallowed_name_list = list()
self.parent.disallowed_name_list.append(self.variables.new_account_your_name.get_value())

# Python print to Vitaq Output tab to show ahow values that have been generated 
# and to also 'watch' them in the Script Output tab
print ('Create a new account with these attributes: ',
       self.parent.new_account_name,self.variables.new_account_your_name.get_value())

# Python script logic to increment the new account 'counter' to keep 
# track of how many accounts we have created
self.parent.account_count += 1

# Use Selenium to Enter New Account Name on WebApp
self.parent.test_if.perform_action('set_value', 'New_Account_Name', 
                                   value='{}'.format(self.parent.new_account_name))

# using xlswriter to write auto-generated test data into a spreadsheet
# Create a workbook and add a worksheet for spreadsheet oracle
self.parent.workbook = xlsxwriter.Workbook('{}.xlsx'.format(self.parent.new_account_name))
self.parent.worksheet = self.parent.workbook.add_worksheet()
# Add a bold format to use to highlight cells
self.parent.bold = self.parent.workbook.add_format({'bold': True})
# Add a number format for cells with money.
self.parent.money = self.parent.workbook.add_format({'num_format': '#,##0.00'})
# Add a number format for cells with units.
self.parent.units = self.parent.workbook.add_format({'num_format': '#,##0'})
# Start by collecting the header data
self.parent.worksheet.write('A1','What?', self.parent.bold)
self.parent.worksheet.set_column(0, 0, 24)
self.parent.worksheet.write('B1','Amount', self.parent.bold)
self.parent.worksheet.set_column(1, 1, 12)
self.parent.worksheet.write('C1`','Who Paid?', self.parent.bold)
self.parent.worksheet.set_column(2, 2, 12)
self.parent.worksheet.write('D1','How Many Participated?', self.parent.bold)
self.parent.worksheet.set_column(3, 3, 28)
# Write the auto-generated value of the Account owner into cell E1 of the spreadsheet workbook
self.parent.worksheet.set_column(4, 4, 12)
self.parent.worksheet.write('E1', self.variables.new_account_your_name.get_value(), self.parent.bold)

22.5. Add_Participant

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Ask Vitaq to Auto-Generate a 'unique' Participant Name from the list 
# of names defined in the Add_Participant Activity Variable
self.variables.add_participant_name.dynamic_disallow_list(self.parent.disallowed_name_list)
self.variables.add_participant_name.gen()
print ('Participant Name is: ',self.variables.add_participant_name.get_value())
self.parent.disallowed_name_list.append(self.variables.add_participant_name.get_value())

# Write the new participant name into the Add a new person field
self.parent.test_if.perform_action('click', 'add_participant')
self.parent.test_if.perform_action('set_value', 'new_participant_name', 
                                   value='{}'.format(self.variables.add_participant_name.get_value()))
self.parent.test_if.perform_action('send_keys', 'new_participant_name', 
                                   keys=['ENTER'])
time.sleep(2)


# Dynamically disable the Add_Particpant Test Action when we have used up 
# all of the names on the Add_Participant_Name Activity Variable list
if len(self.parent.disallowed_name_list) == len(self.variables.add_participant_name.data):
    self.set_enabled(False)
    
# Writing auto-generated test data into spreadsheets using xlswriter 
# (to create a test oracle to check bill splitting app balance results against) 
self.parent.worksheet.set_column(len(self.parent.disallowed_name_list)+3, len(self.parent.disallowed_name_list)+3, 12)
self.parent.worksheet.write('{}1'.format(xlsxwriter.utility.xl_col_to_name(len(self.parent.disallowed_name_list)+3)), 
                            self.variables.add_participant_name.get_value(), self.parent.bold)

22.6. Expenses

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Click the save button in the SplitMe WebApp to stop adding participants
time.sleep(2)
self.parent.test_if.perform_action('click', 'Save')
# Need to select Account to add Expenses to and then fill in expense
time.sleep(2)
self.parent.test_if.perform_action('click', 'Account', 
                                   text='{}'.format(self.parent.new_account_name))

# Keep track of expenses being added
self.parent.expense_count = 0
time.sleep(2)

22.7. Add_an_Expense

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# Call the the Vitaq test data generator to randomly generate a value for the Expense_item and it's cost
self.variables.expense_item.gen()
self.variables.expense_value.gen()

print ('Expense item is: ',self.variables.expense_item.get_value(),
       ' of value = ',self.variables.expense_value.get_value())

# Ask Selenium to Write the Vitaq generated test data for Expense information into the SplitMe WebApp

self.parent.test_if.perform_action('click', 'add_an_expense')
self.parent.test_if.perform_action('set_value', 'expense_what', 
                                   value='{}'.format(self.variables.expense_item.get_value()))
self.parent.test_if.perform_action('set_value', 'expense_amount', 
                                   value='{}'.format(self.variables.expense_value.get_value()))

# Ask Selenium to set todays date by default
time.sleep(1)
self.parent.test_if.perform_action('click', 'expense_date')
time.sleep(1)
self.parent.test_if.perform_action('click', 'set_date')
time.sleep(1)
# Leave the Who Paid as the account owner by default for initial testing
self.parent.test_if.perform_action('click', 'expense_who')
time.sleep(1)
self.parent.test_if.perform_action('click', 'Paid_Me')
time.sleep(1)

# Write the auto-generated expense test data into the spreadsheet oracle
row = self.parent.expense_count + 1
col = 0
expense = [self.variables.expense_item.get_value(), 
           self.variables.expense_value.get_value(), 
           self.variables.new_account_your_name.get_value(), 
           '=SUM(E{}:Z{})'.format(self.parent.expense_count+2, 
                                  self.parent.expense_count+2)]

print ("Your Name is: ", self.variables.new_account_your_name.get_value())

# Randomly select who participated in the Expense
participant_id = 0
number_of_participants = 0
self.participation_list = []
while participant_id < len(self.parent.disallowed_name_list):
    while number_of_participants <= len(self.parent.disallowed_name_list)/2:
        self.variables.add_participant_name.dynamic_allow_only_list(self.parent.disallowed_name_list)
        self.variables.add_participant_name.gen()
        self.participation_list.append(self.variables.add_participant_name.get_value())
        number_of_participants += 1
    if self.parent.disallowed_name_list[participant_id] in self.participation_list:
        expense.append(1)
        print ('Participant in bill split is: ',self.parent.disallowed_name_list[participant_id])
    else:
        if participant_id <= 1 or expense[2] == self.parent.disallowed_name_list[participant_id]:
            expense.append(1)
            print ('Participant in bill split is: ',self.parent.disallowed_name_list[participant_id])
        else:
            expense.append(0)
            # Use selenium to select who does not participate in the expense bill split for the web App
            self.parent.test_if.perform_action('click', 'expense_nonparticipant', 
                                               text='{}'.format(self.parent.disallowed_name_list[participant_id]))         
            print ('Non Participant in bill split is: ',self.parent.disallowed_name_list[participant_id])
    participant_id += 1
    
# write out expense lines and format for the expense test date n into row n of the 
# spreadsheet to give correct financial calculations in the oracle
for item in expense:
    if col == 1:
        self.parent.worksheet.write(row, col, item, self.parent.money)
    elif col in [0, 2]:
        self.parent.worksheet.write(row, col, item)
    else:
        self.parent.worksheet.write(row, col, item, self.parent.units)
            # Use selenium to select who does not participate in the expense bill split for the web App
            self.parent.test_if.perform_action('click', 'expense_nonparticipant', text='{}'.format(self.parent.disallowed_name_list[participant_id]))         
            print ('Non Participant in bill split is: ',self.parent.disallowed_name_list[participant_id])
    participant_id += 1
    
# write out expense lines and format for the expense test date n into row n of the spreadsheet to give correct financial calculations in the oracle
for item in expense:
    if col == 1:
        self.parent.worksheet.write(row, col, item, self.parent.money)
    elif col in [0, 2]:
        self.parent.worksheet.write(row, col, item)
    else:
        self.parent.worksheet.write(row, col, item, self.parent.units)
    col += 1
        self.parent.worksheet.write(row, col, item, self.parent.money)
    elif col in [0, 2]:
        self.parent.worksheet.write(row, col, item)
    else:
        self.parent.worksheet.write(row, col, item, self.parent.units)
    col += 1

22.8. Save

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Ask Selenium to Click the save button in the Tricount App to save the expense item
print ("Saving current Expense")
time.sleep(2)
self.parent.test_if.perform_action('click', 'expense_save')
time.sleep(2)
# Keep track of the Expense lines added, after 3 Expenses allow Balance to be selected
self.parent.expense_count += 1
if self.parent.expense_count <= 2:
	self.parent.Balance.set_enabled(False)
else:
    self.parent.Balance.set_enabled(True)

22.9. Balance

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
# Ask Selenium to click Debt then Expenses
self.parent.test_if.perform_action('click', 'debts')
time.sleep(2)

self.parent.test_if.perform_action('click', 'expenses')
time.sleep(2)

# Ask Selenium to click balances to show who owes what
time.sleep(2)
self.parent.test_if.perform_action('click', 'balance')
time.sleep(2)

# Use xlswriter to write total balance calculation results into account spreadsheet oracle
expense_column_number = 4
expense_column = xlsxwriter.utility.xl_col_to_name(expense_column_number)
last_expense_line = self.parent.expense_count + 1
red = self.parent.workbook.add_format({'font_color': 'red'})
default = self.parent.workbook.add_format()
self.parent.worksheet.write_rich_string('A{}'.format(last_expense_line + 2), 
                                        default, 
                                        'Total ', 
                                        red, 
                                        'Debt', 
                                        default, 
                                        '/income')
self.parent.worksheet.write('B{}'.format(last_expense_line + 2),
                            '=SUM(B2:B{})'.format(last_expense_line), 
                            self.parent.money)
participant_balance = 0
# Write participant balance calculation results into account spreadsheet oracle
while expense_column_number < len(self.parent.disallowed_name_list) + 4:
    self.parent.worksheet.write_array_formula('{}{}'.format(expense_column, last_expense_line+2), 
                                              '{}=-SUM({}2:{}{}/D2:D{}*B2:B{})+SUMIF(C2:C{},{}1,B2:B{}){}'.format('{', 
                                                                                                                  expense_column, 
                                                                                                                  expense_column, 
                                                                                                                  last_expense_line, 
                                                                                                                  last_expense_line, 
                                                                                                                  last_expense_line, 
                                                                                                                  last_expense_line, 
                                                                                                                  expense_column, 
                                                                                                                  last_expense_line, '}'), 
                                              self.parent.money)
    expense_column_number += 1
    expense_column = xlsxwriter.utility.xl_col_to_name(expense_column_number)
# Write our checksum balance values into the oracle spreadsheet
self.parent.worksheet.write(last_expense_line + 3, 0, 
                            'Oracle CheckSum balance', 
                            self.parent.bold)
participant_column_number = 4
while participant_column_number < len(self.parent.disallowed_name_list) + 4:
    participant_column = xlsxwriter.utility.xl_col_to_name(participant_column_number)
    self.parent.worksheet.write(last_expense_line + 3, 
                                participant_column_number, 
                                '={}{}'.format(participant_column, last_expense_line + 2), 
                                self.parent.money)
    print ('the oracle checksum is being written into: ',
           '{}{}'.format(participant_column, 
                         last_expense_line + 4))
    participant_column_number += 1
self.parent.workbook.close()

time.sleep(2)

# Check the balance results in our spreadsheet oracle against the balance calculations in the tricount WebApp
# Read the balance results using xlwings python library
self.parent.app = xlwings.App(visible=False)
print('{}.xlsx'.format(self.parent.new_account_name))
oracle_workbook = xlwings.Book('{}.xlsx'.format(self.parent.new_account_name))
WorkSheetTab = oracle_workbook.sheets['Sheet1']
participant_column_number = 4
while participant_column_number < len(self.parent.disallowed_name_list) + 4:
    participant_column = xlsxwriter.utility.xl_col_to_name(participant_column_number)
    participant_balance = abs(round(WorkSheetTab.range('{}{}'.format(participant_column, 
                                                                     last_expense_line + 4)).value, 2))
    print ('Oracle balance value = ',
           participant_balance, 
           'of type ', 
           type(participant_balance))
    splitme_app_balance = self.parent.test_if.perform_action('get_value', 'balance_by_amount', 
                                                             text='{}'.format(participant_balance))
    print(type(splitme_app_balance))
    splitme_app_balance_value = unicodedata.normalize('NFKD',splitme_app_balance).encode('ascii','replace')
    splitme_app_balance_value = splitme_app_balance_value.replace('?','')
    print(splitme_app_balance_value, type(splitme_app_balance_value))
    splitme_app_balance_value = abs(round(float(splitme_app_balance_value), 2))
    print('The App balance value = ',splitme_app_balance_value, 'of type', type(splitme_app_balance_value))
    participant_column_number += 1
    try:
        # Test that splitme_app_balance_value and participant_balance are equal. 
        # If the values do not compare equal, the test will fail.
        self.parent.assertions.assertEqual(splitme_app_balance_value,
                                           participant_balance,
                                           msg='***TEST HAS FAILED*** Assertion check fired')
    except AssertionError as err:
        print('Participant balance calculated by App is incorrect')
        print(err)
        self.info('Info: {}'.format(err))
        self.error_continue('Participant balance value difference: {}'
                            .format(splitme_app_balance_value-participant_balance))
        return False
print ('Participant Balance is correct, ***TEST HAS PASSED***')
oracle_workbook.close()

22.10. Activity_End

1
2
self.app.kill()
self.driver.quit()