year = 2021;" to the new year. // Run: ./run_selenium.sh --filter CAPayrollDeductionCRACompareTest::testCRAToCSVFile <-- This will add lines to the above CSV file once its complete. // Run: ./run_selenium.sh --filter CAPayrollDeductionCRACompareTest::testCRAFromCSVFile <-- This will test the PDOC numbers against our own. /** * @group CAPayrollDeductionCRACompareTest */ class CAPayrollDeductionCRACompareTest extends PHPUnit\Extensions\Selenium2TestCase { private $default_wait_timeout = 4000;//100000; function waitUntilByXPath( $xpath, $timeout = null ) { if ( $timeout == null ) { $timeout = $this->default_wait_timeout; } $this->waitUntil( function () use ( $xpath ) { try { $element = $this->byXPath( $xpath ); if ( $element->displayed() ) { return true; } } catch ( PHPUnit_Extensions_Selenium2TestCase_WebDriverException $e ) { } return null; }, $timeout ); } public function setUp(): void { global $selenium_config; $this->selenium_config = $selenium_config; Debug::text( 'Running setUp(): ', __FILE__, __LINE__, __METHOD__, 10 ); require_once( Environment::getBasePath() . '/classes/payroll_deduction/PayrollDeduction.class.php' ); $this->year = 2022; $this->tax_table_file = dirname( __FILE__ ) . '/../payroll_deduction/CAPayrollDeductionTest' . $this->year . '.csv'; $this->cra_deduction_test_csv_file = dirname( $this->tax_table_file ) . DIRECTORY_SEPARATOR . 'CAPayrollDeductionCRATest' . $this->year . '.csv'; $this->company_id = PRIMARY_COMPANY_ID; $this->selenium_test_case_runs = 0; TTDate::setTimeZone( 'Etc/GMT+8', true ); //Due to being a singleton and PHPUnit resetting the state, always force the timezone to be set. $this->setHost( $selenium_config['host'] ); $this->setBrowser( $selenium_config['browser'] ); $this->setBrowserUrl( $selenium_config['default_url'] ); } public function tearDown(): void { Debug::text( 'Running tearDown(): ', __FILE__, __LINE__, __METHOD__, 10 ); } function CRAPayrollDeductionOnlineCalculator( $args = [] ) { if ( ENABLE_SELENIUM_REMOTE_TESTS != true ) { return false; } Debug::Arr( $args, 'Args: ', __FILE__, __LINE__, __METHOD__, 10 ); if ( count( $args ) == 0 ) { return false; } try { if ( $this->selenium_test_case_runs == 0 ) { $url = 'https://www.canada.ca/en/revenue-agency/services/e-services/e-services-businesses/payroll-deductions-online-calculator.html'; Debug::text( 'Navigating to URL: ' . $url, __FILE__, __LINE__, __METHOD__, 10 ); $this->url( $url ); //Click "I Accept" $this->waitUntilByXPath( '//a[contains(.,\'I accept\')]' ); $ae = $this->byXPath( '//a[contains(.,\'I accept\')]' ); Debug::text( 'Active Element Text: ' . $ae->text(), __FILE__, __LINE__, __METHOD__, 10 ); $ae->click(); $this->waitUntilByXPath( '//*[@id="welcome_button_next"]' ); $ae = $this->byXPath( '//*[@id="welcome_button_next"]' ); Debug::text( 'Active Element Text: ' . $ae->text(), __FILE__, __LINE__, __METHOD__, 10 ); $ae->click(); } else { usleep( 500000 ); $this->waitUntilByXPath( '//*[@id="payrollDeductionsResults_button_modifyCalculationButton"]' ); $ae = $this->byXPath( '//*[@id="payrollDeductionsResults_button_modifyCalculationButton"]' ); //Modify the current calculation Debug::text( 'Active Element Text: ' . $ae->text(), __FILE__, __LINE__, __METHOD__, 10 ); $ae->click(); } $province_options = [ 'AB' => 'ALBERTA', 'BC' => 'BRITISH_COLUMBIA', 'SK' => 'SASKATCHEWAN', 'MB' => 'MANITOBA', 'QC' => 'QUEBEC', 'ON' => 'ONTARIO', 'NL' => 'NEWFOUNDLAND_AND_LABRADOR', 'NB' => 'NEW_BRUNSWICK', 'NS' => 'NOVA_SCOTIA', 'PE' => 'PRINCE_EDWARD_ISLAND', 'NT' => 'NORTHWEST_TERRITORIES', 'YT' => 'YUKON', 'NU' => 'NUNAVUT', ]; Debug::Arr( Option::getByKey( $args['province'], $province_options ), 'Attempting to Select Province Value: ', __FILE__, __LINE__, __METHOD__, 10 ); $this->waitUntilByXPath( '//*[@id="jurisdiction"]' ); $ae = $this->byId( 'jurisdiction' ); $this->select( $ae )->selectOptionByValue( Option::getByKey( $args['province'], $province_options ) ); $pp_options = [ 52 => 'WEEKLY_52PP', 26 => 'BI_WEEKLY', 24 => 'SEMI_MONTHLY', ]; $ae = $this->byId( 'payPeriodFrequency' ); $this->select( $ae )->selectOptionByValue( Option::getByKey( $args['pay_period_schedule'], $pp_options ) ); $ae = $this->byId( 'datePaidYear' ); $this->select( $ae )->selectOptionByLabel( date( 'Y', $args['date'] ) ); $ae = $this->byId( 'datePaidMonth' ); $this->select( $ae )->selectOptionByLabel( date( 'm', $args['date'] ) ); //Leading 0 $ae = $this->byId( 'datePaidDay' ); $this->select( $ae )->selectOptionByLabel( date( 'd', $args['date'] ) ); //Leading 0 $ae = $this->byId( 'payrollDeductionsStep1_button_next' ); Debug::text( 'Active Element Text: ' . $ae->text(), __FILE__, __LINE__, __METHOD__, 10 ); $ae->click(); $this->waitUntilByXPath( '//*[@id="incomeAmount"]' ); usleep( 500000 ); $ae = $this->byId( 'incomeAmount' ); $ae->click(); $this->keys( $args['gross_income'] ); //Sometimes some keystrokes get missed, try putting a wait above here. $ae = $this->byId( 'payrollDeductionsStep2a_button_next' ); Debug::text( 'Active Element Text: ' . $ae->text(), __FILE__, __LINE__, __METHOD__, 10 ); $ae->click(); //$this->waitUntilByXPath( '//*[@id="federalClaimCode"]' ); //if ( isset( $args['federal_claim'] ) ) { // $ae = $this->byId( 'federalClaimCode' ); // $this->select( $ae )->selectOptionByValue( ( $args['federal_claim'] == 0 ? 'CLAIM_CODE_0' : 'CLAIM_CODE_1' ) ); //Only support 0=$1, 1=Basic Claim //} // //if ( isset( $args['provincial_claim'] ) && $args['province'] != 'QC' ) { //QC doesn't have provincial claim code. // $ae = $this->byId( 'provinceTerritoryClaimCode' ); // $this->select( $ae )->selectOptionByValue( ( $args['provincial_claim'] == 0 ? 'CLAIM_CODE_0' : 'CLAIM_CODE_1' ) ); //Only support 0=$1, 1=Basic Claim //} if ( isset( $args['federal_claim'] ) ) { $this->waitUntilByXPath( '//*[@id="td1ClaimAmountFed"]' ); $ae = $this->byId( 'td1ClaimAmountFed' ); $ae->click(); $this->keys( $args['federal_claim'] ); //Sometimes some keystrokes get missed, try putting a wait above here. } if ( isset( $args['provincial_claim'] ) && $args['province'] != 'QC' ) { $this->waitUntilByXPath( '//*[@id="td1ClaimAmountProv"]' ); $ae = $this->byId( 'td1ClaimAmountProv' ); $ae->click(); $this->keys( $args['provincial_claim'] ); //Sometimes some keystrokes get missed, try putting a wait above here. } $result_row_offset = 1; if ( isset( $args['ytd_cpp_earnings'] ) ) { $ae = $this->byId( 'pensionableEarningsYearToDate' ); $ae->click(); $this->keys( $args['ytd_cpp_earnings'] ); } if ( isset( $args['ytd_cpp'] ) ) { $ae = $this->byId( 'cppOrQppContributionsDeductedYearToDate' ); $ae->click(); $this->keys( $args['ytd_cpp'] ); } if ( isset( $args['ytd_ei_earnings'] ) ) { $ae = $this->byId( 'insurableEarningsYearToDate' ); $ae->click(); $this->keys( $args['ytd_ei_earnings'] ); } if ( isset( $args['ytd_ei'] ) ) { $ae = $this->byId( 'employmentInsuranceDeductedYearToDate' ); $ae->click(); $this->keys( $args['ytd_ei'] ); } usleep( 500000 ); $ae = $this->byId( 'payrollDeductionsStep3_button_calculate' ); Debug::text( 'Active Element Text: ' . $ae->text(), __FILE__, __LINE__, __METHOD__, 10 ); $ae->click(); // //Handle results here // $screenshot_file_name = '/tmp/cra_result_screenshot-' . $args['province'] . '-' . $args['federal_claim'] . '-' . $args['provincial_claim'] . '-' . $args['gross_income'] . '.png'; file_put_contents( $screenshot_file_name, $this->currentScreenshot() ); //Make sure the gross income matches first. $ae = $this->byXPath( '/html/body/div/div/main/section[2]/table[1]/tbody/tr[1]/td[1]' ); //Was: 1 Debug::Text( 'AE Text (Gross Income) [1]: ' . $ae->text(), __FILE__, __LINE__, __METHOD__, 10 ); $ae = $this->byXPath( '/html/body/div/div/main/section[2]/table[1]/tbody/tr[1]/td[3]' ); Debug::Text( 'AE Text (Gross Income) [1]: ' . $ae->text() . ' Expecting: ' . $args['gross_income'], __FILE__, __LINE__, __METHOD__, 10 ); //$retarr['gross_inc'] = TTi18n::parseFloat( $ae->text() ); $this->assertEquals( TTi18n::parseFloat( $ae->text() ), $args['gross_income'] ); $result_row_offset += 5; //Federal Tax $ae = $this->byXPath( '/html/body/div/div/main/section[2]/table[1]/tbody/tr[' . $result_row_offset . ']' ); //Was: 7 Debug::Text( 'AE Text (Federal) [' . $result_row_offset . ']: ' . $ae->text(), __FILE__, __LINE__, __METHOD__, 10 ); $ae = $this->byXPath( '/html/body/div/div/main/section[2]/table[1]/tbody/tr[' . $result_row_offset . ']/td[2]' ); Debug::Text( 'AE Text (Federal) [' . $result_row_offset . ']: ' . $ae->text(), __FILE__, __LINE__, __METHOD__, 10 ); $retarr['federal_deduction'] = TTi18n::parseFloat( $ae->text() ); $result_row_offset += 1; //Provincial Tax $ae = $this->byXPath( '/html/body/div/div/main/section[2]/table[1]/tbody/tr[' . $result_row_offset . ']' ); //Was: 8 Debug::Text( 'AE Text (Province) [' . $result_row_offset . ']: ' . $ae->text(), __FILE__, __LINE__, __METHOD__, 10 ); $ae = $this->byXPath( '/html/body/div/div/main/section[2]/table[1]/tbody/tr[' . $result_row_offset . ']/td[2]' ); Debug::Text( 'AE Text (Province) [' . $result_row_offset . ']: ' . $ae->text(), __FILE__, __LINE__, __METHOD__, 10 ); $retarr['provincial_deduction'] = TTi18n::parseFloat( $ae->text() ); $result_row_offset += 2; //CPP $ae = $this->byXPath( "/html/body/div/div/main/section[2]/table[1]/tbody/tr[" . $result_row_offset . "]" ); Debug::Text( 'AE Text (CPP) [' . $result_row_offset . ']: ' . $ae->text(), __FILE__, __LINE__, __METHOD__, 10 ); $ae = $this->byXPath( "/html/body/div/div/main/section[2]/table[1]/tbody/tr[" . $result_row_offset . "]/td[3]" ); Debug::Text( 'AE Text (CPP) [' . $result_row_offset . ']: ' . $ae->text(), __FILE__, __LINE__, __METHOD__, 10 ); $retarr['cpp_deduction'] = TTi18n::parseFloat( $ae->text() ); $result_row_offset += 1; //EI $ae = $this->byXPath( "/html/body/div/div/main/section[2]/table[1]/tbody/tr[" . $result_row_offset . "]" ); Debug::Text( 'AE Text (EI) [' . $result_row_offset . ']: ' . $ae->text(), __FILE__, __LINE__, __METHOD__, 10 ); $ae = $this->byXPath( "/html/body/div/div/main/section[2]/table[1]/tbody/tr[" . $result_row_offset . "]/td[3]" ); Debug::Text( 'AE Text (EI) [' . $result_row_offset . ']: ' . $ae->text(), __FILE__, __LINE__, __METHOD__, 10 ); $retarr['ei_deduction'] = TTi18n::parseFloat( $ae->text() ); //Debug::Arr( $this->source(), 'Raw Source: ', __FILE__, __LINE__, __METHOD__, 10); //sleep(5); $this->selenium_test_case_runs++; } catch ( Exception $e ) { Debug::Text( 'Exception: ' . $e->getMessage(), __FILE__, __LINE__, __METHOD__, 10 ); file_put_contents( tempnam( '/tmp/', 'cra_result_screenshot_exception' ) . '.png', $this->currentScreenshot() ); sleep( 15 ); } if ( isset( $retarr ) ) { Debug::Arr( $retarr, 'Retarr: ', __FILE__, __LINE__, __METHOD__, 10 ); return $retarr; } Debug::Text( 'ERROR: Returning FALSE!', __FILE__, __LINE__, __METHOD__, 10 ); return false; } //Simple control test to ensure the numbers match for the previous year. function testCRAControlCurrentYear() { $pd_obj = new PayrollDeduction( 'CA', 'BC' ); $pd_obj->setDate( TTDate::getEndYearEpoch( ( TTDate::getBeginYearEpoch() - 86400 ) ) ); $pd_obj->setEnableCPPAndEIDeduction( true ); //Deduct CPP/EI. $pd_obj->setAnnualPayPeriods( 26 ); $pd_obj->setFederalTotalClaimAmount( 1 ); //1=Basic Claim Amount $pd_obj->setProvincialTotalClaimAmount( 1 ); //1=Basic Claim Amount $pd_obj->setEIExempt( false ); $pd_obj->setCPPExempt( false ); $pd_obj->setFederalTaxExempt( false ); $pd_obj->setProvincialTaxExempt( false ); $pd_obj->setYearToDateCPPContribution( 0 ); $pd_obj->setYearToDateEIContribution( 0 ); $pd_obj->setGrossPayPeriodIncome( 9933.99 ); $args = [ 'date' => strtotime( $pd_obj->getDate() ), //Must be epoch. 'province' => $pd_obj->getProvince(), 'pay_period_schedule' => $pd_obj->getAnnualPayPeriods(), 'federal_claim' => $pd_obj->getFederalTotalClaimAmount(), 'provincial_claim' => $pd_obj->getProvincialTotalClaimAmount(), 'gross_income' => $pd_obj->getGrossPayPeriodIncome(), ]; $retarr = $this->CRAPayrollDeductionOnlineCalculator( $args ); $this->assertEquals( $this->mf( $pd_obj->getFederalPayPeriodDeductions() ), $retarr['federal_deduction'] ); $this->assertEquals( $this->mf( $pd_obj->getProvincialPayPeriodDeductions() ), $retarr['provincial_deduction'] ); $this->assertEquals( $this->mf( $pd_obj->getEmployeeCPP() ), $retarr['cpp_deduction'] ); $this->assertEquals( $this->mf( $pd_obj->getEmployeeEI() ), $retarr['ei_deduction'] ); return true; } //Simple control test to ensure the numbers match for the next year. // Since this would normally be run sometimes in December, this will compare the values for the upcoming tax year. function testCRAControlNextYear() { $pd_obj = new PayrollDeduction( 'CA', 'BC' ); $pd_obj->setDate( ( TTDate::getEndYearEpoch( time() ) + 86400 ) ); //**NOTE: this will fail if its run in the most recent tax year. $pd_obj->setEnableCPPAndEIDeduction( true ); //Deduct CPP/EI. $pd_obj->setAnnualPayPeriods( 26 ); $pd_obj->setFederalTotalClaimAmount( 1 ); //1=Basic Claim Amount $pd_obj->setProvincialTotalClaimAmount( 1 ); //1=Basic Claim Amount $pd_obj->setEIExempt( false ); $pd_obj->setCPPExempt( false ); $pd_obj->setFederalTaxExempt( false ); $pd_obj->setProvincialTaxExempt( false ); $pd_obj->setYearToDateCPPContribution( 0 ); $pd_obj->setYearToDateEIContribution( 0 ); $pd_obj->setGrossPayPeriodIncome( 9933.99 ); $args = [ 'date' => strtotime( $pd_obj->getDate() ), //Must be epoch. 'province' => $pd_obj->getProvince(), 'pay_period_schedule' => $pd_obj->getAnnualPayPeriods(), 'federal_claim' => $pd_obj->getFederalTotalClaimAmount(), 'provincial_claim' => $pd_obj->getProvincialTotalClaimAmount(), 'gross_income' => $pd_obj->getGrossPayPeriodIncome(), ]; $retarr = $this->CRAPayrollDeductionOnlineCalculator( $args ); $this->assertEquals( $this->mf( $pd_obj->getFederalPayPeriodDeductions() ), $retarr['federal_deduction'] ); $this->assertEquals( $this->mf( $pd_obj->getProvincialPayPeriodDeductions() ), $retarr['provincial_deduction'] ); $this->assertEquals( $this->mf( $pd_obj->getEmployeeCPP() ), $retarr['cpp_deduction'] ); $this->assertEquals( $this->mf( $pd_obj->getEmployeeEI() ), $retarr['ei_deduction'] ); return true; } public function mf( $amount ) { return Misc::MoneyRound( $amount ); } function testCRAToCSVFile() { $this->assertEquals( true, file_exists( $this->tax_table_file ) ); if ( file_exists( $this->cra_deduction_test_csv_file ) ) { $file = new SplFileObject( $this->cra_deduction_test_csv_file, 'r' ); $file->seek( PHP_INT_MAX ); $total_compare_lines = $file->key() + 1; unset( $file ); Debug::text( 'Found existing CRATest file to resume with lines: ' . $total_compare_lines, __FILE__, __LINE__, __METHOD__, 10 ); } $test_rows = Misc::parseCSV( $this->tax_table_file, true ); $total_rows = ( count( $test_rows ) + 1 ); $i = 2; foreach ( $test_rows as $row ) { if ( isset( $total_compare_lines ) && $i < $total_compare_lines ) { Debug::text( ' Skipping to line: ' . $total_compare_lines . '/' . $i, __FILE__, __LINE__, __METHOD__, 10 ); $i++; continue; } Debug::text( 'Province: ' . $row['province'] . ' Income: ' . $row['gross_income'], __FILE__, __LINE__, __METHOD__, 10 ); if ( isset( $row['gross_income'] ) && isset( $row['low_income'] ) && isset( $row['high_income'] ) && $row['gross_income'] == '' && $row['low_income'] != '' && $row['high_income'] != '' ) { $row['gross_income'] = ( $row['low_income'] + ( ( $row['high_income'] - $row['low_income'] ) / 2 ) ); } if ( $row['country'] != '' && $row['gross_income'] != '' ) { //echo $i.'/'.$total_rows.'. Testing Province: '. $row['province'] .' Income: '. $row['gross_income'] ."\n"; Debug::text( $i . '/' . $total_rows . '. Testing Province: ' . $row['province'] . ' Income: ' . $row['gross_income'], __FILE__, __LINE__, __METHOD__, 10 ); $args = [ 'date' => strtotime( $row['date'] ), 'province' => $row['province'], 'pay_period_schedule' => 26, 'federal_claim' => $this->mf( $row['federal_claim'] ), 'provincial_claim' => $this->mf( $row['provincial_claim'] ), 'gross_income' => $this->mf( $row['gross_income'] ), ]; //Debug::Arr( $row, 'aFinal Row: ', __FILE__, __LINE__, __METHOD__, 10); $tmp_cra_data = $this->CRAPayrollDeductionOnlineCalculator( $args ); if ( is_array( $tmp_cra_data ) ) { $retarr[] = array_merge( $row, $tmp_cra_data ); //Debug::Arr( $retarr, 'bFinal Row: ', __FILE__, __LINE__, __METHOD__, 10); //sleep(2); //Should we be friendly to the Gov't server? // if ( $i > 5 ) { // break; // } } else { Debug::text( 'ERROR! Data from CRA is invalid!', __FILE__, __LINE__, __METHOD__, 10 ); break; } } $i++; } if ( isset( $retarr ) ) { //generate column array. $column_keys = array_keys( $retarr[0] ); foreach ( $column_keys as $column_key ) { $columns[$column_key] = $column_key; } //var_dump($test_data); //var_dump($retarr); //echo Misc::Array2CSV( $retarr, $columns, FALSE, TRUE ); file_put_contents( $this->cra_deduction_test_csv_file, Misc::Array2CSV( $retarr, $columns, false, true ), FILE_APPEND ); //Make sure all rows are tested. $this->assertEquals( $total_rows, ( $i - 1 ) ); } else { $this->assertEquals( true, false ); } } function testCRAFromCSVFile() { $this->assertEquals( true, file_exists( $this->cra_deduction_test_csv_file ) ); $test_rows = Misc::parseCSV( $this->cra_deduction_test_csv_file, true ); $total_rows = ( count( $test_rows ) + 1 ); $i = 2; foreach ( $test_rows as $row ) { //Debug::text('Province: '. $row['province'] .' Income: '. $row['gross_income'], __FILE__, __LINE__, __METHOD__, 10); if ( isset( $row['gross_income'] ) && isset( $row['low_income'] ) && isset( $row['high_income'] ) && $row['gross_income'] == '' && $row['low_income'] != '' && $row['high_income'] != '' ) { $row['gross_income'] = ( $row['low_income'] + ( ( $row['high_income'] - $row['low_income'] ) / 2 ) ); } if ( $row['country'] != '' && $row['gross_income'] != '' ) { //echo $i.'/'.$total_rows.'. Testing Province: '. $row['province'] .' Income: '. $row['gross_income'] ."\n"; $pd_obj = new PayrollDeduction( $row['country'], $row['province'] ); $pd_obj->setDate( strtotime( $row['date'] ) ); $pd_obj->setEnableCPPAndEIDeduction( true ); //Deduct CPP/EI. $pd_obj->setAnnualPayPeriods( $row['pay_periods'] ); $pd_obj->setFederalTotalClaimAmount( $row['federal_claim'] ); //Amount from 2005, Should use amount from 2007 automatically. $pd_obj->setProvincialTotalClaimAmount( $row['provincial_claim'] ); $pd_obj->setEIExempt( false ); $pd_obj->setCPPExempt( false ); $pd_obj->setFederalTaxExempt( false ); $pd_obj->setProvincialTaxExempt( false ); $pd_obj->setYearToDateCPPContribution( 0 ); $pd_obj->setYearToDateEIContribution( 0 ); $pd_obj->setGrossPayPeriodIncome( $this->mf( $row['gross_income'] ) ); $this->assertEquals( $this->mf( $pd_obj->getGrossPayPeriodIncome() ), $this->mf( $row['gross_income'] ) ); if ( $row['federal_deduction'] != '' ) { $this->assertEqualsWithDelta( (float)$this->mf( $row['federal_deduction'] ), (float)$this->mf( $pd_obj->getFederalPayPeriodDeductions() ), 0.015, 'I: '. $i .' Gross Income: '. $row['gross_income'] .' Province: '. $row['province'] .' Federal Claim: '. $row['federal_claim'] ); //0.015=Allowed Delta } if ( $row['provincial_deduction'] != '' ) { $this->assertEqualsWithDelta( (float)$this->mf( $row['provincial_deduction'] ), (float)$this->mf( $pd_obj->getProvincialPayPeriodDeductions() ), 0.015, 'I: '. $i .' Gross Income: '. $row['gross_income'] .' Province: '. $row['province'] .' Provincial Claim: '. $row['provincial_claim'] ); //0.015=Allowed Delta } } $i++; } //Make sure all rows are tested. $this->assertEquals( $total_rows, ( $i - 1 ) ); } } ?>