Commit 971cb387 authored by Son Nguyen's avatar Son Nguyen Committed by Aaron Wells

Upgrade behat 3.0. Bug 1463203

Fix behat step definitions, features

Change-Id: I2bf4e94804b9e86c4e54e5f9e66af233b3f86abf
parent ffa2ed34
{
"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/"
}
}
}
......@@ -240,9 +240,11 @@ function saveitem(itemid) {
}
function changemenu() {
isPageRendering = true;
selectedmenu = $('menuselect').value;
getitems();
getadminfiles();
isPageRendering = false;
}
var selectedmenu = 'loggedoutmenu';
......
......@@ -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();
// Now we can begin switching $CFG->X for $CFG->behat_X.
$CFG->wwwroot = $CFG->behat_wwwroot;
$CFG->dbprefix = $CFG->behat_dbprefix;
$CFG->dataroot = $CFG->behat_dataroot;
}
// Check if the test mode is enabled
if (isset($CFG->behat_dataroot)) {
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')) {
if (empty($CFG->behat_wwwroot) || empty($CFG->behat_dataroot) || empty($CFG->behat_dbprefix)) {
log_('Behat tests cannot run unless $cfg->behat_wwwroot, $cfg->behat_dataroot, and $cfg->behat_dbprefix are defined in config.php');
die(1);
}
// Now we can begin switching $CFG->X for $CFG->behat_X.
// Keep the origin settings for validating only
$CFG->wwwroot_orig = isset($CFG->wwwroot) ? $CFG->wwwroot : null;
$CFG->dataroot_orig = isset($CFG->dataroot) ? $CFG->dataroot : null;
$CFG->dbprefix_orig = isset($CFG->dbprefix) ? $CFG->dbprefix : null;
$CFG->wwwroot = $CFG->behat_wwwroot;
$CFG->dataroot = $CFG->behat_dataroot;
$CFG->dbprefix = $CFG->behat_dbprefix;
}
// Fix up paths in $CFG
......
......@@ -265,6 +265,7 @@ function displayMessage(message, type, hideprevmsg) {
* = true if the request is still in progress
*/
var isRequestStillProcessing = false;
var isPageRendering = false;
/* Display a nice little loading notification */
function processingStart(msg) {
......
......@@ -531,6 +531,10 @@ EOF;
$stylesheets = get_stylesheets_for_current_page($stylesheets, $extraconfig);
// Disable CSS transforms, transitions, and animations when running behat tests
if (defined('BEHAT_TEST')) {
$stylesheets[] = get_config('wwwroot') . 'testing/frameworks/behat/no_transitions.css';
}
$smarty->assign('STYLESHEETLIST', $stylesheets);
if (!empty($theme_list)) {
// this gets assigned in smarty_core, but do it again here if it's changed locally
......
......@@ -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;
}
......
......@@ -9,6 +9,10 @@
*
*/
require_once(get_config('libroot') . 'institution.php');
require_once(get_config('libroot') . 'group.php');
require_once(get_config('libroot') . 'view.php');
/**
* Data generator class for unit tests and other tools like behat that need to create fake test sites.
*
......
......@@ -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;
}
......@@ -554,6 +550,12 @@ abstract class TestingUtil {
closedir($handle);
}
// Create the folder 'behat' in dataroot
$dirpath = self::get_dataroot() . '/' . self::get_framework();
if (!file_exists($dirpath)) {
mkdir($dirpath);
}
}
/**
......@@ -657,29 +659,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 +690,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 +723,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 +740,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 +768,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 +791,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,10 @@ 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) &&
(isPageRendering === false) &&
(document.readyState === "complete") &&
(jQuery(".collapsing").length === 0)';
/**
* @var Escaper
......@@ -235,20 +238,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.
*
......@@ -603,7 +611,7 @@ class BehatBase extends Behat\MinkExtension\Context\RawMinkContext {
function($context) {
// It may return 0 if tinyMCE is loaded but not the instances, so we just loop again.
$neditors = $context->getSession()->evaluateScript('return tinyMCE.editors.length;');
$neditors = $context->getSession()->evaluateScript('return tinymce.editors.length;');
if ($neditors == 0) {
return false;
}
......@@ -611,12 +619,12 @@ class BehatBase extends Behat\MinkExtension\Context\RawMinkContext {
// It may be there but not ready.
$iframeready = $context->getSession()->evaluateScript('
var readyeditors = new Array;
for (editorid in tinyMCE.editors) {
if (tinyMCE.editors[editorid].getDoc().readyState === "complete") {
for (editorid in tinymce.editors) {
if (tinymce.editors[editorid].getDoc().readyState === "complete") {
readyeditors[editorid] = editorid;
}
}
if (tinyMCE.editors.length === readyeditors.length) {
if (tinymce.editors.length === readyeditors.length) {
return "complete";
}
return "";
......
......@@ -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
*
......@@ -70,93 +48,72 @@ class BehatCommand {
* @return array CLI command outputs [0] => string, [1] => integer
*/
public final static function run($options = '') {
global $CFG;
$currentcwd = getcwd();
// Change to composer installed directory
chdir($CFG->docroot);
exec(get_composerroot_dir() . self::get_behat_command() . ' ' . $options . ' 2>/dev/null', $output, $code);
chdir(get_mahararoot_dir());
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)
)) {