TimeTrex Community Edition v16.2.0

This commit is contained in:
2022-12-13 07:10:06 +01:00
commit 472f000c1b
6810 changed files with 2636142 additions and 0 deletions

View File

@ -0,0 +1,522 @@
<?php /** @noinspection PhpMissingDocCommentInspection */
/*********************************************************************************
*
* TimeTrex is a Workforce Management program developed by
* TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
*
* You should have received a copy of the GNU Affero General Public License along
* with this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
*
* You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
* #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
*
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by TimeTrex" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by TimeTrex".
*
********************************************************************************/
//Each Year:
// Copy testcases/payroll_deduction/CAPayrollDeductionCRATest2019.csv to the new year. Clear out all lines but the header.
// Update below "$this->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 ) );
}
}
?>

View File

@ -0,0 +1,90 @@
<?php /** @noinspection PhpMissingDocCommentInspection */
/*********************************************************************************
*
* TimeTrex is a Workforce Management program developed by
* TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
*
* You should have received a copy of the GNU Affero General Public License along
* with this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
*
* You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
* #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
*
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by TimeTrex" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by TimeTrex".
*
********************************************************************************/
require_once( 'TTSeleniumGlobal.php' );
/**
* @group UI
*/
class UILanguageTest extends TTSeleniumGlobal {
public function setUp(): void {
parent::setUp();
}
public function tearDown(): void {
parent::tearDown();
}
public function setUpPage() {
//$this->currentWindow()->maximize();
$this->currentWindow()->size( [ 'width' => $this->width, 'height' => $this->height ] );
}
function testUILanguage() {
$user = 'demoadmin2';
$pass = 'demo.de';
$this->goToLogin( $user );
$this->changeLanguageLogin( 'es' );
sleep( 5 ); //Sleep not ideal but waits for language to change.
$this->assertEquals( 'Logueo Seguro', $this->byId( 'secure-login-text' )->text() );
$this->Login( $user, $pass );
$this->waitForUIInitComplete();
$this->assertEquals( 'Panel de control', $this->byId( 'main-menu-link-home' )->text() );
$this->changeLanguagePreference( 'en' );
$this->waitForUIInitComplete();
$this->assertEquals( 'Dashboard', $this->byId( 'main-menu-link-home' )->text() );
$this->changeLanguagePreference( 'es' );
$this->waitForUIInitComplete();
$this->assertEquals( 'Panel de control', $this->byId( 'main-menu-link-home' )->text() );
$this->changeLanguagePreference( 'en' );
$this->waitForUIInitComplete();
$this->assertEquals( 'Dashboard', $this->byId( 'main-menu-link-home' )->text() );
}
}
?>

View File

@ -0,0 +1,134 @@
<?php /** @noinspection PhpMissingDocCommentInspection */
/*********************************************************************************
*
* TimeTrex is a Workforce Management program developed by
* TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
*
* You should have received a copy of the GNU Affero General Public License along
* with this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
*
* You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
* #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
*
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by TimeTrex" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by TimeTrex".
*
********************************************************************************/
require_once( 'TTSeleniumGlobal.php' );
/**
* @group UI
*/
class UILoginTest extends TTSeleniumGlobal {
public function setUp(): void {
parent::setUp();
}
public function tearDown(): void {
parent::tearDown();
}
public function setUpPage() {
//$this->currentWindow()->maximize();
$this->currentWindow()->size( [ 'width' => $this->width, 'height' => $this->height ] );
}
function testUILoginLogout() {
$this->Login( 'demoadmin2', 'demo.de' );
$this->Logout();
}
// function testEditUser() {
// //TODO: Use input field names/ids rather then positions or xpath indexes.
// $this->Login();
//
// //Go to employee list
// $this->getText('xpath=//div[@id=\'ribbon\']/ul/li[11]/a');
// $this->click('link=Employee');
// $this->click('css=#Employee > img');
// $this->waitForAttribute( 'css=div.view@init_complete' );
//
// //Add new employee.
// $this->getText('xpath=//div[@id=\'ribbon\']/ul/li[11]/a');
// $this->click('css=#addIcon > img');
// $this->waitForAttribute( 'css=div.view@init_complete' );
//
// //Enter employee information.
// $this->type('xpath=id(\'tab0_content_div\')/div[1]/div[13]/div[2]/input', 'selenium.test');
// $this->fireEvent('xpath=id(\'tab0_content_div\')/div[1]/div[13]/div[2]/input', 'keyup');
// $this->waitForElementPresent('xpath=id(\'tab0_content_div\')/div[1]/div[13]/div[2]/input[contains(@class,\'error-tip\')]', FALSE );
//
// $this->type('xpath=id(\'tab0_content_div\')/div[1]/div[15]/div[2]/input', 'demo');
// $this->fireEvent('xpath=id(\'tab0_content_div\')/div[1]/div[15]/div[2]/input', 'keyup');
// $this->waitForElementPresent('xpath=id(\'tab0_content_div\')/div[1]/div[15]/div[2]/input[contains(@class,\'error-tip\')]', FALSE );
//
// $this->type('xpath=id(\'tab0_content_div\')/div[1]/div[17]/div[2]/input', 'demo');
// $this->fireEvent('xpath=id(\'tab0_content_div\')/div[1]/div[17]/div[2]/input', 'keyup');
// $this->waitForElementPresent('xpath=id(\'tab0_content_div\')/div[1]/div[17]/div[2]/input[contains(@class,\'error-tip\')]', FALSE );
//
// $this->type('xpath=(//input[@type=\'text\'])[12]', 'selenium');
// $this->fireEvent('xpath=(//input[@type=\'text\'])[12]', 'keyup');
// $this->type('xpath=(//input[@type=\'text\'])[13]', 'test');
// $this->fireEvent('xpath=(//input[@type=\'text\'])[13]', 'keyup');
// $this->waitForElementPresent('xpath=(//input[@type=\'text\'])[13][contains(@class,\'error-tip\')]', FALSE );
//
// $this->waitForElementPresent('xpath=id(\'EmployeeContextMenu\')/div/div/div[1]/ul/li[8][contains(@class,\'disable-image\')]', FALSE );
// $this->waitForAttribute( 'css=div.edit-view@validate_complete' );
//
// //Save employee
// $this->click('xpath=id(\'EmployeeContextMenu\')/div/div/div[1]/ul/li[8]');
// $this->waitForElementPresent('css=div.popup-loading' );
// $this->waitForElementPresent('css=div.edit-view', FALSE );
// $this->waitForAttribute( 'css=div.view@init_complete' );
//
// //Search for newly created user
// $this->click('link=BASIC SEARCH');
// $this->waitForElementPresent('div.ui-tabs-hide', FALSE );
// $this->type('css=input.t-text-input', 'selenium');
// $this->click('id=searchBtn');
// $this->waitForAttribute( 'css=div.search-panel@search_complete' );
//
// //Select employee
// $this->uncheck('xpath=//input[contains(@id,\'jqg_employee_view_container_\')]');
// $this->click('xpath=//input[contains(@id,\'jqg_employee_view_container_\')]');
// $this->waitForElementPresent('xpath=id(\'EmployeeContextMenu\')/div/div/div[1]/ul/li[5][contains(@class,\'disable-image\')]', FALSE );
//
// //Delete employee
// $this->click('id=deleteIcon');
// $this->isElementPresent('css=div.confirm-alert');
//
// //Confirm delete
// $this->click('id=yesBtn');
// $this->isElementPresent('css=div.no-result-div');
//
//
// $this->Logout();
// }
}
?>

View File

@ -0,0 +1,77 @@
<?php
/*********************************************************************************
*
* TimeTrex is a Workforce Management program developed by
* TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
*
* You should have received a copy of the GNU Affero General Public License along
* with this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
*
* You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
* #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
*
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by TimeTrex" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by TimeTrex".
*
********************************************************************************/
require_once( 'TTSeleniumGlobal.php' );
/**
* @group UI
*/
class UIMiscTest extends TTSeleniumGlobal {
public function setUpPage() {
//$this->currentWindow()->maximize();
$this->currentWindow()->size( [ 'width' => $this->width, 'height' => $this->height ] );
}
function testUIGetGlobalVariable() {
$this->Login( 'demoadmin2', 'demo.de' );
$javascript = [ 'script' => 'return Global.getUIReadyStatus();', 'args' => [] ];
$var = $this->execute( $javascript );
Debug::text( 'Global variable 1 retrieved: ' . print_r( $var, true ), __FILE__, __LINE__, __METHOD__, 10 );
$this->waitForUIInitComplete();
$this->clickMainMenuItem( 'Attendance' );
$this->clickMainMenuItem( 'TimeSheet' );
$this->waitForUIInitComplete();
$javascript = [ 'script' => 'return Global.UIReadyStatus;', 'args' => [] ];
$var2 = $this->execute( $javascript );
Debug::text( 'Global variable 2 retrieved: ' . print_r( $var, true ), __FILE__, __LINE__, __METHOD__, 10 );
$this->assertNotEmpty( $var );
$this->assertNotEmpty( $var2 );
$this->Logout();
}
}
?>

View File

@ -0,0 +1,80 @@
<?php /** @noinspection PhpMissingDocCommentInspection */
/*********************************************************************************
*
* TimeTrex is a Workforce Management program developed by
* TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
*
* You should have received a copy of the GNU Affero General Public License along
* with this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
*
* You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
* #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
*
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by TimeTrex" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by TimeTrex".
*
********************************************************************************/
require_once( 'TTSeleniumGlobal.php' );
use \PHPUnit\Extensions\Selenium2TestCase\Keys as Keys;
/**
* @group UI
*/
class UIQUnitTest extends TTSeleniumGlobal {
public function setUp(): void {
parent::setUp();
}
public function tearDown(): void {
parent::tearDown();
}
public function setUpPage() {
//$this->currentWindow()->maximize();
$this->currentWindow()->size( [ 'width' => $this->width, 'height' => $this->height ] );
}
function testUIQUnit() {
$user = 'demoadmin2';
$pass = 'demo.de';
$this->Login( $user, $pass );
//WebDriverKeys::PAGE_DOWN;
$this->keys( Keys::CONTROL . Keys::ALT . Keys::SHIFT . Keys::F12 );
$this->waitUntilByCssSelector( '#tt_debug_console #qunit_test_button' );
$this->byId( 'qunit_test_button' )->click();
$this->waitUntilByCssSelector( '#qunit-banner.qunit-pass', 60000 );
$this->assertTrue( $this->byCssSelector( '#qunit-banner.qunit-pass' )->displayed() );
}
}
?>

View File

@ -0,0 +1,782 @@
<?php /** @noinspection PhpMissingDocCommentInspection */
/*********************************************************************************
*
* TimeTrex is a Workforce Management program developed by
* TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
*
* You should have received a copy of the GNU Affero General Public License along
* with this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
*
* You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
* #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
*
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by TimeTrex" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by TimeTrex".
*
********************************************************************************/
require_once( 'TTSeleniumGlobal.php' );
/**
* @group UI
*/
class UIScreenShotTest extends TTSeleniumGlobal {
public $user_name = '';
public $screenshot_path = '';
public function setUpPage() {
//$this->currentWindow()->maximize();
$this->currentWindow()->size( [ 'width' => $this->width, 'height' => $this->height ] );
}
function testUIScreenShot() {
//$this->screenshot_path = DIRECTORY_SEPARATOR . 'home' . DIRECTORY_SEPARATOR . $this->getOSUser() . DIRECTORY_SEPARATOR . 'public_html' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . 'UIScreenShotTest' . DIRECTORY_SEPARATOR . APPLICATION_VERSION . '-' . date( 'Ymd-His' );
$this->screenshot_path = DIRECTORY_SEPARATOR . 'var' . DIRECTORY_SEPARATOR . 'www' . DIRECTORY_SEPARATOR . 'UIScreenShotTest' . DIRECTORY_SEPARATOR . $this->getOSUser() . '-' . APPLICATION_VERSION . '-' . date( 'Ymd-His' );
$user_login_info = [
'demoadmin2' => 'demo.de',
'john.doe2' => 'demo.jo',
'jane.doe2' => 'demo.ja',
];
$resolution_array = [
[ 'w' => 1920, 'h' => 1080 ],
//array( 'w' => 1440, 'h' => 900), //Not serving much purpose at this time.
[ 'w' => 1280, 'h' => 800 ],
//array( 'w' => 1366, 'h' => 768 ),
//array( 'w' => 320, 'h' => 568 ), //iphone crashes tests, can find anything (maybe because it's off screen?)
//array( 'w' => 1027, 'h' => 728 ), //too small
//array( 'w' => 1280 , 'h' => 720 ), //smallest?
];
//single entry point to make error trapping easier
try {
foreach ( $resolution_array as $resolution ) {
$this->width = $resolution['w'];
$this->height = $resolution['h'];
$win = $this->currentWindow();
$win->size( [ 'width' => $this->width, 'height' => $this->height ] );
foreach ( $user_login_info as $user => $pass ) {
Debug::Text( 'logging in as ' . $user, __FILE__, __LINE__, __METHOD__, 10 );
$this->startTesting( $user, $pass );
$this->assertEquals( true, true, 'Test Completed Successfully.' );
}
}
} catch ( Exception $e ) {
//Do not use $e->getTrace() here or there will be a very hard to diagnose infinite loop and memory exhaustion.
Debug::Text( $e->getMessage(), __FILE__, __LINE__, __METHOD__, 10 );
Debug::Arr( $e->getTraceAsString(), 'An error occcured while running automated testing. in ' . $e->getFile() . ' on line: ' . $e->getLine(), __FILE__, __LINE__, __METHOD__, 10 );
$this->waitForUIInitComplete();
$this->takeScreenshot( $this->screenshot_path . DIRECTORY_SEPARATOR . 'error.png' );
$this->assertEquals( false, true, 'The test exited with an error: ' . $e->getMessage() );
$this->quit(); //Close browser.
}
}
function startTesting( $user, $pass ) {
$this->user_name = $user;
//uncomment these to limit the tests to a specific top level menu
//$debug_menu_item = 'block'; //used for testing user and resolution loops
//$debug_menu_item = 'main-menu-link-attendance_menu';
//$debug_menu_item = 'main-menu-link-schedule_menu';
//$debug_menu_item = 'main-menu-link-employee_menu';
//$debug_menu_item = 'main-menu-link-company_menu';
//$debug_menu_item = 'main-menu-link-payroll_menu';
//$debug_menu_item = 'main-menu-link-policy_menu';
//$debug_menu_item = 'main-menu-link-invoice_menu';
//$debug_menu_item = 'main-menu-link-hr_menu';
//$debug_menu_item = 'main-menu-link-recruitment_menu';
//$debug_menu_item = 'main-menu-link-document_menu';
//$debug_menu_item = 'main-menu-link-report_menu';
//$debug_menu_item = 'main-menu-link-ui_kit';
$hit_debugger = false;
$this->Login( $user, $pass );
//In case users are set to timesheet as default screen, prevents crash
$this->waitForUIInitComplete();
$this->goToDashboard();
//Process profile menu items first.
if ( isset( $debug_menu_item ) == false || ( isset( $debug_menu_item ) == true && $debug_menu_item == 'profile_menu_items' ) ) {
$this->processProfileMenu( $this->screenshot_path . DIRECTORY_SEPARATOR . $this->width . 'x' . $this->height . DIRECTORY_SEPARATOR . $this->user_name . DIRECTORY_SEPARATOR . 'profile-menu-items' );
}
//Get the top level menu items only.
$menu_elements = $this->getArrayBySelector( '#main-menu > li > a' );
foreach ( $menu_elements as $menu_element ) {
if ( $menu_element->attribute('id') == 'main-menu-link-home' || ( isset( $debug_menu_item ) && $debug_menu_item != $menu_element->attribute( 'id' ) && $hit_debugger == false ) ) {
Debug::Text( 'Menu skipped or debug limited - B.', __FILE__, __LINE__, __METHOD__, 10 );
continue;
} else {
$hit_debugger = true; // comment this out to test just the debug item. Defualt is to test everything forward of specified debug menu item.
}
$resolution = $this->width . 'x' . $this->height;
$menu_screenshot_path = $this->screenshot_path . DIRECTORY_SEPARATOR . $resolution . DIRECTORY_SEPARATOR . $this->user_name . DIRECTORY_SEPARATOR . $menu_element->attribute( 'id' );
$screenshot_filename = $menu_screenshot_path . '.png';
$this->clickMainMenuItem( $menu_element->attribute( 'id' ) );
$this->waitForUIInitComplete();
if ( $menu_element->attribute( 'id' ) != 'main-menu-link-home' ) {
Debug::Text( 'Processing Top Level Menu Element: [' . $menu_element->attribute( 'id' ) . '] screenshot filename: ' . $screenshot_filename, __FILE__, __LINE__, __METHOD__, 10 );
$this->takeScreenshot( $screenshot_filename );
$this->processSubMenu( $menu_element, $menu_screenshot_path );
}
$this->waitForUIInitComplete();
$this->clickMainMenuItem( $menu_element->attribute( 'id' ) );
}
Debug::Text( 'logging out', __FILE__, __LINE__, __METHOD__, 10 );
$this->waitForUIInitComplete();
$this->Logout();
$this->waitForUIInitComplete();
}
function processProfileMenu( $menu_screenshot_path ) {
//Uncomment to test specific profile menu items.
//$debug_menu_item = 'profile-in-out';
$edit_only_menus = [
'profile-menu-LoginUserPreference',
'profile-menu-LoginUserContact',
'profile-menu-ChangePassword',
'profile-in-out',
'profile-help',
];
$header_icons = [
'profile-in-out',
'profile-notifications',
'profile-help',
];
//Get all the profile menu items.
$menu_elements = $this->getArrayBySelector( '#profile-menu-items > li > button' );
//Check if topbar header icons are visibiile and add them to list of menu elements to traverse.
if ( $this->isThere( '#profile-in-out' ) ) {
$menu_elements[] = $this->byId( 'profile-in-out' );
}
if ( $this->isThere( '#profile-notifications' ) ) {
$menu_elements[] = $this->byId( 'profile-notifications' );
}
if ( $this->isThere( '#profile-help' ) ) {
$menu_elements[] = $this->byId( 'profile-help' );
}
foreach ( $menu_elements as $menu_element ) {
if ( $menu_element->attribute( 'id' ) == 'profile-menu-Logout' || ( isset( $debug_menu_item ) && $debug_menu_item != $menu_element->attribute( 'id' ) ) ) {
Debug::Text( 'Skipping menu item.', __FILE__, __LINE__, __METHOD__, 10 );
continue;
}
if ( in_array( $menu_element->attribute( 'id' ), $header_icons ) ) {
$menu_element->click();
if ( $menu_element->attribute( 'id' ) == 'profile-help' ) {
if ( $this->isThere( '#profile-menu-About' ) ) {
$this->byId( 'profile-menu-About' )->click();
} else {
//User cannot view About page.
continue;
}
}
} else {
$this->clickProfileMenuItem( $menu_element->attribute( 'id' ) );
}
$this->waitForUIInitComplete();
$profile_menu_screenshot_path = $menu_screenshot_path . DIRECTORY_SEPARATOR . $menu_element->attribute( 'id' );
$profile_menu_screenshot_filename = $profile_menu_screenshot_path . '.png';
$this->waitForUIInitComplete();
Debug::Text( 'Taking screenshot for profile menu element: ' . $menu_element->attribute( 'id' ) . ' screenshot filename: ' . $profile_menu_screenshot_filename, __FILE__, __LINE__, __METHOD__, 10 );
$this->takeScreenshot( $profile_menu_screenshot_filename );
if ( in_array( $menu_element->attribute( 'id' ), $edit_only_menus ) === false ) {
$this->processContextIcons( $profile_menu_screenshot_path, $this->byId( 'profile-menu-items' ), $menu_element );
$this->processEditScreen( $profile_menu_screenshot_path, $this->byId( 'profile-menu-items' ), $menu_element, false );
} else {
$edit_view_context_menu_id = $this->getCurrentContextMenuId();
$this->processTabs( $profile_menu_screenshot_path, $menu_element->attribute( 'id' ), $edit_view_context_menu_id, true );
}
$this->sectionCleanUp( $profile_menu_screenshot_path, $this->byId( 'profile-menu-items' ), $menu_element );
}
}
function processSubMenu( $root_el, $menu_screenshot_path ) {
$sub_menu_li_id = str_replace( 'main-menu-link', 'main-menu-item', $root_el->attribute( 'id' ) );
$sub_menu_ul = $this->byCssSelector( '#' . $sub_menu_li_id . ' ul' );
//array of submenus to limit testing to
//$debug_sub_menu = array('PayPeriodSchedule');
//array of sub elements in which we do not want to click any action icons.
//these are mostly actions that have only save and cancel buttons
$no_context_menus = [
//wizards
'main-menu-link-ProcessPayrollWizard',
'main-menu-link-PayrollRemittanceAgencyEventWizardController',
'main-menu-link-ImportCSV',
];
$skip_menus = [
'main-menu-link-QuickStartWizard', //Skip as progressing this wizard modifies the data
];
$edit_only_views = [
'main-menu-link-Company',
'main-menu-link-InvoiceConfig',
'main-menu-link-RecruitmentPortalConfig',
];
$nested_sub_menu_views = [
'main-menu-link-report_employee_reports',
'main-menu-link-report_timesheet_reports',
'main-menu-link-report_payroll_reports',
'main-menu-link-report_job_tracking_reports',
'main-menu-link-report_invoice_reports',
'main-menu-link-report_tax_reports',
'main-menu-link-report_hr_reports',
];
Debug::Text( 'Processing Submenus at: #' . $root_el->attribute( 'id' ) . ' .ribbon-sub-menu-icon', __FILE__, __LINE__, __METHOD__, 10 );
//Do not select further nested menus such as Employee Reports, TimeSheet Reports, etc. Those are handled further below.
$sub_menu_elements = $sub_menu_ul->elements(
$this->using( 'css selector' )->value( 'ul:not([style="display: none;"]) > li > a' )
);
if ( count( $sub_menu_elements ) > 0 ) {
foreach ( $sub_menu_elements as $sub_menu_element ) {
if ( in_array( $sub_menu_element->attribute( 'id' ), $skip_menus ) || isset( $debug_sub_menu ) && in_array( $sub_menu_element->attribute( 'id' ), $debug_sub_menu ) ) {
Debug::Text( 'Menu item debug limited or skipped. - D.', __FILE__, __LINE__, __METHOD__, 10 );
continue;
}
Debug::Text( 'Processing Submenus for selector: ' . $sub_menu_element->attribute( 'id' ), __FILE__, __LINE__, __METHOD__, 10 );
$this->clickMainMenuItem( $sub_menu_element->attribute( 'id' ) );
Debug::Text( $sub_menu_element->attribute( 'id' ) . ' submenu clicked.', __FILE__, __LINE__, __METHOD__, 10 );
$submenu_screenshot_path = $menu_screenshot_path . DIRECTORY_SEPARATOR . $sub_menu_element->attribute( 'id' );
$submenu_screenshot_filename = $submenu_screenshot_path . '.png';
$this->waitForUIInitComplete();
Debug::Text( 'Taking screenshot for submenu element: ' . $sub_menu_element->attribute( 'id' ) . ' screenshot filename: ' . $submenu_screenshot_filename, __FILE__, __LINE__, __METHOD__, 10 );
$this->takeScreenshot( $submenu_screenshot_filename );
if ( in_array( $sub_menu_element->attribute( 'id' ), $no_context_menus ) === false && in_array( $sub_menu_element->attribute( 'id' ), $edit_only_views ) === false && in_array( $sub_menu_element->attribute( 'id' ), $nested_sub_menu_views ) === false ) {
$this->processContextIcons( $submenu_screenshot_path, $root_el, $sub_menu_element );
}
//Reports and other views are nested another layer down
if ( in_array( $sub_menu_element->attribute( 'id' ), $nested_sub_menu_views ) ) {
$parent_sub_menu_element = $sub_menu_element->byXPath( "./.." );
Debug::Text( 'Processing nested sub menus for selector: ' . $parent_sub_menu_element->attribute( 'id' ), __FILE__, __LINE__, __METHOD__, 10 );
$nested_sub_menu_elements = $parent_sub_menu_element->elements(
$this->using( 'css selector' )->value( 'ul > li > a' )
);
array_shift( $nested_sub_menu_elements ); //Remove the first element, which is the current submenu
foreach ( $nested_sub_menu_elements as $nested_sub_menu_element ) {
Debug::Text( 'Processing nested submenu menus for selector: ' . $nested_sub_menu_element->attribute( 'id' ), __FILE__, __LINE__, __METHOD__, 10 );
$this->clickMainMenuItem( $nested_sub_menu_element->attribute( 'id' ) );
Debug::Text( $nested_sub_menu_element->attribute( 'id' ) . ' nested sub menu clicked.', __FILE__, __LINE__, __METHOD__, 10 );
$report_submenu_screenshot_path = $menu_screenshot_path . DIRECTORY_SEPARATOR . $nested_sub_menu_element->attribute( 'id' );
$report_submenu_screenshot_filename = $report_submenu_screenshot_path . '.png';
$this->waitForUIInitComplete();
Debug::Text( 'Taking screenshot for nested sub menu element: ' . $nested_sub_menu_element->attribute( 'id' ) . ' screenshot filename: ' . $report_submenu_screenshot_filename, __FILE__, __LINE__, __METHOD__, 10 );
$this->takeScreenshot( $report_submenu_screenshot_filename );
$this->waitForUIInitComplete();
$this->processTabs( $report_submenu_screenshot_path, $nested_sub_menu_element, $this->getCurrentContextMenuId(), true );
$this->clickCancel( $this->getCurrentContextMenuId() );
}
} else {
$this->sectionCleanUp( $submenu_screenshot_path, $root_el, $sub_menu_element );
$this->waitForUIInitComplete();
}
//Debug::Text( 'Reset to the top-level menu: ' . $sub_menu_element->attribute('id'), __FILE__, __LINE__, __METHOD__, 10 );
//$this->clickMainMenuItem( $sub_menu_element->attribute('id') );
//$this->waitForUIInitComplete();
}
}
}
function getCurrentContextMenuId() {
if ( $this->isThere( '.edit-view .context-menu-mount-container' ) ) {
return $this->byCssSelector( '.edit-view .context-menu-mount-container' )->attribute( 'id' );
}
$this->waitUntilByCssSelector( '.context-menu-mount-container' );
return $this->byCssSelector( '.context-menu-mount-container' )->attribute( 'id' );
}
function sectionCleanUp( $submenu_screenshot_path, $root_el, $sub_menu_element ) {
$context_menu_id = $this->getCurrentContextMenuId();
//cleanup etc before going to the next submenu
switch ( $sub_menu_element->attribute( 'id' ) ) {
case 'profile-in-out':
$this->clickCancel( $context_menu_id );
$this->waitThenClick( '#yesBtn' );
break;
case 'profile-notifications':
$this->byId( 'context-button-read' )->click();
break;
case 'profile-help':
break;
case 'main-menu-link-ProcessPayrollWizard':
case 'main-menu-link-PayrollRemittanceAgencyEventWizardController':
case 'main-menu-link-ImportCSV':
$this->processWizard( $submenu_screenshot_path, $root_el, $sub_menu_element );
break;
case 'main-menu-link-QuickStartWizard':
$this->processWizard( $submenu_screenshot_path, $root_el, $sub_menu_element );
//might need this for a fresh install, but not after shutting off quick start nag screen.
// if ( $this->byId('yesBtn') ) {
// $this->byId('yesBtn')->click();
// }
break;
case 'main-menu-link-RecruitmentPortalConfig':
break;
default:
$this->processEditScreen( $submenu_screenshot_path, $root_el, $sub_menu_element, false );
break;
}
}
function clickRootAndSub( $root_el, $sub_el ) {
$this->waitForUIInitComplete();
$this->waitThenClick( '#' . $root_el->attribute( 'id' ) );
$this->waitForUIInitComplete();
$this->waitThenClick( '#' . $sub_el->attribute( 'id' ) );
$this->waitForUIInitComplete();
return true;
}
function clickMinimizedWindow() {
Debug::Text( 'Clicking minimized tab...', __FILE__, __LINE__, __METHOD__, 10 );
$this->waitThenClick( '.view-min-tab' );
sleep( 1 ); //For some reason without this we get this exception often when going to Payroll -> Pay Stubs, Pay Stub Transaction, then clicking the minimized window: #ribbon .context-menu a - stale element reference: element is not attached to the page document
return true;
}
function processContextIcons( $path, $root_el, $sub_el ) {
Debug::Text( 'processContextIcons: Root ID: ' . $root_el->attribute( 'id' ) . ' Sub Element: ' . $sub_el->attribute( 'id' ), __FILE__, __LINE__, __METHOD__, 10 );
//$context_menu_debug_id = 'editclienticon';
//array of action icons we do not wish to click
$skip_array = [
//no need
'context-button-save',
'context-button-delete_icon',
'context-button-cancel',
'context-button-copy',
'context-button-move',
'context-button-print_menu',
'context-button-export_excel',
'context-group-export_excel-split-menu',
'context-group-read',
'context-group-navigate',
'context-button-read',
'context-button-unread',
'context-button-navigate',
'context-button-jump_to',
'context-group-jump_to_header',
'',
'context-button-inboxicon',
'context-button-senticon',
'context-button-authorizationrequesticon',
'context-button-authorizationtimesheeticon',
'context-button-authorizationexpenseicon',
//disabled for now due to bugs
//all of these have submenu icons and the context icons are not working yet
'context-button-re_calculate_timesheet',
'context-button-generate_pay_stub',
//'context-button-clientcontacticon',
//'context-button-invoiceicon',
//'context-button-transactionicon',
//'context-button-paymentmethodicon',
//'context-button-accumulatedtimeicon',
];
$this->waitForUIInitComplete();
$this->waitUntilByCssSelector( '.context-menu-mount-container' );
$context_menu_id = $this->byCssSelector( '.context-menu-mount-container' )->attribute( 'id' );
$button_groups = $this->getArrayBySelector( '#' . $context_menu_id . ' div div div' );
//Parse the button types and add to $action_element_groups
foreach ( $button_groups as $button_group ) {
Debug::Text( 'Parsing buttons in button group: ' . $button_group->attribute( 'id' ), __FILE__, __LINE__, __METHOD__, 10 );
//Now get each button in the group if it is not disabled
$buttons_in_group = $button_group->elements(
$this->using( 'css selector' )->value( 'button:not(.p-disabled)' )
);
//Only click the first button in button group for now
$buttons_in_group = array_slice( $buttons_in_group, 0, 1 );
$previous_context_menu_id = '';
foreach ( $buttons_in_group as $context_button ) {
if ( in_array( $context_button->attribute( 'id' ), $skip_array ) == false ) {
if ( ( !isset( $context_menu_debug_id ) || stristr( $context_button->attribute( 'id' ), $context_menu_debug_id ) ) == false ) {
$this->waitForUIInitComplete();
continue;
}
Debug::Text( 'Clicking: #' . $context_button->attribute( 'id' ), __FILE__, __LINE__, __METHOD__, 10 );
$context_button->click();
$this->waitForUIInitComplete();
//If context button such as Employee -> New opens a dialog, click yes
if ( $this->isThere( '#yesBtn' ) ) {
$this->byId( 'yesBtn' )->click();
$this->waitForUIInitComplete();
}
//Get the new context menu on the new view if it exists. This happens when clicking "New" and other actions.
//Check an edit view was even created in first place
if ( $this->isThere( '.edit-view .context-menu-mount-container' ) ) {
$edit_view_context_menu = $this->byCssSelector( '.edit-view .context-menu-mount-container' );
$edit_view_context_menu_id = $edit_view_context_menu->attribute( 'id' );
} else {
$edit_view_context_menu_id = 'no_context_menu';
$edit_view_context_menu = null;
}
if ( $edit_view_context_menu_id != $previous_context_menu_id ) {
Debug::Text( 'New context menu id: ' . $edit_view_context_menu_id, __FILE__, __LINE__, __METHOD__, 10 );
$previous_context_menu_id = $edit_view_context_menu_id;
$new_context_menu = true;
} else {
$new_context_menu = false;
}
Debug::Text( '******** Taking screenshot for edit context menu #' . $edit_view_context_menu_id . ' screenshot filename: ' . $path . DIRECTORY_SEPARATOR . $edit_view_context_menu_id . '.png', __FILE__, __LINE__, __METHOD__, 10 );
$this->takeScreenshot( $path . DIRECTORY_SEPARATOR . $edit_view_context_menu_id . '.png', true );
//process tabs on applicable views
$this->waitForUIInitComplete();
//after screenshot is taken, some views need custom closure code
switch ( $context_button->attribute( 'id' ) ) {
case 'context-button-in_out':
Debug::Text( 'Shutting down an inout screen: ' . $edit_view_context_menu_id, __FILE__, __LINE__, __METHOD__, 10 );
$this->clickCancel( $edit_view_context_menu_id );
$this->waitThenClick( '#yesBtn' );
break;
//TODO: Handle the below buttons as they are in dropdowns now
//case 'scheduleicon':
//case 'paystubicon':
// $this->clickMinimizedWindow();
// break;
//case 'clientcontacticon':
//case 'invoiceicon':
//case 'transactionicon':
//case 'paymentmethodicon':
// $this->processTabs( $path . DIRECTORY_SEPARATOR . $id, $root_id, $sub_el_id, $id ); //fix
// $this->clickMinimizedWindow();
// break;
//case 'recalculatetimesheet':
//case 'generatepaystub':
//case 'jobinvoiceicon':
case 'quickstartwizard':
case 'context-button-import_icon':
case 'context-button-share_report':
$this->processWizard( $path . DIRECTORY_SEPARATOR . $edit_view_context_menu_id, $root_el, $sub_el );
break;
case 'accumulatedtimeicon':
// $this->processTabs ( $path . DIRECTORY_SEPARATOR . $id, $root_id , $sub_el_id, $el->attribute('id'));
break;
case 'paystubtransactionicon':
Debug::Text( 'paystubtransaction context icon...', __FILE__, __LINE__, __METHOD__, 10 );
$this->clickMinimizedWindow();
break;
// case 'remittancesourceaccount':
// $this->processTabs ( $path . DIRECTORY_SEPARATOR . $id, $root_id, $sub_el_id, $id, TRUE );
// break;
default:
if ( $edit_view_context_menu !== null ) {
//If there is a new context menu, process the tabs
if ( $new_context_menu == true ) {
$this->processTabs( $path . DIRECTORY_SEPARATOR . $edit_view_context_menu_id, $context_button->attribute( 'id' ), $edit_view_context_menu_id, true );
}
$this->clickCancel( $edit_view_context_menu_id );
}
//$this->byCssSelector( '.context-menu-bar .tticon-cancel_black_24dp' )->byXPath("./..")->click(); //TODO: Fix cancel button for all context menus.
//no hashtag
//$this->clickCancel();
// if ( $this->byCssSelector('#'. $this->byCssSelector('#ribbon .context-menu a')->attribute('ref') .' li:not(.disable-image):not(.invisible-image) #cancelIcon') ) {
// Debug::Text( 'clicking: #cancelIcon.', __FILE__, __LINE__, __METHOD__, 10 );
//
// $this->waitThenClick('#'. $this->byCssSelector('#ribbon .context-menu a')->attribute('ref') .' li:not(.disable-image):not(.invisible-image) #cancelIcon' );
// }
}
//Make sure that cancel icon is not invisible or disabled.
$this->waitForUIInitComplete();
}
}
}
Debug::Text( 'Done! Clicking context menu now...', __FILE__, __LINE__, __METHOD__, 10 );
//$this->waitThenClick( '#ribbon .context-menu a' );
$this->waitForUIInitComplete();
}
function processTabs( $path, $icon_clicked, $context_menu_id = false, $is_edit_view = false ) {
Debug::Text( 'Retrieving Tabs for: ' . $context_menu_id, __FILE__, __LINE__, __METHOD__, 10 );
$tabs = $this->getTabs( $icon_clicked, $context_menu_id );
Debug::Text( 'Processing Tabs for: ' . $context_menu_id, __FILE__, __LINE__, __METHOD__, 10 );
if ( count( $tabs ) > 1 ) {
$first_tab = array_shift( $tabs );
foreach ( $tabs as $tab_el ) {
$name = $tab_el->attribute( 'ref' );
Debug::Text( 'Processing Tab: ' . $name, __FILE__, __LINE__, __METHOD__, 10 );
if ( $tab_el->displayed() ) {
$tab_el->click();
} else {
//TODO: Scroll the tab buttons if tab is not visible.
}
$this->waitForUIInitComplete();
if ( $is_edit_view != false ) {
$screenshotFileName = $path . DIRECTORY_SEPARATOR . 'edit_view_' . $name . '.png';
} else {
$screenshotFileName = $path . DIRECTORY_SEPARATOR . $name . '.png';
}
Debug::Text( 'Taking screenshot for Tab: ' . $name . ' screenshot filename: ' . $screenshotFileName, __FILE__, __LINE__, __METHOD__, 10 );
$this->takeScreenshot( $screenshotFileName, true );
}
if ( $first_tab->displayed() ) {
$first_tab->click(); //click first tab before cancel.
} else {
//TODO: Scroll the tab buttons if tab is not visible.
}
} else {
$this->waitForUIInitComplete();
}
}
function getTabs( $clicked_icon_name, $context_menu_id ) {
$ignore_array = [
//causes crash as tries to click invisible audit tab
'profile-in-out',
];
if ( in_array( $clicked_icon_name, $ignore_array ) == true ) {
Debug::Text( 'Not even looking at tabs', __FILE__, __LINE__, __METHOD__, 10 );
return [];
}
$this->waitForUIInitComplete();
$parent_context_border = $this->byCssSelector( '#' . $context_menu_id )->byXPath( "./.." );
//Find none hidden tabs
Debug::Text( 'Finding all tabs for:' . $context_menu_id, __FILE__, __LINE__, __METHOD__, 10 );
$tabs = $parent_context_border->elements(
$this->using( 'css selector' )->value( 'ul.ui-tabs-nav > li:not([style="display: none;"]) > a' )
);
return $tabs;
}
function processWizard( $submenu_screenshot_path, $root_el, $sub_el ) {
$root_id = $root_el->attribute( 'id' );
$sub_id = $sub_el->attribute( 'id' );
Debug::Text( 'Looking At Wizard View: ' . $root_id . ' Sub View: ' . $sub_id, __FILE__, __LINE__, __METHOD__, 10 );
//Wizards save steps so make sure we go to first step before taking screenshots.
while ( $this->byId( 'wizard-back-button' )->attribute( 'disabled' ) == false && str_contains( $this->byId( 'wizard-back-button' )->attribute( 'class' ), 'disable-image' ) == false ) {
$this->byId( 'wizard-back-button' )->click();
$this->waitForUIInitComplete();
if ( $this->isThere( '#yesBtn' ) ) {
$this->byId( 'yesBtn' )->click();
$this->waitForUIInitComplete();
}
}
//Now go forward to last step in the wizard.
$step = 1;
$screenshotFileName = $submenu_screenshot_path . DIRECTORY_SEPARATOR . 'wizard' . $sub_id . 'step' . $step . '.png';
Debug::Text( 'Taking screenshot for wizard View: ' . $root_id . '=>' . $sub_id . ' screenshot filename: ' . $screenshotFileName, __FILE__, __LINE__, __METHOD__, 10 );
$this->takeScreenshot( $screenshotFileName );
while ( $this->byId( 'wizard-forward-button' )->attribute( 'disabled' ) == false && str_contains( $this->byId( 'wizard-forward-button' )->attribute( 'class' ), 'disable-image' ) == false ) {
$this->byId( 'wizard-forward-button' )->click();
$this->waitForUIInitComplete();
if ( $this->isThere( '#yesBtn' ) ) {
$this->byId( 'yesBtn' )->click();
$this->waitForUIInitComplete();
}
if ( $this->isThere( '#t-alert-close' ) ) {
$this->byId( 't-alert-close' )->click();
$this->waitForUIInitComplete();
}
$step++;
$screenshotFileName = $submenu_screenshot_path . DIRECTORY_SEPARATOR . 'wizard' . $sub_id . 'step' . $step . '.png';
Debug::Text( 'Taking screenshot for wizard View: ' . $root_id . '=>' . $sub_id . ' screenshot filename: ' . $screenshotFileName, __FILE__, __LINE__, __METHOD__, 10 );
$this->takeScreenshot( $screenshotFileName );
}
$this->byId( 'wizard-close-button' )->click();
$this->waitForUIInitComplete();
if ( $this->isThere( '#yesBtn' ) ) {
$this->byId( 'yesBtn' )->click();
$this->waitForUIInitComplete();
}
if ( $this->isThere( '#t-alert-close' ) ) {
$this->byId( 't-alert-close' )->click();
$this->waitForUIInitComplete();
}
}
function processEditScreen( $submenu_screenshot_path, $root_el, $sub_el, $context_el ) {
$root_id = $root_el->attribute( 'id' );
$sub_id = $sub_el->attribute( 'id' );
Debug::Text( 'Looking At Edit View: ' . $root_id . ' Sub View: ' . $sub_id, __FILE__, __LINE__, __METHOD__, 10 );
if ( $context_el == false ) {
$context_menu_id = $this->byCssSelector( '.context-menu-mount-container' )->attribute( 'id' );
}
$ignore_list = [
//popovers that have grids under them so the grid is found and causes a crash.
'context-button-in_out',
'profile-menu-ChangePassword',
'profile-menu-LoginUserPreference',
'profile-menu-LoginUserContact',
//overly complex grids that will need special considerations:
'main-menu-link-TimeSheet',
'main-menu-link-Schedule',
];
if ( in_array( $sub_id, $ignore_list ) ) {
Debug::Text( 'Skipping due to ignore list...', __FILE__, __LINE__, __METHOD__, 10 );
return;
}
//TODO: Look at this css selector
$css_selector = '.grid-div .ui-jqgrid .ui-jqgrid-btable tr:nth-child(2) td:nth-child(2)';
$this->waitForUIInitComplete();
if ( $this->isThere( '.grid-div .no-result-div' ) === false && $this->isThere( $css_selector ) === true && $this->isThere( '#' . $context_menu_id . ' .left-view' ) == true ) {
Debug::Text( 'Processing Edit View: ' . $root_id . '=>' . $sub_id, __FILE__, __LINE__, __METHOD__, 10 );
$this->waitThenClick( $css_selector );
//Don't click view on Pay Stub or Invoice as it will download a file.
if ( $sub_id == 'main-menu-link-PayStub' || $sub_id == 'main-menu-link-GovernmentDocument' || $sub_id == 'main-menu-link-Invoice' ) {
if ( $this->isThere( '#' . $context_menu_id . ' #context-button-edit' ) ) {
$this->waitThenClick( '#' . $context_menu_id . ' #context-button-edit' );
}
$icon_clicked = 'edit';
} else {
$this->waitThenClick( '#' . $context_menu_id . ' #context-button-view' );
$icon_clicked = 'view';
}
$this->waitForUIInitComplete();
$screenshotFileName = $submenu_screenshot_path . DIRECTORY_SEPARATOR . 'edit_view_' . $sub_id . '.png';
Debug::Text( 'Taking screenshot for Edit View: ' . $root_id . '=>' . $sub_id . ' screenshot filename: ' . $screenshotFileName, __FILE__, __LINE__, __METHOD__, 10 );
$this->takeScreenshot( $screenshotFileName );
if ( $this->isThere( '.edit-view .context-menu-mount-container' ) ) {
$edit_view_context_menu = $this->byCssSelector( '.edit-view .context-menu-mount-container' );
$edit_view_context_menu_id = $edit_view_context_menu->attribute( 'id' );
$this->processTabs( $submenu_screenshot_path . DIRECTORY_SEPARATOR . 'edit_view_' . $context_menu_id, $icon_clicked, $edit_view_context_menu_id, true );
$this->waitForUIInitComplete();
Debug::Text( 'Clicking Cancel at end of processEditScreen()', __FILE__, __LINE__, __METHOD__, 10 );
$this->clickCancel( $edit_view_context_menu_id );
}
//does every edit have a cancel? NO. Exceptions is the exception, so it's in the ignore_list.
$this->waitForUIInitComplete();
}
Debug::Text( 'Done...', __FILE__, __LINE__, __METHOD__, 10 );
}
}
?>

View File

@ -0,0 +1,425 @@
<?php /** @noinspection PhpMissingDocCommentInspection */
/*********************************************************************************
*
* TimeTrex is a Workforce Management program developed by
* TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by
* the Free Software Foundation with the addition of the following permission
* added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
* WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
* WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
*
* You should have received a copy of the GNU Affero General Public License along
* with this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
*
* You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
* #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
*
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
*
* In accordance with Section 7(b) of the GNU Affero General Public License
* version 3, these Appropriate Legal Notices must retain the display of the
* "Powered by TimeTrex" logo. If the display of the logo is not reasonably
* feasible for technical reasons, the Appropriate Legal Notices must display
* the words "Powered by TimeTrex".
*
********************************************************************************/
/**
* Class TTSeleniumGlobal
*
* weird xpath examples:
* // //*[starts-with(@id, 'ceil_')]
*/
class TTSeleniumGlobal extends PHPUnit\Extensions\Selenium2TestCase {
private $default_wait_timeout = 4000;//100000;
public $width = 1440;
public $height = 900;
public function setUp(): void {
global $selenium_config;
$this->selenium_config = $selenium_config;
Debug::text( 'Running setUp(): ', __FILE__, __LINE__, __METHOD__, 10 );
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->setPort( (int)$selenium_config['port'] );
$this->setBrowser( $selenium_config['browser'] );
$this->setBrowserUrl( $selenium_config['default_url'] );
$this->setDesiredCapabilities( [ 'chromeOptions' => [ 'args' => [ '--incognito' ], ] ] ); //Use incognito mode to help prevent caching between sessions and saving passwords and such.
$this->setDesiredCapabilities( [
'goog:chromeOptions' => [
'w3c' => false,
],
] );
}
public function tearDown(): void {
Debug::text( 'Running tearDown(): ', __FILE__, __LINE__, __METHOD__, 10 );
}
function goToLogin( $user ) {
Debug::text( 'Login to: ' . $this->selenium_config['default_url'] . ' Username: ' . $user, __FILE__, __LINE__, __METHOD__, 10 );
$this->url( $this->selenium_config['default_url'] );
sleep( 2.5 ); //have to be sure that Global.js is loaded before we start trying to use it.
$this->setUnitTestMode( $user );
$this->waitUntilByCssSelector( '#user_name' );
}
function Login( $user, $pass ) {
//disable the overlay to speed up testing
$this->goToLogin( $user );
$this->waitThenClick( '#user_name' );
$this->keys( $user );
//$this->keys('demoadmin2');
$this->waitThenClick( '#password' );
$this->keys( $pass );
$this->waitThenClick( '#login_btn' );
sleep( 1 ); //wait for login
$this->waitForUIInitComplete();
$this->waitUntilById( 'topbar-company-logo' ); //the css not() selector is there to differentiate the various calls in the server log.
//needed as development mode reloads and clears the variables.
$javascript = [ 'script' => 'Global.setUnitTestMode();', 'args' => [] ];
$this->execute( $javascript );
Debug::text( 'Login Complete...', __FILE__, __LINE__, __METHOD__, 10 );
}
function Logout() {
//because we could want to log out from any point
$this->goToDashboard();
$this->waitForUIInitComplete();
$this->waitUntilById( 'profile-button' );
$this->byId( 'profile-button' )->click();
$this->waitUntilById( 'profile-menu-Logout' );
$this->byId( 'profile-menu-Logout' )->click();
$this->waitUntilById( 'user_name' );
Debug::text( 'Logout...', __FILE__, __LINE__, __METHOD__, 10 );
}
function waitUntilById( $id, $timeout = null ) {
if ( $timeout == null ) {
$timeout = $this->default_wait_timeout;
}
$this->waitUntil( function () use ( $id ) {
$javascript = [ 'script' => "$('#overlay.overlay:visible').length", 'args' => [] ];
if ( $this->execute( $javascript ) == 0 && $this->byId( $id ) ) {
return true;
}
return null;
}, $timeout );
}
function changeLanguageLogin( $language_value ) {
$this->waitUntilByCssSelector( '#language-selector' );
$language_selector = $this->byId( 'language-selector' );
$language_selector->click();
$language_options = $language_selector->elements(
$this->using( 'css selector' )->value( 'option' )
);
foreach ( $language_options as $language_option ) {
if ( $language_option->value() == $language_value ) {
$language_option->click();
break;
}
}
}
function changeLanguagePreference( $language_value ) {
$this->goToDashboard();
$this->waitUntilById( 'profile-button' );
$this->byId( 'profile-button' )->click();
$this->waitUntilById( 'profile-menu-LoginUserPreference' );
$this->byId( 'profile-menu-LoginUserPreference' )->click();
$this->waitForUIInitComplete();
$language_selector = $this->byCssSelector( '#tab_preferences_content_div select:first-of-type' );
$language_selector->click();
$language_options = $language_selector->elements(
$this->using( 'css selector' )->value( 'option' )
);
foreach ( $language_options as $language_option ) {
if ( $language_option->value() == $language_value ) {
$language_option->click();
break;
}
}
$this->byId( 'context-button-save' )->click();
$this->waitForUIInitComplete();
}
function waitUntilByCssSelector( $selector, $timeout = null ) {
if ( $timeout == null ) {
$timeout = $this->default_wait_timeout;
}
$this->waitUntil( function () use ( $selector ) {
$javascript = [ 'script' => "$('#overlay.overlay:visible').length", 'args' => [] ];
if ( $this->execute( $javascript ) == 0 && $this->byCssSelector( $selector ) ) {
return true;
}
return null;
}, $timeout );
}
function takeScreenshot( $screenshot_file_name, $create_dir = true ) {
if ( $create_dir === true ) {
$dirname = dirname( $screenshot_file_name );
if ( file_exists( $dirname ) == false ) {
mkdir( $dirname, 0777, true );
}
}
$this->waitForUIInitComplete();
// get the mousepointer and focus away from hover effects and flashing cursors
// these cause significant differences in the screenshots.
$this->waitUntilByCssSelector( '#powered_by,#copy_right_logo' );
$this->moveto( $this->byCssSelector( '#powered_by,#copy_right_logo' ) );
$this->waitUntilByCssSelector( '#powered_by,#copy_right_logo' );
$retval = file_put_contents( $screenshot_file_name, $this->currentScreenshot() );
chmod( $screenshot_file_name, 0777 );
return $retval;
}
function getOSUser() {
if ( function_exists( 'posix_geteuid' ) && function_exists( 'posix_getpwuid' ) ) {
$user = posix_getpwuid( posix_geteuid() );
Debug::Text( 'Webserver running as User: ' . $user['name'], __FILE__, __LINE__, __METHOD__, 10 );
return $user['name'];
}
return false;
}
function goToDashboard() {
$this->waitUntilById( 'topbar-company-logo' );
$this->byId( 'topbar-company-logo' )->byXPath("./..")->click(); //click on the logo parent (href) to go to the dashboard
//dashboard will reliably use init_complete after everything is loaded.
$this->waitForUIInitComplete();
}
function waitForUIInitComplete() {
$this->waitForUIInitCompleteLoops = 0;
$this->waitUntil( function ( $_self_ ) {
//Global.getUIReadyStatus will be == 2 when the screens are finished loading.
$javascript = [ 'script' => 'if ( ( typeof Global != "undefined" && Global.getUIReadyStatus() == 2 ) && ( typeof TTPromise != "undefined" && TTPromise.isPendingPromises() == false ) ) { return true; } else { return false; }', 'args' => [] ];
$js_retval = $this->execute( $javascript );
Debug::Text( 'waitForUI result: ' . var_export( $js_retval, true ), __FILE__, __LINE__, __METHOD__, 10 );
if ( isset( $js_retval ) && $js_retval == true ) {
return true;
} else {
$ui_ready_status = $this->execute( [ 'script' => 'if ( typeof Global != "undefined" ) { return Global.getUIReadyStatus(); } else { return null; }', 'args' => [] ] );
$pending_promises = $this->execute( [ 'script' => 'if ( typeof TTPromise != "undefined" ) { return TTPromise.isPendingPromises(); } else { return null; }', 'args' => [] ] );
Debug::Text( ' waitForUI UIReadyStatus: ' . var_export( $ui_ready_status, true ) . ' Pending Promises: ' . var_export( $pending_promises, true ), __FILE__, __LINE__, __METHOD__, 10 );
if ( $_self_->waitForUIInitCompleteLoops > 10 ) {
//trigger checking promises again to workaround selenium bug where they resolve without firing function
$this->execute( [ 'script' => 'TTPromise.wait()', 'args' => [] ] );
Debug::Text( ' waitForUI Triggering TTPromise.wait()... Loops: ' . $this->waitForUIInitCompleteLoops, __FILE__, __LINE__, __METHOD__, 10 );
}
$_self_->waitForUIInitCompleteLoops++;
return null;
}
}, 60000 ); //Wait for up to 60 seconds.
}
function setUnitTestMode( $username ) {
$sf = TTnew( 'StationFactory' ); /** @var StationFactory $sf */
$slf = TTnew( 'StationListFactory' ); /** @var StationListFactory $slf */
$slf->getByStationId( 'UNITTEST' );
if ( $slf->getRecordCount() == 0 ) {
$ulf = TTNew( 'UserListFactory' ); /** @var UserListFactory $ulf */
$ulf->getByUserName( $username );
if ( $ulf->getRecordCount() > 0 ) {
$sf->setCompany( $ulf->getCurrent()->getCompany() );
$sf->setStatus( 20 );
$sf->setType( 10 );
$sf->setDescription( 'Unit Testing Rig' );
$sf->setStation( 'UNITTEST' );
$sf->setSource( 'ANY' );
$sf->setBranchSelectionType( 10 ); //enabled all
$sf->setDepartmentSelectionType( 10 ); //enabled all
$sf->setGroupSelectionType( 10 ); //enabled all
if ( $sf->isValid() ) {
$sf->Save();
}
} else {
Debug::Text( 'username not found in db', __FILE__, __LINE__, __METHOD__, 10 );
}
} else {
Debug::Text( 'station exists', __FILE__, __LINE__, __METHOD__, 10 );
}
//run necessary js for unit tests
$javascript = [ 'script' => 'Global.setUnitTestMode();', 'args' => [] ];
$this->execute( $javascript );
//$path = Environment::getCookieBaseURL();
//set the same sessionid for all tests
//$javascript = [ 'script' => "$.cookie( 'StationID', 'UNITTESTS', {expires: 30, path: '$path'} );", 'args' => [] ];
$javascript = [ 'script' => "Global.setStationID('UNITTEST')", 'args' => [] ];
$this->execute( $javascript );
}
function isThere( $css_selector ) {
$result = $this->elements( $this->using( 'css selector' )->value( $css_selector ) );
if ( count( $result ) > 0 ) {
foreach ( $result as $el ) {
if ( $el->displayed() && $el->enabled() ) {
return true;
break;
}
}
}
return false;
}
function waitThenClick( $selector ) {
Debug::Text( 'Attempting to click Selector: ' . $selector, __FILE__, __LINE__, __METHOD__, 10 );
$javascript = [ 'script' => "return $('#overlay.overlay').length", 'args' => [] ];
$overlay_shown = $this->execute( $javascript );
if ( $overlay_shown > 0 ) {
Debug::Text( ' Overlay status check: ' . $overlay_shown, __FILE__, __LINE__, __METHOD__, 10 );
sleep( 1 );
$this->waitThenClick( $selector );
return;
}
try {
if ( ( substr( $selector, 0, 1 ) == '#' && strstr( $selector, ' ' ) == false ) || strstr( $selector, 'menu:' ) == true ) {
//need to do this because of malformed ids in the top menu causing wating by selector to fail.
$id = substr( $selector, 1, strlen( $selector ) );
Debug::Text( ' Waiting on ID: ' . $id, __FILE__, __LINE__, __METHOD__, 10 );
$this->waitUntilById( $id, 10000 );
Debug::Text( ' Clicking ID: ' . $id, __FILE__, __LINE__, __METHOD__, 10 );
$this->byId( $id )->click();
} else {
Debug::Text( ' Waiting on selector: ' . $selector, __FILE__, __LINE__, __METHOD__, 10 );
$this->waitUntilByCssSelector( $selector, 10000 );
Debug::Text( ' Clicking selector: ' . $selector, __FILE__, __LINE__, __METHOD__, 10 );
$el = $this->byCssSelector( $selector );
//Debug::Text( ' Element about to be clicked: Enabled: '. $el->enabled() .' Displayed: '. $el->displayed(), __FILE__, __LINE__, __METHOD__, 10 );
$el->click();
}
} catch ( Exception $e ) {
$this->takeScreenshot( $this->screenshot_path . DIRECTORY_SEPARATOR . 'waitThenClickException.png', true );
Debug::Text( 'Click failed on: ' . $selector . ' Screenshot path: ' . $this->screenshot_path, __FILE__, __LINE__, __METHOD__, 10 );
//$javascript = array('script' => "$('" . $selector . "').click()", 'args' => array());
//$this->execute( $javascript );
throw new Exception( $selector . ' - ' . $e->getMessage() );
}
Debug::Text( 'Done: ' . $selector, __FILE__, __LINE__, __METHOD__, 10 );
}
function getArrayBySelector( $css_selector ) {
Debug::Text( 'Getting array by selector: ' . $css_selector, __FILE__, __LINE__, __METHOD__, 10 );
//$this->waitUntilByCssSelector( $css_selector,10000 );
//http://stackoverflow.com/questions/16637806/select-all-matching-elements-in-phpunit-selenium-2-test-case
$retval = $this->elements(
$this->using( 'css selector' )->value( $css_selector )
);
if ( isset( $retval ) ) {
Debug::Text( count( $retval ) . ' RESULTS FOR: ' . $css_selector, __FILE__, __LINE__, __METHOD__, 10 );
// foreach ( $retval as $el ) {
// Debug::Text( ' Element: ' . $el->attribute( 'ref' ) .' ID: '. $el->attribute( 'id' ), __FILE__, __LINE__, __METHOD__, 10 );
// }
return $retval;
}
return [];
}
function clickCancel( $menu_id = false ) {
//TODO look at this
if ( $menu_id !== false ) {
$selector = '#' . $menu_id . ' .tticon-cancel_black_24dp';
$this->waitThenClick( $selector );
Debug::Text( 'Clicking Cancel [' . $selector . ']', __FILE__, __LINE__, __METHOD__, 10 );
} else {
//TODO: Fix this branchs
$javascript = [ 'script' => "$('#topContainer .ribbon .ribbon-tab-out-side:visible #cancelIcon').click()", 'args' => [] ];
$this->execute( $javascript );
Debug::Arr( $javascript, 'Executing cancelclick with js', __FILE__, __LINE__, __METHOD__, 10 );
}
}
function clickMainMenuItem( $menu_id ) {
//Only works for main menu horizontal default mode currently.
$this->waitUntilByCssSelector( '#main-menu' );
$this->byCssSelector( '#main-menu' )->byId( $menu_id )->click();
Debug::Text( 'Opening Main Menu Item [' . $menu_id . ']', __FILE__, __LINE__, __METHOD__, 10 );
}
function clickProfileMenuItem( $profile_menu_id ) {
$this->waitForUIInitComplete();
$this->waitUntilById( 'profile-button' );
$this->byId( 'profile-button' )->click();
$this->waitUntilById( $profile_menu_id );
$this->byId( $profile_menu_id )->click();
$this->waitForUIInitComplete();
}
}