Commit a8289074 authored by Son Nguyen's avatar Son Nguyen

Upgrade behat 3.0

behatnotnedded

Change-Id: Iaffc0dca9c98ee96c38c1ad9ecb060faa543f76d
parent 87918aab
{
"require": {
"php": ">=5.3.1",
"behat/behat": "2.5.4",
"behat/mink": "1.6.0",
"php": ">=5.3.3",
"behat/behat": "3.0.10",
"behat/mink": "1.6.1",
"behat/mink-extension": "1.3.3",
"behat/mink-extension": "2.0.1",
"behat/mink-goutte-driver": "1.1.0",
"behat/mink-selenium2-driver": "1.2.0",
"fabpot/goutte": "1.0.7",
"fabpot/goutte": "2.0.4",
"phpunit/phpunit": "3.7.38"
},
"autoload": {
"psr-0": {
"MaharaExtension": "../htdocs/testing/frameworks/behat/extension/"
}
}
}
......@@ -50,65 +50,30 @@ if (empty($CFG->directorypermissions)) {
}
$CFG->filepermissions = $CFG->directorypermissions & 0666;
if (defined('BEHAT_SITE_RUNNING')) {
// We already switched to behat test site previously.
}
else if (!empty($CFG->behat_wwwroot) ||empty($CFG->behat_dataroot) || !empty($CFG->behat_dbprefix)) {
// The behat is configured on this server, we need to find out if this is the behat test
// site based on the URL used for access.
require_once($CFG->docroot . '/testing/frameworks/behat/lib.php');
if (behat_is_test_site()) {
// Checking the integrity of the provided $CFG->behat_* vars and the
// selected wwwroot to prevent conflicts with production and phpunit environments.
behat_check_config_vars();
// Check that the directory does not contains other things.
if (!file_exists("$CFG->behat_dataroot/behattestdir.txt")) {
if ($dh = opendir($CFG->behat_dataroot)) {
while (($file = readdir($dh)) !== false) {
if ($file === 'behat' || $file === '.' || $file === '..' || $file === '.DS_Store') {
continue;
}
behat_error(BEHAT_EXITCODE_CONFIG, '$CFG->behat_dataroot directory is not empty, ensure this is the directory where you want to install behat test dataroot');
}
closedir($dh);
unset($dh);
unset($file);
}
if (defined('BEHAT_UTIL')) {
// Now we create dataroot directory structure for behat tests.
testing_initdataroot($CFG->behat_dataroot, 'behat');
} else {
behat_error(BEHAT_EXITCODE_INSTALL);
}
}
if (!defined('BEHAT_UTIL') && !defined('BEHAT_TEST')) {
// Somebody tries to access test site directly, tell them if not enabled.
if (!file_exists($CFG->behat_dataroot . '/behat/test_environment_enabled.txt')) {
behat_error(BEHAT_EXITCODE_CONFIG, 'Behat is configured but not enabled on this test site.');
}
}
// Constant used to inform that the behat test site is being used,
// this includes all the processes executed by the behat CLI command like
// the site reset, the steps executed by the browser drivers when simulating
// a user session and a real session when browsing manually to $CFG->behat_wwwroot
// like the browser driver does automatically.
// Different from BEHAT_TEST as only this last one can perform CLI
// actions like reset the site or use data generators.
define('BEHAT_SITE_RUNNING', true);
// Clean extra config.php settings.
//behat_clean_init_config();
// Check if the test mode is enabled
require_once($CFG->docroot . '/testing/frameworks/behat/classes/util.php');
if (BehatTestingUtil::is_test_site_enabled()) {
define('BEHAT_TEST', 1);
}
// When running behat tests or behat util CLI commnands,
// switch the $CFG->X for $CFG->behat_X.
if ((defined('BEHAT_UTIL')
|| defined('BEHAT_TEST')
)
&& (!empty($CFG->behat_wwwroot)
&& !empty($CFG->behat_dataroot)
&& !empty($CFG->behat_dbprefix))
) {
// Now we can begin switching $CFG->X for $CFG->behat_X.
// Keep the origin settings for validating only
$CFG->wwwroot_orig = $CFG->wwwroot;
$CFG->dataroot_orig = $CFG->dataroot;
$CFG->dbprefix_orig = $CFG->dbprefix;
$CFG->wwwroot = $CFG->behat_wwwroot;
$CFG->dbprefix = $CFG->behat_dbprefix;
$CFG->dataroot = $CFG->behat_dataroot;
}
$CFG->dbprefix = $CFG->behat_dbprefix;
}
// Fix up paths in $CFG
......
......@@ -155,7 +155,7 @@ class TestsFinder {
case 'features':
$regexp = '|' . $sep . 'tests' . $sep . 'behat' . $sep . '.*\.feature$|';
break;
case 'stepsdefinitions':
case 'contexts':
$regexp = '|' . $sep . 'tests' . $sep . 'behat' . $sep . 'Behat.*\.php$|';
break;
}
......
......@@ -74,6 +74,16 @@ abstract class TestingUtil {
return self::$originaldatafilesjson;
}
/**
* Return the mahara root dir which should contains htdocs and test directories
*
* @static
* @return string the mahara root dir
*/
public static function get_mahararoot() {
return dirname(dirname(dirname(__DIR__)));
}
/**
* Return the dataroot. It's useful when mocking the dataroot when unit testing this class itself.
*
......@@ -125,27 +135,12 @@ abstract class TestingUtil {
}
/**
* Make sure the db and dataroot settings is for a test site
* Checks if the mahara db and dataroot are enabled to test
*
* @static
* @return bool
*/
public static function is_test_site() {
$framework = self::get_framework();
if (!file_exists(self::get_dataroot() . '/' . $framework . 'testdir.txt')) {
// this is already tested in bootstrap script,
// but anyway presence of this file means the dataroot is for testing
return false;
}
if (table_exists(new XMLDBTable('config'))) {
if (!get_config($framework . 'test')) {
return false;
}
}
public static function is_test_site_enabled() {
return true;
}
......@@ -154,11 +149,12 @@ abstract class TestingUtil {
*
* @return bool
*/
public static function is_test_data_updated() {
public static function is_test_site_updated() {
$framework = self::get_framework();
$datarootpath = self::get_dataroot() . '/' . $framework;
if (!file_exists($datarootpath . '/tabledata.ser') or !file_exists($datarootpath . '/tablestructure.ser')) {
$datarootpath = self::get_dataroot() . DIRECTORY_SEPARATOR . $framework;
if (!file_exists($datarootpath . DIRECTORY_SEPARATOR . 'tabledata.ser')
|| !file_exists($datarootpath . DIRECTORY_SEPARATOR . 'tablestructure.ser')) {
return false;
}
......@@ -657,29 +653,29 @@ abstract class TestingUtil {
foreach ($pluginstocheck as $plugin) {
$dirhandle = opendir(get_config('docroot') . $plugin);
while (false !== ($dir = readdir($dirhandle))) {
if (strpos($dir, '.') === 0 or 'CVS' == $dir) {
if (strpos($dir, '.') === 0) {
continue;
}
if (!is_dir(get_config('docroot') . $plugin . '/' . $dir)) {
if (!is_dir(get_config('docroot') . $plugin . DIRECTORY_SEPARATOR . $dir)) {
continue;
}
$plugins[] = array($plugin, $dir);
if ($plugin == 'artefact') { // go check it for blocks as well
$btlocation = get_config('docroot') . $plugin . '/' . $dir . '/blocktype';
$btlocation = get_config('docroot') . $plugin . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR . 'blocktype';
if (!is_dir($btlocation)) {
continue;
}
$btdirhandle = opendir($btlocation);
while (false !== ($btdir = readdir($btdirhandle))) {
if (strpos($btdir, '.') === 0 or 'CVS' == $btdir) {
if (strpos($btdir, '.') === 0) {
continue;
}
if (!is_dir(get_config('docroot') . $plugin . '/' . $dir . '/blocktype/' . $btdir)) {
if (!is_dir(get_config('docroot') . $plugin . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR . 'blocktype' . DIRECTORY_SEPARATOR . $btdir)) {
continue;
}
$plugins[] = array('blocktype', $dir . '/' . $btdir);
$plugins[] = array('blocktype', $dir . DIRECTORY_SEPARATOR . $btdir);
}
}
}
......@@ -688,19 +684,19 @@ abstract class TestingUtil {
foreach ($plugins as $plugin) {
$plugintype = $plugin[0];
$pluginname = $plugin[1];
$pluginpath = "$plugin[0]/$plugin[1]";
$pluginpath = $plugin[0] . DIRECTORY_SEPARATOR . $plugin[1];
$pluginkey = "$plugin[0].$plugin[1]";
if ($plugintype == 'blocktype' && strpos($pluginname, '/') !== false) {
if ($plugintype == 'blocktype' && strpos($pluginname, DIRECTORY_SEPARATOR) !== false) {
// sigh.. we're a bit special...
$bits = explode('/', $pluginname);
$pluginpath = 'artefact/' . $bits[0] . '/blocktype/' . $bits[1];
$bits = explode(DIRECTORY_SEPARATOR, $pluginname);
$pluginpath = 'artefact' . DIRECTORY_SEPARATOR . $bits[0] . DIRECTORY_SEPARATOR . 'blocktype' . DIRECTORY_SEPARATOR . $bits[1];
}
log_info("Uninstalling $plugintype.$pluginname");
$location = get_config('docroot') . $pluginpath . '/db';
if (is_readable($location . '/install.xml')) {
uninstall_from_xmldb_file($location . '/install.xml');
$location = get_config('docroot') . $pluginpath . DIRECTORY_SEPARATOR . 'db';
if (is_readable($location . DIRECTORY_SEPARATOR . 'install.xml')) {
uninstall_from_xmldb_file($location . DIRECTORY_SEPARATOR . 'install.xml');
}
}
......@@ -721,7 +717,7 @@ abstract class TestingUtil {
execute_sql('SET foreign_key_checks = 0');
}
log_info('Uninstalling core');
uninstall_from_xmldb_file(get_config('docroot') . 'lib/db/install.xml');
uninstall_from_xmldb_file(get_config('docroot') . 'lib'.DIRECTORY_SEPARATOR.'db'.DIRECTORY_SEPARATOR.'install.xml');
if (is_mysql()) {
execute_sql('SET foreign_key_checks = 1');
}
......@@ -738,21 +734,24 @@ abstract class TestingUtil {
$framework = self::get_framework();
$childclassname = $framework . 'TestingUtil';
$files = scandir(self::get_dataroot() . '/' . $framework);
foreach ($files as $file) {
if (in_array($file, $childclassname::$datarootskipondrop)) {
continue;
$filedir = self::get_dataroot() . DIRECTORY_SEPARATOR . $framework;
if (file_exists($filedir)) {
$files = scandir($filedir);
foreach ($files as $file) {
if (in_array($file, $childclassname::$datarootskipondrop)) {
continue;
}
$path = $filedir . DIRECTORY_SEPARATOR . $file;
rmdirr($path);
}
$path = self::get_dataroot() . '/' . $framework . '/' . $file;
rmdirr($path);
}
$jsonfilepath = self::get_dataroot() . '/' . self::$originaldatafilesjson;
$jsonfilepath = self::get_dataroot() . DIRECTORY_SEPARATOR . self::$originaldatafilesjson;
if (file_exists($jsonfilepath)) {
// Delete the json file.
unlink($jsonfilepath);
// Delete the dataroot artefact.
rmdirr(self::get_dataroot() . '/artefact');
rmdirr(self::get_dataroot() . DIRECTORY_SEPARATOR .'artefact');
}
}
......@@ -763,7 +762,7 @@ abstract class TestingUtil {
* @param string $utilclassname the util class name..
*/
protected static function skip_original_data_files($utilclassname) {
$jsonfilepath = self::get_dataroot() . '/' . self::$originaldatafilesjson;
$jsonfilepath = self::get_dataroot() . DIRECTORY_SEPARATOR . self::$originaldatafilesjson;
if (file_exists($jsonfilepath)) {
$listfiles = file_get_contents($jsonfilepath);
......@@ -786,27 +785,26 @@ abstract class TestingUtil {
protected static function save_original_data_files() {
global $CFG;
$jsonfilepath = self::get_dataroot() . '/' . self::$originaldatafilesjson;
$jsonfilepath = self::get_dataroot() . DIRECTORY_SEPARATOR . self::$originaldatafilesjson;
$filedir = self::get_dataroot() . DIRECTORY_SEPARATOR . 'artefact';
// Save the original dataroot files if not done (only executed the first time).
if (!file_exists($jsonfilepath)) {
if (file_exists($filedir)
&& !file_exists($jsonfilepath)) {
$listfiles = array();
$listfiles['artefact/.'] = 'artefact/.';
$listfiles['artefact/..'] = 'artefact/..';
$filedir = self::get_dataroot() . '/artefact';
if (file_exists($filedir)) {
$directory = new RecursiveDirectoryIterator($filedir);
foreach (new RecursiveIteratorIterator($directory) as $file) {
if ($file->isDir()) {
$key = substr($file->getPath(), strlen(self::get_dataroot() . '/'));
}
else {
$key = substr($file->getPathName(), strlen(self::get_dataroot() . '/'));
}
$listfiles[$key] = $key;
$directory = new RecursiveDirectoryIterator($filedir);
foreach (new RecursiveIteratorIterator($directory) as $file) {
if ($file->isDir()) {
$key = substr($file->getPath(), strlen(self::get_dataroot() . DIRECTORY_SEPARATOR));
}
else {
$key = substr($file->getPathName(), strlen(self::get_dataroot() . DIRECTORY_SEPARATOR));
}
$listfiles[$key] = $key;
}
// Save the file list in a JSON file.
......
......@@ -8,7 +8,7 @@
* @copyright portions from mahara Behat, 2013 David Monllaó
*
*/
require_once(dirname(dirname(dirname(__DIR__))) . '/htdocs/testing/frameworks/behat/classes/BehatBase.php');
require_once(__DIR__ . '/BehatBase.php');
use Behat\Behat\Context\Step\Given as Given,
Behat\Gherkin\Node\TableNode as TableNode,
......
......@@ -8,7 +8,7 @@
* @copyright portions from mahara Behat, 2013 David Monllaó
*
*/
require_once(dirname(dirname(dirname(__DIR__))) . '/htdocs/testing/frameworks/behat/classes/BehatBase.php');
require_once(__DIR__ . '/BehatBase.php');
use Behat\Behat\Context\Step\Given as Given,
Behat\Gherkin\Node\TableNode as TableNode,
......
......@@ -58,7 +58,9 @@ class BehatBase extends Behat\MinkExtension\Context\RawMinkContext {
/**
* The JS code to check that the page is ready.
*/
const PAGE_READY_JS = '(isRequestStillProcessing === false) && (document.readyState === "complete")';
const PAGE_READY_JS = '(isRequestStillProcessing === false) &&
(document.readyState === "complete") &&
(jQuery(".collapsing").length === 0)';
/**
* @var Escaper
......@@ -235,20 +237,25 @@ class BehatBase extends Behat\MinkExtension\Context\RawMinkContext {
}
/**
* Escapes the double quote character.
*
* Double quote is the argument delimiter, it can be escaped
* with a backslash, but we auto-remove this backslashes
* before the step execution, this method is useful when using
* arguments as arguments for other steps.
* Escapes the double quote characters.
*
* @param string $string
* @return string
*/
public function escape($string) {
public function escapeDoubleQuotes($string) {
return str_replace('"', '\"', $string);
}
/**
* Unescapes the double quote characters.
*
* @param string $string
* @return string
*/
public function unescapeDoubleQuotes($string) {
return str_replace('\"', '"', $string);
}
/**
* Executes the passed closure until returns true or time outs.
*
......
......@@ -24,28 +24,6 @@ class BehatCommand {
*/
const DOCS_URL = 'https://wiki.mahara.org/wiki/Testing/Behat_Testing';
/**
* Ensures the behat dir exists in maharadata
* @return string Full path
*/
public static function get_behat_dir() {
global $CFG;
$behatdir = $CFG->behat_dataroot . '/behat';
if (!is_dir($behatdir)) {
if (!mkdir($behatdir, $CFG->directorypermissions, true)) {
behat_error(BEHAT_EXITCODE_PERMISSIONS, 'Directory ' . $behatdir . ' can not be created');
}
}
if (!is_writable($behatdir)) {
behat_error(BEHAT_EXITCODE_PERMISSIONS, 'Directory ' . $behatdir . ' is not writable');
}
return $behatdir;
}
/**
* Returns the executable path
*
......@@ -75,88 +53,68 @@ class BehatCommand {
$currentcwd = getcwd();
// Change to composer installed directory
chdir($CFG->docroot);
exec(get_composerroot_dir() . self::get_behat_command() . ' ' . $options . ' 2>/dev/null', $output, $code);
exec(get_composerroot_dir() . self::get_behat_command() . ' ' . $options, $output, $code);
chdir($currentcwd);
return array($output, $code);
}
/**
* Checks if behat is set up and working
*
* Notifies failures both from CLI and web interface.
*
* It checks behat dependencies have been installed and runs
* the behat help command to ensure it works as expected
* Checks if
* - behat and its composer dependencies are installed
* - behat and its composer dependencies are up to date with composer.json
* - behat is working
*
* @return int Error code or 0 if all ok
*/
public static function behat_setup_problem() {
global $CFG;
public static function get_behat_setup_status() {
// Mahara setting.
if (!self::are_behat_dependencies_installed()) {
if (!self::is_behat_installed()) {
return BEHAT_EXITCODE_NOTINSTALLED;
}
// Returning composer error code to avoid conflicts with behat and mahara error codes.
self::output_msg(get_string('errorcomposer', 'behat'));
return BEHAT_EXITCODE_COMPOSER;
if (!self::is_behat_updated()) {
return BEHAT_EXITCODE_NOTUPDATED;
}
// Behat test command.
// Run behat command.
list($output, $code) = self::run(' --help');
if ($code != 0) {
// Returning composer error code to avoid conflicts with behat and mahara error codes.
self::output_msg(get_string('errorbehatcommand', 'behat', self::get_behat_command()));
return BEHAT_EXITCODE_COMPOSER;
}
// No empty values.
if (empty($CFG->behat_dataroot) || empty($CFG->behat_dbprefix) || empty($CFG->behat_wwwroot)) {
self::output_msg(get_string('errorsetconfig', 'behat'));
return BEHAT_EXITCODE_CONFIG;
}
// Not repeated values.
// We only need to check this when the behat site is not running as
// at this point, when it is running, all $CFG->behat_* vars have
// already been copied to $CFG->dataroot, $CFG->dbprefix and $CFG->wwwroot.
if (!defined('BEHAT_SITE_RUNNING') &&
($CFG->behat_dbprefix == $CFG->dbprefix ||
$CFG->behat_dataroot == $CFG->dataroot ||
$CFG->behat_wwwroot == $CFG->wwwroot ||
(!empty($CFG->phpunit_dbprefix) && $CFG->phpunit_dbprefix == $CFG->behat_dbprefix) ||
(!empty($CFG->phpunit_dataroot) && $CFG->phpunit_dataroot == $CFG->behat_dataroot)
)) {
self::output_msg(get_string('erroruniqueconfig', 'behat'));
return BEHAT_EXITCODE_CONFIG;
}
// Checking behat dataroot existence otherwise echo about admin/tool/behat/cli/init.php.
if (!empty($CFG->behat_dataroot)) {
$CFG->behat_dataroot = realpath($CFG->behat_dataroot);
}
if (empty($CFG->behat_dataroot) || !is_dir($CFG->behat_dataroot) || !is_writable($CFG->behat_dataroot)) {
self::output_msg(get_string('errordataroot', 'behat'));
return BEHAT_EXITCODE_CONFIG;
return BEHAT_EXITCODE_CANNOTRUN;
}
return 0;
}
/**
* Has the site installed composer with --dev option
* Returns TRUE if behat and its components are installed
* @return bool
*/
public static function are_behat_dependencies_installed() {
if (!is_dir(get_composerroot_dir() . '/vendor/behat')) {
public static function is_behat_installed() {
if (!is_dir(get_composerroot_dir().DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'behat')) {
return false;
}
return true;
}
/**
* Returns TRUE if the composer lock file is up to date
* @return bool
*/
public static function is_behat_updated() {
$composerroot = get_composerroot_dir();
if (file_exists($composerroot.DIRECTORY_SEPARATOR.'composer.lock')
&& file_exists($composerroot.DIRECTORY_SEPARATOR.'composer.json')) {
$lock = json_decode(file_get_contents($composerroot.DIRECTORY_SEPARATOR.'composer.lock'))->hash;
$json = md5(file_get_contents($composerroot.DIRECTORY_SEPARATOR.'composer.json'));
if ($lock === $json) {
return true;
}
}
return false;
}
/**
* Outputs a message.
*
......
......@@ -67,7 +67,11 @@ class BehatForms extends BehatBase {
try {
// Expand fieldsets link.
$xpath = "//fieldset[contains(concat(' ', @class, ' '), ' collapsible ')]"
. "/legend/descendant::a[contains(concat(' ', @class, ' '), ' collapsed ')]";
. "/legend/descendant::a[contains(concat(' ', @class, ' '), ' collapsed ')]" .
"|" .
$xpath = "//div[contains(concat(' ', @class, ' '), ' collapsible-group ')]" .
"//a[contains(concat(' ', normalize-space(@data-toggle), ' '), ' collapse ')" .
" and contains(concat(' ', normalize-space(@class), ' '), ' collapsed ')]";
while ($collapseexpandlink = $this->find('xpath', $xpath, false, false, self::REDUCED_TIMEOUT)) {
$collapseexpandlink->click();
}
......@@ -422,6 +426,7 @@ class BehatForms extends BehatBase {
// We delegate to behat_form_field class, it will
// guess the type properly as it is a select tag.
$fieldlocator = $this->unescapeDoubleQuotes($fieldlocator);
$field = BehatFieldManager::get_form_field_from_label($fieldlocator, $this);
$field->set_value($value);
}
......
......@@ -20,11 +20,7 @@ use Behat\Mink\Exception\ExpectationException as ExpectationException,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
Behat\Mink\Exception\DriverException as DriverException,
WebDriver\Exception\NoSuchElement as NoSuchElement,
WebDriver\Exception\StaleElementReference as StaleElementReference,
Behat\Behat\Context\Step\Given as Given,
Behat\Behat\Context\Step\When as When,
Behat\Behat\Context\Step\Then as Then
;
WebDriver\Exception\StaleElementReference as StaleElementReference;
/**
* Cross plugin steps definitions.
......@@ -52,13 +48,17 @@ class BehatGeneral extends BehatBase {
* @Given /^I log in as "(?P<username>(?:[^"]|\\")*)" with password "(?P<password>(?:[^"]|\\")*)"$/
*/
public function i_login_as($username, $password) {
return array(
new Given('I am on homepage'),
new Given('I wait until the page is ready'),
new When('I fill in "login_username" with "' . $username .'"'),
new When('I fill in "login_password" with "' . $password .'"'),
new When('I press "Login"'),
$this->visitPath("/");
$this->wait_until_the_page_is_ready();
$this->getSession()->getPage()->fillField(
"login_username",
$username
);
$this->getSession()->getPage()->fillField(
"login_password",
$password
);
$this->getSession()->getPage()->pressButton("Login");
}
/**
......@@ -67,10 +67,9 @@ class BehatGeneral extends BehatBase {
* @Given /^I log out$/
*/
public function i_logout() {
return array(