725 lines
27 KiB
PHP
725 lines
27 KiB
PHP
<?php
|
|
// This file is part of Moodle - http://moodle.org/
|
|
//
|
|
// Moodle is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Moodle 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 General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
/**
|
|
* Mobile/desktop app steps definitions.
|
|
*
|
|
* @package core
|
|
* @category test
|
|
* @copyright 2018 The Open University
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
|
|
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
|
|
|
|
require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
|
|
|
|
use Behat\Gherkin\Node\TableNode;
|
|
use Behat\Mink\Exception\DriverException;
|
|
use Behat\Mink\Exception\ExpectationException;
|
|
|
|
/**
|
|
* Mobile/desktop app steps definitions.
|
|
*
|
|
* @package core
|
|
* @category test
|
|
* @copyright 2018 The Open University
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
class behat_app extends behat_base {
|
|
/** @var stdClass Object with data about launched Ionic instance (if any) */
|
|
protected static $ionicrunning = null;
|
|
|
|
/** @var string URL for running Ionic server */
|
|
protected $ionicurl = '';
|
|
|
|
/** @var bool Checks whether the app is runing a legacy version (ionic 3) */
|
|
protected $islegacy;
|
|
|
|
/**
|
|
* Checks if the current OS is Windows, from the point of view of task-executing-and-killing.
|
|
*
|
|
* @return bool True if Windows
|
|
*/
|
|
protected static function is_windows() : bool {
|
|
return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
|
|
}
|
|
|
|
/**
|
|
* Called from behat_hooks when a new scenario starts, if it has the app tag.
|
|
*
|
|
* This updates Moodle configuration and starts Ionic running, if it isn't already.
|
|
*/
|
|
public function start_scenario() {
|
|
$this->check_behat_setup();
|
|
$this->fix_moodle_setup();
|
|
$this->ionicurl = $this->start_or_reuse_ionic();
|
|
}
|
|
|
|
/**
|
|
* Opens the Moodle app in the browser and introduces the enters the site.
|
|
*
|
|
* @Given /^I enter the app$/
|
|
* @throws DriverException Issue with configuration or feature file
|
|
* @throws dml_exception Problem with Moodle setup
|
|
* @throws ExpectationException Problem with resizing window
|
|
*/
|
|
public function i_enter_the_app() {
|
|
$this->i_launch_the_app();
|
|
$this->enter_site();
|
|
}
|
|
|
|
/**
|
|
* Opens the Moodle app in the browser.
|
|
*
|
|
* @Given /^I launch the app$/
|
|
* @throws DriverException Issue with configuration or feature file
|
|
* @throws dml_exception Problem with Moodle setup
|
|
* @throws ExpectationException Problem with resizing window
|
|
*/
|
|
public function i_launch_the_app() {
|
|
// Check the app tag was set.
|
|
if (!$this->has_tag('app')) {
|
|
throw new DriverException('Requires @app tag on scenario or feature.');
|
|
}
|
|
|
|
// Go to page and prepare browser for app.
|
|
$this->prepare_browser();
|
|
}
|
|
|
|
/**
|
|
* Finds elements in the app.
|
|
*
|
|
* @Then /^I should(?P<not_boolean> not)? find "(?P<text_string>(?:[^"]|\\")*)"(?: near "(?P<near_string>(?:[^"]|\\")*)")? in the app$/
|
|
* @param string $not
|
|
* @param string $text
|
|
* @param string $near
|
|
*/
|
|
public function i_find_in_the_app($not, $text='', $near='') {
|
|
$not = !empty($not);
|
|
$text = addslashes_js($text);
|
|
$near = addslashes_js($near);
|
|
|
|
$this->spin(function() use ($not, $text, $near) {
|
|
$result = $this->evaluate_script("return window.behat.find(\"$text\", \"$near\");");
|
|
|
|
if ($not && $result === 'OK') {
|
|
throw new DriverException('Error, found an item that should not be found');
|
|
}
|
|
|
|
if (!$not && $result !== 'OK') {
|
|
throw new DriverException('Error finding item - ' . $result);
|
|
}
|
|
|
|
return true;
|
|
});
|
|
$this->wait_for_pending_js();
|
|
}
|
|
|
|
/**
|
|
* Check if elements are selected in the app.
|
|
*
|
|
* @Then /^"(?P<text_string>(?:[^"]|\\")*)"(?: near "(?P<near_string>(?:[^"]|\\")*)")? should(?P<not_boolean> not)? be selected in the app$/
|
|
* @param string $text
|
|
*/
|
|
public function be_selected_in_the_app($text, $near='', $not='') {
|
|
$not = !empty($not);
|
|
$text = addslashes_js($text);
|
|
$near = addslashes_js($near);
|
|
|
|
$this->spin(function() use ($not, $text, $near) {
|
|
$result = $this->evaluate_script("return window.behat.isSelected(\"$text\", \"$near\");");
|
|
|
|
switch ($result) {
|
|
case 'YES':
|
|
if ($not) {
|
|
throw new ExpectationException("Item was selected and shouldn't have", $this->getSession()->getDriver());
|
|
}
|
|
break;
|
|
case 'NO':
|
|
if (!$not) {
|
|
throw new ExpectationException("Item wasn't selected and should have", $this->getSession()->getDriver());
|
|
}
|
|
break;
|
|
default:
|
|
throw new DriverException('Error finding item - ' . $result);
|
|
}
|
|
|
|
return true;
|
|
});
|
|
$this->wait_for_pending_js();
|
|
}
|
|
|
|
/**
|
|
* Checks the Behat setup - tags and configuration.
|
|
*
|
|
* @throws DriverException
|
|
*/
|
|
protected function check_behat_setup() {
|
|
global $CFG;
|
|
|
|
// Check JavaScript is enabled.
|
|
if (!$this->running_javascript()) {
|
|
throw new DriverException('The app requires JavaScript.');
|
|
}
|
|
|
|
// Check the config settings are defined.
|
|
if (empty($CFG->behat_ionic_wwwroot) && empty($CFG->behat_ionic_dirroot)) {
|
|
throw new DriverException('$CFG->behat_ionic_wwwroot or $CFG->behat_ionic_dirroot must be defined.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fixes the Moodle admin settings to allow mobile app use (if not already correct).
|
|
*
|
|
* @throws dml_exception If there is any problem changing Moodle settings
|
|
*/
|
|
protected function fix_moodle_setup() {
|
|
global $CFG, $DB;
|
|
|
|
// Configure Moodle settings to enable app web services.
|
|
if (!$CFG->enablewebservices) {
|
|
set_config('enablewebservices', 1);
|
|
}
|
|
if (!$CFG->enablemobilewebservice) {
|
|
set_config('enablemobilewebservice', 1);
|
|
}
|
|
|
|
// Add 'Create token' and 'Use REST webservice' permissions to authenticated user role.
|
|
$userroleid = $DB->get_field('role', 'id', ['shortname' => 'user']);
|
|
$systemcontext = \context_system::instance();
|
|
role_change_permission($userroleid, $systemcontext, 'moodle/webservice:createtoken', CAP_ALLOW);
|
|
role_change_permission($userroleid, $systemcontext, 'webservice/rest:use', CAP_ALLOW);
|
|
|
|
// Check the value of the 'webserviceprotocols' config option. Due to weird behaviour
|
|
// in Behat with regard to config variables that aren't defined in a settings.php, the
|
|
// value in $CFG here may reflect a previous run, so get it direct from the database
|
|
// instead.
|
|
$field = $DB->get_field('config', 'value', ['name' => 'webserviceprotocols'], IGNORE_MISSING);
|
|
if (empty($field)) {
|
|
$protocols = [];
|
|
} else {
|
|
$protocols = explode(',', $field);
|
|
}
|
|
if (!in_array('rest', $protocols)) {
|
|
$protocols[] = 'rest';
|
|
set_config('webserviceprotocols', implode(',', $protocols));
|
|
}
|
|
|
|
// Enable mobile service.
|
|
require_once($CFG->dirroot . '/webservice/lib.php');
|
|
$webservicemanager = new webservice();
|
|
$service = $webservicemanager->get_external_service_by_shortname(
|
|
MOODLE_OFFICIAL_MOBILE_SERVICE, MUST_EXIST);
|
|
if (!$service->enabled) {
|
|
$service->enabled = 1;
|
|
$webservicemanager->update_external_service($service);
|
|
}
|
|
|
|
// If installed, also configure local_mobile plugin to enable additional features service.
|
|
$localplugins = core_component::get_plugin_list('local');
|
|
if (array_key_exists('mobile', $localplugins)) {
|
|
$service = $webservicemanager->get_external_service_by_shortname(
|
|
'local_mobile', MUST_EXIST);
|
|
if (!$service->enabled) {
|
|
$service->enabled = 1;
|
|
$webservicemanager->update_external_service($service);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Starts an Ionic server if necessary, or uses an existing one.
|
|
*
|
|
* @return string URL to Ionic server
|
|
* @throws DriverException If there's a system error starting Ionic
|
|
*/
|
|
protected function start_or_reuse_ionic() {
|
|
global $CFG;
|
|
|
|
if (empty($CFG->behat_ionic_dirroot) && !empty($CFG->behat_ionic_wwwroot)) {
|
|
// Use supplied Ionic server which should already be running.
|
|
$url = $CFG->behat_ionic_wwwroot;
|
|
} else if (self::$ionicrunning) {
|
|
// Use existing Ionic instance launched previously.
|
|
$url = self::$ionicrunning->url;
|
|
} else {
|
|
// Open Ionic process in relevant path.
|
|
$path = realpath($CFG->behat_ionic_dirroot);
|
|
$stderrfile = $CFG->dataroot . '/behat/ionic-stderr.log';
|
|
$prefix = '';
|
|
// Except on Windows, use 'exec' so that we get the pid of the actual Node process
|
|
// and not the shell it uses to execute. You can't do exec on Windows; there is a
|
|
// bypass_shell option but it is not the same thing and isn't usable here.
|
|
if (!self::is_windows()) {
|
|
$prefix = 'exec ';
|
|
}
|
|
$process = proc_open($prefix . 'ionic serve --no-interactive --no-open',
|
|
[['pipe', 'r'], ['pipe', 'w'], ['file', $stderrfile, 'w']], $pipes, $path);
|
|
if ($process === false) {
|
|
throw new DriverException('Error starting Ionic process');
|
|
}
|
|
fclose($pipes[0]);
|
|
|
|
// Get pid - we will need this to kill the process.
|
|
$status = proc_get_status($process);
|
|
$pid = $status['pid'];
|
|
|
|
// Read data from stdout until the server comes online.
|
|
// Note: On Windows it is impossible to read simultaneously from stderr and stdout
|
|
// because stream_select and non-blocking I/O don't work on process pipes, so that is
|
|
// why stderr was redirected to a file instead. Also, this code is simpler.
|
|
$url = null;
|
|
$stdoutlog = '';
|
|
while (true) {
|
|
$line = fgets($pipes[1], 4096);
|
|
if ($line === false) {
|
|
break;
|
|
}
|
|
|
|
$stdoutlog .= $line;
|
|
|
|
if (preg_match('~^\s*Local: (http\S*)~', $line, $matches)) {
|
|
$url = $matches[1];
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If it failed, close the pipes and the process.
|
|
if (!$url) {
|
|
fclose($pipes[1]);
|
|
proc_close($process);
|
|
$logpath = $CFG->dataroot . '/behat/ionic-start.log';
|
|
$stderrlog = file_get_contents($stderrfile);
|
|
@unlink($stderrfile);
|
|
file_put_contents($logpath,
|
|
"Ionic startup log from " . date('c') .
|
|
"\n\n----STDOUT----\n$stdoutlog\n\n----STDERR----\n$stderrlog");
|
|
throw new DriverException('Unable to start Ionic. See ' . $logpath);
|
|
}
|
|
|
|
// Remember the URL, so we can reuse it next time, and other details so we can kill
|
|
// the process.
|
|
self::$ionicrunning = (object)['url' => $url, 'process' => $process, 'pipes' => $pipes,
|
|
'pid' => $pid];
|
|
$url = self::$ionicrunning->url;
|
|
}
|
|
return $url;
|
|
}
|
|
|
|
/**
|
|
* Closes Ionic (if it was started) at end of test suite.
|
|
*
|
|
* @AfterSuite
|
|
*/
|
|
public static function close_ionic() {
|
|
if (self::$ionicrunning) {
|
|
fclose(self::$ionicrunning->pipes[1]);
|
|
|
|
if (self::is_windows()) {
|
|
// Using proc_terminate here does not work. It terminates the process but not any
|
|
// other processes it might have launched. Instead, we need to use an OS-specific
|
|
// mechanism to kill the process and children based on its pid.
|
|
exec('taskkill /F /T /PID ' . self::$ionicrunning->pid);
|
|
} else {
|
|
// On Unix this actually works, although only due to the 'exec' command inserted
|
|
// above.
|
|
proc_terminate(self::$ionicrunning->process);
|
|
}
|
|
self::$ionicrunning = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Goes to the app page and then sets up some initial JavaScript so we can use it.
|
|
*
|
|
* @param string $url App URL
|
|
* @throws DriverException If the app fails to load properly
|
|
*/
|
|
protected function prepare_browser() {
|
|
// Restart the browser and set its size.
|
|
$this->getSession()->restart();
|
|
$this->resize_window('360x720', true);
|
|
|
|
if (empty($this->ionicurl)) {
|
|
$this->ionicurl = $this->start_or_reuse_ionic();
|
|
}
|
|
|
|
// Check whether the app is running a legacy version.
|
|
$json = @file_get_contents("{$this->ionicurl}/assets/env.json") ?: @file_get_contents("{$this->ionicurl}/config.json");
|
|
$data = json_decode($json);
|
|
$appversion = $data->build->version ?? str_replace('-dev', '', $data->versionname);
|
|
|
|
$this->islegacy = version_compare($appversion, '3.9.5', '<');
|
|
|
|
// Visit the Ionic URL and wait for it to load.
|
|
$this->getSession()->visit($this->ionicurl);
|
|
$this->spin(function($context) {
|
|
$title = $context->getSession()->getPage()->find('xpath', '//title');
|
|
|
|
if ($title) {
|
|
$text = $title->getHtml();
|
|
|
|
if (
|
|
($this->islegacy && $text === 'Moodle Desktop') ||
|
|
(!$this->islegacy && $text === 'Moodle App')
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
throw new DriverException('Moodle app not found in browser');
|
|
}, false, 60);
|
|
|
|
// Run the scripts to install Moodle 'pending' checks.
|
|
$islegacyboolean = $this->islegacy ? 'true' : 'false';
|
|
$this->execute_script("window.BehatMoodleAppLegacy = $islegacyboolean;");
|
|
$this->execute_script(file_get_contents(__DIR__ . '/app_behat_runtime.js'));
|
|
|
|
// Assert initial page.
|
|
$this->spin(function($context) {
|
|
$page = $context->getSession()->getPage();
|
|
$element = $page->find('xpath', '//page-core-login-site//input[@name="url"]');
|
|
|
|
if ($element) {
|
|
// Wait for the onboarding modal to open, if any.
|
|
$this->wait_for_pending_js();
|
|
|
|
$element = $this->islegacy
|
|
? $page->find('xpath', '//page-core-login-site-onboarding')
|
|
: $page->find('xpath', '//core-login-site-onboarding');
|
|
|
|
if ($element) {
|
|
$this->i_press_in_the_app('Skip');
|
|
}
|
|
|
|
// Login screen found.
|
|
return true;
|
|
}
|
|
|
|
if ($page->find('xpath', '//page-core-mainmenu')) {
|
|
// Main menu found.
|
|
return true;
|
|
}
|
|
|
|
throw new DriverException('Moodle app not launched properly');
|
|
}, false, 60);
|
|
|
|
// Continue only after JS finishes.
|
|
$this->wait_for_pending_js();
|
|
}
|
|
|
|
protected function enter_site() {
|
|
if (!$this->is_in_login_page()) {
|
|
// Already in the site.
|
|
return;
|
|
}
|
|
|
|
global $CFG;
|
|
|
|
$this->i_set_the_field_in_the_app($this->islegacy ? 'campus.example.edu' : 'Your site', $CFG->wwwroot);
|
|
$this->i_press_in_the_app($this->islegacy ? 'Connect!' : 'Connect to your site');
|
|
$this->wait_for_pending_js();
|
|
}
|
|
|
|
/**
|
|
* Carries out the login steps for the app, assuming the user is on the app login page. Called
|
|
* from behat_auth.php.
|
|
*
|
|
* @param string $username Username (and password)
|
|
* @throws Exception Any error
|
|
*/
|
|
public function login(string $username) {
|
|
$this->i_set_the_field_in_the_app('Username', $username);
|
|
$this->i_set_the_field_in_the_app('Password', $username);
|
|
|
|
// Note there are two 'Log in' texts visible (the title and the button) so we have to use
|
|
// a 'near' value here.
|
|
$this->i_press_in_the_app('Log in', 'Forgotten');
|
|
|
|
// Wait until the main page appears.
|
|
$this->spin(
|
|
function($context, $args) {
|
|
$mainmenu = $context->getSession()->getPage()->find('xpath', '//page-core-mainmenu');
|
|
if ($mainmenu) {
|
|
return 'mainpage';
|
|
}
|
|
throw new DriverException('Moodle app main page not loaded after login');
|
|
}, false, 30);
|
|
|
|
// Wait for JS to finish as well.
|
|
$this->wait_for_pending_js();
|
|
}
|
|
|
|
/**
|
|
* Presses standard buttons in the app.
|
|
*
|
|
* @Given /^I press the (?P<button_name>back|main menu|page menu) button in the app$/
|
|
* @param string $button Button type
|
|
* @throws DriverException If the button push doesn't work
|
|
*/
|
|
public function i_press_the_standard_button_in_the_app(string $button) {
|
|
$this->spin(function($context, $args) use ($button) {
|
|
$result = $this->evaluate_script("return window.behat.pressStandard('{$button}');");
|
|
if ($result !== 'OK') {
|
|
throw new DriverException('Error pressing standard button - ' . $result);
|
|
}
|
|
return true;
|
|
});
|
|
$this->wait_for_pending_js();
|
|
}
|
|
|
|
/**
|
|
* Receives push notifications for forum events.
|
|
*
|
|
* @Given /^I receive a forum push notification for:$/
|
|
* @param TableNode $data
|
|
*/
|
|
public function i_receive_a_forum_push_notification(TableNode $data) {
|
|
global $DB, $CFG;
|
|
|
|
$data = (object) $data->getColumnsHash()[0];
|
|
$module = $DB->get_record('course_modules', ['idnumber' => $data->module]);
|
|
$discussion = $DB->get_record('forum_discussions', ['name' => $data->discussion]);
|
|
$notification = json_encode([
|
|
'site' => md5($CFG->wwwroot . $data->username),
|
|
'courseid' => $discussion->course,
|
|
'moodlecomponent' => 'mod_forum',
|
|
'name' => 'posts',
|
|
'contexturl' => '',
|
|
'notif' => 1,
|
|
'customdata' => [
|
|
'discussionid' => $discussion->id,
|
|
'cmid' => $module->id,
|
|
'instance' => $discussion->forum,
|
|
],
|
|
]);
|
|
|
|
$this->evaluate_script("return window.pushNotifications.notificationClicked($notification)");
|
|
$this->wait_for_pending_js();
|
|
}
|
|
|
|
/**
|
|
* Closes a popup by clicking on the 'backdrop' behind it.
|
|
*
|
|
* @Given /^I close the popup in the app$/
|
|
* @throws DriverException If there isn't a popup to close
|
|
*/
|
|
public function i_close_the_popup_in_the_app() {
|
|
$this->spin(function($context, $args) {
|
|
$result = $this->evaluate_script("return window.behat.closePopup();");
|
|
if ($result !== 'OK') {
|
|
throw new DriverException('Error closing popup - ' . $result);
|
|
}
|
|
return true;
|
|
});
|
|
$this->wait_for_pending_js();
|
|
}
|
|
|
|
/**
|
|
* Clicks on / touches something that is visible in the app.
|
|
*
|
|
* Note it is difficult to use the standard 'click on' or 'press' steps because those do not
|
|
* distinguish visible items and the app always has many non-visible items in the DOM.
|
|
*
|
|
* @Then /^I press "(?P<text_string>(?:[^"]|\\")*)"(?: near "(?P<near_string>(?:[^"]|\\")*)")? in the app$/
|
|
* @param string $text Text identifying click target
|
|
* @param string $near Text identifying a nearby unique piece of text
|
|
* @throws DriverException If the press doesn't work
|
|
*/
|
|
public function i_press_in_the_app($text, $near='') {
|
|
$this->press($text, $near);
|
|
}
|
|
|
|
/**
|
|
* Select an item from a list of options, such as a radio button.
|
|
*
|
|
* It may be necessary to use this step instead of "I press..." because radio buttons in Ionic are initialized
|
|
* with JavaScript, and clicks may not work until they are initialized properly which may cause flaky tests due
|
|
* to race conditions.
|
|
*
|
|
* @Then /^I select "(?P<text_string>(?:[^"]|\\")*)"(?: near "(?P<near_string>(?:[^"]|\\")*)")? in the app$/
|
|
* @param string $text Text identifying click target
|
|
* @param string $near Text identifying a nearby unique piece of text
|
|
* @throws DriverException If the press doesn't work
|
|
*/
|
|
public function i_select_in_the_app($text, $near='') {
|
|
$this->getSession()->wait(100);
|
|
$this->press($text, $near);
|
|
}
|
|
|
|
/**
|
|
* Check whether the current page is the login form.
|
|
*/
|
|
protected function is_in_login_page(): bool {
|
|
$page = $this->getSession()->getPage();
|
|
$logininput = $page->find('xpath', '//page-core-login-site//input[@name="url"]');
|
|
|
|
return !is_null($logininput);
|
|
}
|
|
|
|
/**
|
|
* Clicks on / touches something that is visible in the app, near some other text.
|
|
*
|
|
* If the $near is specified then when there are multiple matches, it picks the one
|
|
* nearest (in DOM terms) $near. $near should be an exact match, or a partial match that only
|
|
* has one result.
|
|
*
|
|
* @param behat_base $base Behat context
|
|
* @param string $text Text identifying click target
|
|
* @param string $near Text identifying a nearby unique piece of text
|
|
* @throws DriverException If the press doesn't work
|
|
*/
|
|
protected function press(string $text, string $near = '') {
|
|
$this->spin(function($context, $args) use ($text, $near) {
|
|
if ($near !== '') {
|
|
$nearbit = ', "' . addslashes_js($near) . '"';
|
|
} else {
|
|
$nearbit = '';
|
|
}
|
|
$result = $this->evaluate_script('return window.behat.press("' .
|
|
addslashes_js($text) . '"' . $nearbit .');');
|
|
if ($result !== 'OK') {
|
|
throw new DriverException('Error pressing item - ' . $result);
|
|
}
|
|
return true;
|
|
});
|
|
$this->wait_for_pending_js();
|
|
}
|
|
|
|
/**
|
|
* Sets a field to the given text value in the app.
|
|
*
|
|
* Currently this only works for input fields which must be identified using a partial or
|
|
* exact match on the placeholder text.
|
|
*
|
|
* @Given /^I set the field "(?P<field_name>(?:[^"]|\\")*)" to "(?P<text_string>(?:[^"]|\\")*)" in the app$/
|
|
* @param string $field Text identifying field
|
|
* @param string $value Value for field
|
|
* @throws DriverException If the field set doesn't work
|
|
*/
|
|
public function i_set_the_field_in_the_app(string $field, string $value) {
|
|
$this->spin(function($context, $args) use ($field, $value) {
|
|
$result = $this->evaluate_script('return window.behat.setField("' .
|
|
addslashes_js($field) . '", "' . addslashes_js($value) . '");');
|
|
if ($result !== 'OK') {
|
|
throw new DriverException('Error setting field - ' . $result);
|
|
}
|
|
return true;
|
|
});
|
|
$this->wait_for_pending_js();
|
|
}
|
|
|
|
/**
|
|
* Checks that the current header stripe in the app contains the expected text.
|
|
*
|
|
* This can be used to see if the app went to the expected page.
|
|
*
|
|
* @Then /^the header should be "(?P<text_string>(?:[^"]|\\")*)" in the app$/
|
|
* @param string $text Expected header text
|
|
* @throws DriverException If the header can't be retrieved
|
|
* @throws ExpectationException If the header text is different to the expected value
|
|
*/
|
|
public function the_header_should_be_in_the_app(string $text) {
|
|
$this->spin(function() use ($text) {
|
|
$result = $this->evaluate_script('return window.behat.getHeader();');
|
|
|
|
if (substr($result, 0, 3) !== 'OK:') {
|
|
throw new DriverException('Error getting header - ' . $result);
|
|
}
|
|
|
|
$header = substr($result, 3);
|
|
if (trim($header) !== trim($text)) {
|
|
throw new ExpectationException(
|
|
"The header text was not as expected: '$header'",
|
|
$this->getSession()->getDriver()
|
|
);
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check that the app opened a new browser tab.
|
|
*
|
|
* @Given /^the app should(?P<not_boolean> not)? have opened a browser tab$/
|
|
* @param string $not
|
|
*/
|
|
public function the_app_should_have_opened_a_browser_tab($not = '') {
|
|
$not = !empty($not);
|
|
|
|
$this->spin(function() use ($not) {
|
|
$openedbrowsertab = count($this->getSession()->getWindowNames()) === 2;
|
|
|
|
if ($not === $openedbrowsertab) {
|
|
throw new ExpectationException(
|
|
$not
|
|
? 'Did not expect the app to have opened a browser tab'
|
|
: 'Expected the app to have opened a browser tab',
|
|
$this->getSession()->getDriver()
|
|
);
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Switches to a newly-opened browser tab.
|
|
*
|
|
* This assumes the app opened a new tab.
|
|
*
|
|
* @Given /^I switch to the browser tab opened by the app$/
|
|
* @throws DriverException If there aren't exactly 2 tabs open
|
|
*/
|
|
public function i_switch_to_the_browser_tab_opened_by_the_app() {
|
|
$names = $this->getSession()->getWindowNames();
|
|
if (count($names) !== 2) {
|
|
throw new DriverException('Expected to see 2 tabs open, not ' . count($names));
|
|
}
|
|
$this->getSession()->switchToWindow($names[1]);
|
|
}
|
|
|
|
/**
|
|
* Closes the current browser tab.
|
|
*
|
|
* This assumes it was opened by the app and you will now get back to the app.
|
|
*
|
|
* @Given /^I close the browser tab opened by the app$/
|
|
* @throws DriverException If there aren't exactly 2 tabs open
|
|
*/
|
|
public function i_close_the_browser_tab_opened_by_the_app() {
|
|
$names = $this->getSession()->getWindowNames();
|
|
if (count($names) !== 2) {
|
|
throw new DriverException('Expected to see 2 tabs open, not ' . count($names));
|
|
}
|
|
$this->execute_script('window.close()');
|
|
$this->getSession()->switchToWindow($names[0]);
|
|
}
|
|
|
|
/**
|
|
* Switch navigator online mode.
|
|
*
|
|
* @Given /^I switch offline mode to "(?P<offline_string>(?:[^"]|\\")*)"$/
|
|
* @param string $offline New value for navigator online mode
|
|
* @throws DriverException If the navigator.online mode is not available
|
|
*/
|
|
public function i_switch_offline_mode(string $offline) {
|
|
$this->execute_script('appProvider.setForceOffline(' . $offline . ');');
|
|
}
|
|
}
|