diff --git a/.gitignore b/.gitignore index fe2b19d07b093..bf4bf6d7d1150 100644 --- a/.gitignore +++ b/.gitignore @@ -153,3 +153,19 @@ Desktop.ini /libraries/vendor/simplepie/simplepie/build /libraries/vendor/simplepie/simplepie/idn/ReadMe.txt /libraries/vendor/simplepie/simplepie/composer.json + +# System Test related files +tests/codeception/acceptance.suite.yml +tests/codeception/_support/_generated/*TesterActions.php +tests/codeception/joomla-cms* +tests/codeception/_output* +selenium-server-standalone.jar +selenium.log +tests/codeception/cache +tests/codeception/_output +tests/codeception/vendor +composer.phar + +# Build related +RoboFile.ini + diff --git a/RoboFile.dist.ini b/RoboFile.dist.ini new file mode 100644 index 0000000000000..ea5cafda52596 --- /dev/null +++ b/RoboFile.dist.ini @@ -0,0 +1,6 @@ +; If you want to setup your test website (document root) in a different folder, you can do that here. +; You can also set an absolute path, i.e. /path/to/my/cms/folder +cmsPath = tests/codeception/joomla-cms + +; (Linux / Mac only) If you want to set a different owner for the CMS root folder, you can set it here. +localUser = diff --git a/RoboFile.php b/RoboFile.php new file mode 100644 index 0000000000000..86df3f70cb6ca --- /dev/null +++ b/RoboFile.php @@ -0,0 +1,558 @@ +configuration = $this->getConfiguration(); + $this->cmsPath = $this->getTestingPath(); + + // Set default timezone (so no warnings are generated if it is not set) + date_default_timezone_set('UTC'); + } + + /** + * Get (optional) configuration from an external file + * + * @since __DEPLOY_VERSION__ + * + * @return \stdClass|null + */ + public function getConfiguration() + { + $configurationFile = __DIR__ . '/RoboFile.ini'; + + if (!file_exists($configurationFile)) + { + $this->say('No local configuration file'); + + return null; + } + + $configuration = parse_ini_file($configurationFile); + + if ($configuration === false) + { + $this->say('Local configuration file is empty or wrong (check is it in correct .ini format'); + + return null; + } + + return json_decode(json_encode($configuration)); + } + + /** + * Get the correct CMS root path + * + * @since __DEPLOY_VERSION__ + * + * @return string + */ + private function getTestingPath() + { + if (empty($this->configuration->cmsPath)) + { + return $this->testsPath . 'joomla-cms'; + } + + if (!file_exists(dirname($this->configuration->cmsPath))) + { + $this->say('CMS path written in local configuration does not exists or is not readable'); + + return $this->testsPath . 'joomla-cms'; + } + + return $this->configuration->cmsPath; + } + + /** + * Creates a testing Joomla site for running the tests (use it before run:test) + * + * @param bool $useHtaccess (1/0) Rename and enable embedded Joomla .htaccess file + * + * @since __DEPLOY_VERSION__ + * + * @return void + */ + public function createTestingSite($useHtaccess = false) + { + // Clean old testing site + if (is_dir($this->cmsPath)) + { + try + { + $this->taskDeleteDir($this->cmsPath)->run(); + } + catch (Exception $e) + { + // Sorry, we tried :( + $this->say('Sorry, you will have to delete ' . $this->cmsPath . ' manually.'); + + exit(1); + } + } + + $exclude = ['tests', 'tests-phpunit', '.run', '.github', '.git']; + + $this->copyJoomla($this->cmsPath, $exclude); + + // Optionally change owner to fix permissions issues + if (!empty($this->configuration->localUser)) + { + $this->_exec('chown -R ' . $this->configuration->localUser . ' ' . $this->cmsPath); + } + + // Optionally uses Joomla default htaccess file. Used by TravisCI + if ($useHtaccess == true) + { + $this->say('Renaming htaccess.txt to .htaccess'); + $this->_copy('./htaccess.txt', $this->cmsPath . '/.htaccess'); + $this->_exec('sed -e "s,# RewriteBase /,RewriteBase /tests/codeception/joomla-cms,g" -in-place tests/codeception/joomla-cms/.htaccess'); + } + } + + /** + * Copy the Joomla installation excluding folders + * + * @param string $dst Target folder + * @param array $exclude Exclude list of folders + * + * @throws Exception + * + * @since __DEPLOY_VERSION__ + * + * @return void + */ + protected function copyJoomla($dst, $exclude = array()) + { + $dir = @opendir("."); + + if (false === $dir) + { + throw new Exception($this, "Cannot open source directory"); + } + + if (!is_dir($dst)) + { + mkdir($dst, 0755, true); + } + + while (false !== ($file = readdir($dir))) + { + if (in_array($file, $exclude)) + { + continue; + } + + if (($file !== '.') && ($file !== '..')) + { + $srcFile = "." . '/' . $file; + $destFile = $dst . '/' . $file; + + if (is_dir($srcFile)) + { + $this->_copyDir($srcFile, $destFile); + } + else + { + copy($srcFile, $destFile); + } + } + } + + closedir($dir); + } + + /** + * Downloads Composer + * + * @since __DEPLOY_VERSION__ + * + * @return void + */ + private function getComposer() + { + // Make sure we have Composer + if (!file_exists($this->testsPath . 'composer.phar')) + { + $this->_exec('curl -o ' . $this->testsPath . 'composer.phar --retry 3 --retry-delay 5 -sS https://getcomposer.org/installer | php'); + } + } + + /** + * Runs Selenium Standalone Server. + * + * @since __DEPLOY_VERSION__ + * + * @return void + */ + public function runSelenium() + { + if (!$this->isWindows()) + { + $this->_exec($this->testsPath . "vendor/bin/selenium-server-standalone " . $this->getWebDriver() . ' >> selenium.log 2>&1 &'); + } + else + { + $this->_exec("START java.exe -jar " . $this->getWebDriver() . ' tests\codeception\vendor\joomla-projects\selenium-server-standalone\bin\selenium-server-standalone.jar '); + } + + sleep(3); + } + + /** + * Executes all the Selenium System Tests in a suite on your machine + * + * @param array $opts Array of configuration options: + * - 'use-htaccess': renames and enable embedded Joomla .htaccess file + * - 'env': set a specific environment to get configuration from + * + * @since __DEPLOY_VERSION__ + * + * @return mixed + */ + public function runTests($opts = ['use-htaccess' => false, 'env' => 'desktop']) + { + $this->say("Running tests"); + + $this->createTestingSite($opts['use-htaccess']); + + $this->getComposer(); + $this->taskComposerInstall($this->testsPath . 'composer.phar')->run(); + + $this->runSelenium(); + + // Make sure to run the build command to generate AcceptanceTester + if ($this->isWindows()) + { + $this->_exec('php ' . $this->getWindowsPath($this->testsPath . 'vendor/bin/codecept') . ' build'); + $pathToCodeception = $this->getWindowsPath($this->testsPath . 'vendor/bin/codecept'); + } + else + { + $this->_exec('php ' . $this->testsPath . 'vendor/bin/codecept build'); + + $pathToCodeception = $this->testsPath . 'vendor/bin/codecept'; + } + + $this->taskCodecept($pathToCodeception) + ->arg('--steps') + ->arg('--debug') + ->arg('--fail-fast') + ->env($opts['env']) + ->arg($this->testsPath . 'acceptance/install/') + ->run() + ->stopOnFail(); + + $this->taskCodecept() + ->arg('--steps') + ->arg('--debug') + ->arg('--fail-fast') + ->env($opts['env']) + ->arg($this->testsPath . '/acceptance/administrator/components/com_users') + ->run() + ->stopOnFail(); + } + + /** + * Executes a specific Selenium System Tests in your machine + * + * @param string $pathToTestFile Optional name of the test to be run + * @param string $suite Optional name of the suite containing the tests, Acceptance by default. + * + * @since __DEPLOY_VERSION__ + * + * @return void + */ + public function runTest($pathToTestFile = null, $suite = 'acceptance') + { + $this->runSelenium(); + + // Make sure to run the build command to generate AcceptanceTester + $path = 'tests/codeception/vendor/bin/codecept'; + $this->_exec('php ' . $this->isWindows() ? $this->getWindowsPath($path) : $path . ' build'); + + if (!$pathToTestFile) + { + $this->say('Available tests in the system:'); + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( + $this->testsPath . $suite, + RecursiveDirectoryIterator::SKIP_DOTS + ), + RecursiveIteratorIterator::SELF_FIRST + ); + + $tests = array(); + $i = 1; + + $iterator->rewind(); + + while ($iterator->valid()) + { + if (strripos($iterator->getSubPathName(), 'cept.php') + || strripos($iterator->getSubPathName(), 'cest.php') + || strripos($iterator->getSubPathName(), '.feature') + ) + { + $this->say('[' . $i . '] ' . $iterator->getSubPathName()); + + $tests[$i] = $iterator->getSubPathName(); + $i++; + } + + $iterator->next(); + } + + $this->say(''); + $testNumber = $this->ask('Type the number of the test in the list that you want to run...'); + $test = $tests[$testNumber]; + } + + $pathToTestFile = $this->testsPath . $suite . '/' . $test; + + // Loading the class to display the methods in the class + + // Logic to fetch the class name from the file name + $fileName = explode("/", $test); + + // If the selected file is cest only then we will give the option to execute individual methods, we don't need this in cept or feature files + $i = 1; + + if (isset($fileName[1]) && strripos($fileName[1], 'cest')) + { + require $this->testsPath . $suite . '/' . $test; + + $className = explode(".", $fileName[1]); + $class_methods = get_class_methods($className[0]); + + $this->say('[' . $i . '] ' . 'All'); + + $methods[$i] = 'All'; + $i++; + + foreach ($class_methods as $method_name) + { + $reflect = new ReflectionMethod($className[0], $method_name); + + if (!$reflect->isConstructor() && $reflect->isPublic()) + { + $this->say('[' . $i . '] ' . $method_name); + + $methods[$i] = $method_name; + + $i++; + } + } + + $this->say(''); + $methodNumber = $this->ask('Please choose the method in the test that you would want to run...'); + $method = $methods[$methodNumber]; + } + + if (isset($method) && $method != 'All') + { + $pathToTestFile = $pathToTestFile . ':' . $method; + } + + $testPathCodecept = $this->testsPath . 'vendor/bin/codecept'; + + $this->taskCodecept($this->isWindows() ? $this->getWindowsPath($testPathCodecept) : $testPathCodecept) + ->test($pathToTestFile) + ->arg('--steps') + ->arg('--debug') + ->run() + ->stopOnFail(); + } + + /** + * Check if local OS is Windows + * + * @return bool + * + * @since __DEPLOY_VERSION__ + */ + private function isWindows() + { + return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; + } + + /** + * Return the correct path for Windows (needed by CMD) + * + * @param string $path Linux path + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + private function getWindowsPath($path) + { + return str_replace('/', DIRECTORY_SEPARATOR, $path); + } + + /** + * Detect the correct driver for selenium + * + * @return string the webdriver string to use with selenium + * + * @since __DEPLOY_VERSION__ + */ + public function getWebdriver() + { + $suiteConfig = $this->getSuiteConfig(); + $codeceptMainConfig = \Codeception\Configuration::config(); + $browser = $suiteConfig['modules']['config']['JoomlaBrowser']['browser']; + + if ($browser == 'chrome') + { + $driver['type'] = 'webdriver.chrome.driver'; + } + elseif ($browser == 'firefox') + { + $driver['type'] = 'webdriver.gecko.driver'; + } + elseif ($browser == 'MicrosoftEdge') + { + $driver['type'] = 'webdriver.edge.driver'; + + // Check if we are using Windows Insider builds + if ($suiteConfig['modules']['config']['AcceptanceHelper']['MicrosoftEdgeInsiders']) + { + $browser = 'MicrosoftEdgeInsiders'; + } + } + elseif ($browser == 'internet explorer') + { + $driver['type'] = 'webdriver.ie.driver'; + } + + // Check if we have a path for this browser and OS in the codeception settings + if (isset($codeceptMainConfig['webdrivers'][$browser][$this->getOs()])) + { + $driverPath = $codeceptMainConfig['webdrivers'][$browser][$this->getOs()]; + } + else + { + $this->yell('No driver for your browser. Check your browser in acceptance.suite.yml and the webDrivers in codeception.yml'); + + // We can't do anything without a driver, exit + exit(1); + } + + $driver['path'] = $driverPath; + + return '-D' . implode('=', $driver); + } + + /** + * Return the os name + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + private function getOs() + { + $os = php_uname('s'); + + if (strpos(strtolower($os), 'windows') !== false) + { + return 'windows'; + } + + if (strpos(strtolower($os), 'darwin') !== false) + { + return 'mac'; + } + + return 'linux'; + } + + /** + * Get the suite configuration + * + * @param string $suite Name of the test suite + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + private function getSuiteConfig($suite = 'acceptance') + { + if (!$this->suiteConfig) + { + $this->suiteConfig = Symfony\Component\Yaml\Yaml::parse(file_get_contents("tests/codeception/{$suite}.suite.yml")); + } + + return $this->suiteConfig; + } +} diff --git a/build/build.php b/build/build.php index 31b9aec47990e..2fa806556a844 100644 --- a/build/build.php +++ b/build/build.php @@ -115,6 +115,9 @@ 'phpunit.xml.dist', 'tests', 'travisci-phpunit.xml', + 'codeception.yml', + 'RoboFile.php', + 'RoboFile.dist.ini', // Remove the testing sample data from all packages 'installation/sql/mysql/sample_testing.sql', 'installation/sql/postgresql/sample_testing.sql', diff --git a/build/phpcs/Joomla/ruleset.xml b/build/phpcs/Joomla/ruleset.xml index 868db95586e7d..b644453af4001 100644 --- a/build/phpcs/Joomla/ruleset.xml +++ b/build/phpcs/Joomla/ruleset.xml @@ -26,6 +26,9 @@ installation/template/index.php plugins/captcha/recaptcha/recaptchalib.php + + RoboFile.php + diff --git a/build/travis/codeception-tests.sh b/build/travis/codeception-tests.sh new file mode 100644 index 0000000000000..166c88b8a11db --- /dev/null +++ b/build/travis/codeception-tests.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Codeception system tests setup + +set -e + +BASE="$1" + +cd $BASE + +sudo apt-get update -qq +sudo apt-get install -y --force-yes apache2 libapache2-mod-fastcgi php5-curl php5-mysql php5-intl php5-gd fluxbox > /dev/null + +sudo mkdir $BASE/.run + +sudo cp ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf.default ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf +sudo sed -e "s,listen = 127.0.0.1:9000,listen = /tmp/php5-fpm.sock,g" --in-place ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf +sudo sed -e "s,;listen.owner = nobody,listen.owner = $USER,g" --in-place ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf +sudo sed -e "s,;listen.group = nobody,listen.group = $USER,g" --in-place ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf +sudo sed -e "s,;listen.mode = 0660,listen.mode = 0666,g" --in-place ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf +sudo sed -e "s,user = nobody,;user = $USER,g" --in-place ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf +sudo sed -e "s,group = nobody,;group = $USER,g" --in-place ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf +cat ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf +sudo a2enmod rewrite actions fastcgi alias +echo "cgi.fix_pathinfo = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini +~/.phpenv/versions/$(phpenv version-name)/sbin/php-fpm +sudo cp -f tests/codeception/travis-ci-apache.conf /etc/apache2/sites-available/default +sudo sed -e "s?%TRAVIS_BUILD_DIR%?$BASE?g" --in-place /etc/apache2/sites-available/default +git submodule update --init --recursive + +sudo service apache2 restart + +# Xvfb +sudo bash /etc/init.d/xvfb start +sleep 1 # give xvfb some time to start + +# Fluxbox +fluxbox & +sleep 3 # give fluxbox some time to start + +# Composer in tests folder +cd tests/codeception +composer install +cd $BASE + +sudo cp RoboFile.dist.ini RoboFile.ini +sudo cp tests/codeception/acceptance.suite.dist.yml tests/codeception/acceptance.suite.yml diff --git a/codeception.yml b/codeception.yml new file mode 100644 index 0000000000000..932f95bacbed3 --- /dev/null +++ b/codeception.yml @@ -0,0 +1,36 @@ +actor: Tester +paths: + tests: tests/codeception + log: tests/codeception/_output + data: tests/codeception/_data + support: tests/codeception/_support + envs: tests/codeception/_envs +settings: + bootstrap: _bootstrap.php + colors: true + memory_limit: 1024M +extensions: + enabled: + - Codeception\Extension\RunFailed +modules: + config: + Db: + dsn: '' + user: '' + password: '' + dump: tests/codeception/_data/dump.sql +webdrivers: + firefox: + windows: tests\codeception\vendor\joomla-projects\selenium-server-standalone\bin\webdrivers\gecko\geckodriver64.exe + mac: tests/codeception/vendor/joomla-projects/selenium-server-standalone/bin/webdrivers/gecko/geckodriver_mac + linux: tests/codeception/vendor/joomla-projects/selenium-server-standalone/bin/webdrivers/gecko/geckodriver_linux_64 + chrome: + windows: tests\codeception\vendor\joomla-projects\selenium-server-standalone\bin\webdrivers\chrome\chromedriver.exe + mac: tests/codeception/vendor/joomla-projects/selenium-server-standalone/bin/webdrivers/chrome/chromedriver_mac + linux: tests/codeception/vendor/joomla-projects/selenium-server-standalone/bin/webdrivers/chrome/chromedriver_linux_64 + internet explorer: + windows: tests\codeception\vendor\joomla-projects\selenium-server-standalone\bin\webdrivers\internet-explorer32\IEDriverServer.exe + MicrosoftEdge: + windows: tests\codeception\vendor\joomla-projects\selenium-server-standalone\bin\webdrivers\edge\MicrosoftWebDriver.exe + MicrosoftEdgeInsiders: + windows: tests\codeception\vendor\joomla-projects\selenium-server-standalone\bin\webdrivers\edge-insiders\MicrosoftWebDriver.exe diff --git a/tests/codeception/README.md b/tests/codeception/README.md new file mode 100644 index 0000000000000..bd8892409e9bc --- /dev/null +++ b/tests/codeception/README.md @@ -0,0 +1,70 @@ +[Browser Automated Tests for Joomla! CMS(covering Users and Content features - GSoC 16)](https://summerofcode.withgoogle.com/projects/#5724182314745856) +=== + +Abstract +--- +System development nowadays more than ever starts to look for automated test methods. There are several main drivers for this trend: ++ Need for faster design‐develop‐test‐analysis cycle ++ Push for higher quality ++ Increasing complexity of systems and their integration and last but not least ++ Ever‐rising costs of manual testing + +Automation Testing means using an automation tool to execute test case suite. The automation software can also enter test data into the System Under Test, compare expected and actual results and generate detailed test reports. + +Test Automation demands considerable investments of money and resources. Successive development cycles will require execution of same test suite repeatedly. Using a test automation tool it's possible to record this test suite and re-play it as required. Once the test suite is automated, no human intervention is required. + +### Installation + +1. Clone this repository + +2. Install `composer` in your system. Read more about [how to install composer](https://getcomposer.org/doc/00-intro.md) here. + +3. Install composer packages using the following steps from root directory of this project. +We are using `composer.json` file for `tests/codeception` folder, so that you will have to run composer install from the tests directory. + + ```bash + $ cd tests/codeception && composer install + ``` + +4. Copy `tests/codeception/acceptance.suite.dist.yml` to `tests/codeception/acceptance.suite.yml` and change the settings according to your webserver. + + ``` + $ cp acceptance.suite.dist.yml acceptance.suite.yml + ``` + +5. Get back to project root directory using `$ cd ..` twice. + +### Run tests + +To run the tests please execute the following commands. We are using [Robo.li](http://robo.li/) to execute +[PhpUnit](https://phpunit.de/) and Selenium based [Codeception](http://codeception.com/for/joomla) test suits. + +#### To execute all the test features you should use. + +```bash +$ tests/codeception/vendor/bin/robo run:tests +``` + +#### You can individual run `feature` using following command. + +**_Linux & Mac_** +```bash +$ tests/codeception/vendor/bin/robo run:test +``` + +**_Windows_** +```cmd +$ tests\codeception\vendor\bin\robo run:test +``` + +If you want to see the steps then you can use `--steps` option of codeception. +Check [full codecept command list here](http://codeception.com/docs/reference/Commands#Run) + +**Note**: You can modify the timeout time by setting the value of the **TIMEOUT** constant lower for fast machines and higher for slow computers. +The constant located in the file `tests/codeception/acceptance/_bootstrap.php` + +Changing the browser the tests are running with? +--- +In your acceptance.suite.yml just change the browser name. Possible values are firefox, chrome, internet explorer and MicrosoftEdge. + +Note: If you are running Windows Insiders builds, then you need to set MicrosoftEdgeInsiders to true. diff --git a/tests/codeception/_bootstrap.php b/tests/codeception/_bootstrap.php new file mode 100644 index 0000000000000..243f9c85bc6da --- /dev/null +++ b/tests/codeception/_bootstrap.php @@ -0,0 +1,2 @@ +amOnPage($page); + } + + $I->dontSeeInPageSource('Notice:'); + $I->dontSeeInPageSource('Notice:'); + $I->dontSeeInPageSource('Warning:'); + $I->dontSeeInPageSource('Warning:'); + $I->dontSeeInPageSource('Strict standards:'); + $I->dontSeeInPageSource('Strict standards:'); + $I->dontSeeInPageSource('The requested page can\'t be found'); + } +} diff --git a/tests/codeception/_support/FunctionalTester.php b/tests/codeception/_support/FunctionalTester.php new file mode 100644 index 0000000000000..4ef26a8299d71 --- /dev/null +++ b/tests/codeception/_support/FunctionalTester.php @@ -0,0 +1,37 @@ +prefix = (isset($this->config['prefix'])) ? $this->config['prefix'] : ''; + + return parent::_initialize(); + } + + /** + * Inserts an SQL record into a database. This record will be + * erased after the test. + * + * @param string $table Table + * @param array $data Data + * + * @return integer The last insert id + * + * @since __DEPLOY_VERSION__ + */ + public function haveInDatabase($table, array $data) + { + $table = $this->addPrefix($table); + + return parent::haveInDatabase($table, $data); + } + + /** + * Find an entry in the database + * + * @param string $table Table + * @param array $criteria Criteria + * + * @return mixed|false + * + * @since __DEPLOY_VERSION__ + */ + public function findInDatabase($table, $criteria = []) + { + $table = $this->addPrefix($table); + + return parent::seeInDatabase($table, $criteria); + } + + /** + * Don't see in database + * + * @param string $table Table + * @param array $criteria Criteria + * + * @return mixed|false + * + * @since __DEPLOY_VERSION__ + */ + public function dontSeeInDatabase($table, $criteria = []) + { + $table = $this->addPrefix($table); + + return parent::dontSeeInDatabase($table, $criteria); + } + + /** + * Grab an entry from the database + * + * @param string $table Table + * @param string $column Column + * @param array $criteria Criteria + * + * @return mixed + * + * @since __DEPLOY_VERSION__ + */ + public function grabFromDatabase($table, $column, $criteria = null) + { + $table = $this->addPrefix($table); + + return parent::grabFromDatabase($table, $column, $criteria); + } + + /** + * Asserts that the given number of records were found in the database. + * + * @param int $expectedNumber Expected number + * @param string $table Table name + * @param array $criteria Search criteria [Optional] + * + * @return mixed|bool + * + * @since __DEPLOY_VERSION__ + */ + public function seeNumRecords($expectedNumber, $table, array $criteria = []) + { + $table = $this->addPrefix($table); + + return parent::seeNumRecords($expectedNumber, $table, $criteria); + } + + /** + * Returns the number of rows in a database + * + * @param string $table Table name + * @param array $criteria Search criteria [Optional] + * + * @return int + * + * @since __DEPLOY_VERSION__ + */ + public function grabNumRecords($table, array $criteria = []) + { + $table = $this->addPrefix($table); + + return parent::grabNumRecords($table, $criteria); + } + + /** + * Add the table prefix + * + * @param $table string Table without prefix + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + protected function addPrefix($table) + { + return $this->prefix . $table; + } +} diff --git a/tests/codeception/_support/Helper/Unit.php b/tests/codeception/_support/Helper/Unit.php new file mode 100644 index 0000000000000..aefb175f37992 --- /dev/null +++ b/tests/codeception/_support/Helper/Unit.php @@ -0,0 +1,23 @@ + 'system-message-container']; + + /** + * The element id which contains page title in administrator header. + * + * @var array + * @since __DEPLOY_VERSION__ + */ + public static $pageTitle = ['class' => 'page-title']; + + /** + * Locator for page title + * + * @var array + * @since __DEPLOY_VERSION__ + */ + public static $title = ['id' => 'jform_title']; + + /** + * Locator for search input field + * + * @var array + * @since __DEPLOY_VERSION__ + */ + public static $filterSearch = ['id' => 'filter_search']; + + /** + * Locator for status filter under search tool + * + * @var array + * @since __DEPLOY_VERSION__ + */ + public static $filterPublished = 'filter_published'; + + /** + * Locator for search button icon + * + * @var array + * @since __DEPLOY_VERSION__ + */ + public static $iconSearch = ['class' => 'icon-search']; + + /** + * Locator for the Tabs in Edit View + * + * @var array + * @since __DEPLOY_VERSION__ + */ + public static $tabsLocator = ['xpath' => "//ul[@id='myTabTabs']/li/a"]; + + /** + * Locator for the Check All checkbox + * + * @var array + * @since __DEPLOY_VERSION__ + */ + public static $checkAll = ['xpath' => "//thead//input[@name='checkall-toggle' or @name='toggle']"]; + + /** + * Method to search using given keyword + * + * @param string $keyword The keyword to search + * + * @since __DEPLOY_VERSION__ + * + * @return void + */ + public function search($keyword) + { + $I = $this; + + $I->amGoingTo('search for "' . $keyword . '"'); + $I->fillField(static::$filterSearch, $keyword); + $I->click(static::$iconSearch); + } + + /** + * Method to search user with username + * + * @param string $keyword The username of user + * + * @since __DEPLOY_VERSION__ + * + * @return void Checkbox for given username will be checked. + */ + public function haveItemUsingSearch($keyword) + { + $I = $this; + + $I->amOnPage(static::$url); + $I->search($keyword); + $I->checkAllResults(); + $I->wait(1); + } + + /** + * Method is used to see system message after waiting for page title. + * + * @param string $title The webpage title + * @param string $message The unpublish successful message + * + * @since __DEPLOY_VERSION__ + * + * @return void + */ + public function seeSystemMessage($title, $message) + { + $I = $this; + + $I->waitForPageTitle($title); + $I->see($message, self::$systemMessageContainer); + } + + /** + * Method is to Wait for page title until default timeout. + * + * @param string $title Page Title text + * + * @since __DEPLOY_VERSION__ + * + * @return void + */ + public function waitForPageTitle($title) + { + $I = $this; + $I->waitForText($title, TIMEOUT, self::$pageTitle); + } + + /** + * Function to select Toolbar buttons in Joomla! Admin Toolbar Panel + * + * @param string $button The full name of the button + * + * @since __DEPLOY_VERSION__ + * + * @return void + */ + public function clickToolbarButton($button) + { + $I = $this; + $input = strtolower($button); + + $suiteConfiguration = $I->getSuiteConfiguration(); + $screenWidth = explode("x", $suiteConfiguration['modules']['config']['JoomlaBrowser']['window_size']); + + if ($screenWidth[0] <= 480) + { + $I->click('Toolbar'); + } + + switch ($input) + { + case "new": + $I->click(['xpath' => "//div[@id='toolbar-new']//button"]); + break; + case "edit": + $I->click(['xpath' => "//div[@id='toolbar-edit']//button"]); + break; + case "publish": + $I->click(['xpath' => "//div[@id='toolbar-publish']//button"]); + break; + case "unpublish": + $I->click(['xpath' => "//div[@id='toolbar-unpublish']//button"]); + break; + case "archive": + $I->click(['xpath' => "//div[@id='toolbar-archive']//button"]); + break; + case "check-in": + $I->click(['xpath' => "//div[@id='toolbar-checkin']//button"]); + break; + case "batch": + $I->click(['xpath' => "//div[@id='toolbar-batch']//button"]); + break; + case "rebuild": + $I->click(['xpath' => "//div[@id='toolbar-refresh']//button"]); + break; + case "trash": + $I->click(['xpath' => "//div[@id='toolbar-trash']//button"]); + break; + case "save": + $I->click(['xpath' => "//div[@id='toolbar-apply']//button"]); + break; + case "save & close": + $I->click(['xpath' => "//div[@id='toolbar-save']//button"]); + break; + case "save & new": + $I->click(['xpath' => "//div[@id='toolbar-save-new']//button"]); + break; + case "cancel": + $I->click(['xpath' => "//div[@id='toolbar-cancel']//button"]); + break; + case "options": + $I->click(['xpath' => "//div[@id='toolbar-options']//button"]); + break; + case "empty trash": + case "delete": + $I->click(['xpath' => "//div[@id='toolbar-delete']//button"]); + break; + case "unblock": + $I->click(['xpath' => "//div[@id='toolbar-unblock']//button"]); + break; + case "featured": + $I->click(['xpath' => "//div[@id='toolbar-featured']//button"]); + break; + } + } + + /** + * Function to select all the item in the Search results in Administrator List + * + * Note: We recommend use of checkAllResults function only after searchForItem to be sure you are selecting only the desired result set + * + * @since __DEPLOY_VERSION__ + * + * @return void + */ + public function checkAllResults() + { + $I = $this; + + $I->comment("Selecting Checkall button"); + $I->click(self::$checkAll); + } + + /** + * Selects an option in a Chosen Selector based on its id + * + * @param string $selectId The id of the