This commit is contained in:
Mihail Geshoski
2026-03-11 11:33:07 +08:00
11 changed files with 286 additions and 15 deletions
+1 -1
View File
@@ -5,6 +5,6 @@
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core/network",["jquery","core/ajax","core/config","core/notification","core/str"],(function($,Ajax,Config,Notification,Str){var started=!1,warningDisplayed=!1,keepAliveFrequency=0,requestTimeout=0,keepAliveMessage=!1,sessionTimeout=!1,checkFrequency=1e3*Math.min(Config.sessiontimeout/10,600),warningLimit=Config.sessiontimeoutwarning>0?1e3*Config.sessiontimeoutwarning:2*checkFrequency,firstWait=Config.sessiontimeoutwarning>0?Math.min(1e3*(Config.sessiontimeout-Config.sessiontimeoutwarning),5*checkFrequency):5*checkFrequency,timeoutSessionExpired=function(modal){sessionTimeout=!0,warningDisplayed=!1,closeModal(modal),displaySessionExpired()},closeModal=function(modal){modal.destroy()},displaySessionExpired=function(){return Ajax.call([{methodname:"core_session_time_remaining",args:{}}],!0,!0,!0)[0].then((function(args){return!(1e3*args.timeremaining>warningLimit)&&Str.get_strings([{key:"sessionexpired",component:"error"},{key:"sessionerroruser",component:"error"},{key:"loginagain",component:"moodle"},{key:"cancel",component:"moodle"}]).then((function(strings){return Notification.confirm(strings[0],strings[1],strings[2],strings[3],(function(){return location.reload(),!0})),!0})).catch(Notification.exception)}))},touchSession=function(){return sessionTimeout?displaySessionExpired():Ajax.call([{methodname:"core_session_touch",args:{}}],!0,!0,!1,requestTimeout)[0].then((function(){return keepAliveFrequency>0&&setTimeout(touchSession,keepAliveFrequency),!0})).catch((function(){Notification.alert("",keepAliveMessage)}))},checkSession=function(){return sessionTimeout=!1,Ajax.call([{methodname:"core_session_time_remaining",args:{}}],!0,!0,!0)[0].then((function(args){return!(args.userid<=0)&&(args.timeremaining<=0?displaySessionExpired():(1e3*args.timeremaining<=warningLimit&&!warningDisplayed?(warningDisplayed=!0,Str.get_strings([{key:"norecentactivity",component:"moodle"},{key:"sessiontimeoutsoon",component:"moodle"},{key:"extendsession",component:"moodle"},{key:"cancel",component:"moodle"}]).then((function(strings){return Notification.confirm(strings[0],strings[1],strings[2],strings[3],(function(){return touchSession(),warningDisplayed=!1,setTimeout(checkSession,firstWait),!0}),(function(){setTimeout(checkSession,checkFrequency)}))})).then((modal=>{setTimeout(timeoutSessionExpired,1e3*args.timeremaining,modal)})).catch(Notification.exception)):setTimeout(checkSession,checkFrequency),!0))}))},start=function(){keepAliveFrequency>0?setTimeout(touchSession,keepAliveFrequency):setTimeout(checkSession,firstWait)};const isMoodleIframe=function(){if(window.parent===window)return!1;let parentUrl;try{parentUrl=window.parent.location.href}catch(e){return!1}return parentUrl.startsWith(M.cfg.wwwroot)};return{keepalive:async function(freq,timeout,identifier,component){started?window.console.warn("Ignoring session keep-alive. The core/network module was already initialised."):(started=!0,isMoodleIframe()?window.console.warn("Ignoring session keep-alive in this iframe inside another Moodle page."):(window.console.log("Starting Moodle session keep-alive."),keepAliveFrequency=1e3*freq,keepAliveMessage=await Str.get_string(identifier,component),requestTimeout=1e3*timeout,start()))},init:function(){started||(started=!0,isMoodleIframe()?window.console.log("Not starting Moodle session timeout warning in this iframe."):(window.console.log("Starting Moodle session timeout warning."),start()))}}}));
define("core/network",["jquery","core/ajax","core/config","core/notification","core/str","core/modal_save_cancel","core/modal_events","core/network_events"],(function($,Ajax,Config,Notification,Str,SaveCancelModal,ModalEvents,NetworkEvents){var started=!1,warningDisplayed=!1,keepAliveFrequency=0,requestTimeout=0,keepAliveMessage=!1,sessionTimeout=!1,checkFrequency=1e3*Math.min(Config.sessiontimeout/10,600),warningLimit=Config.sessiontimeoutwarning>0?1e3*Config.sessiontimeoutwarning:2*checkFrequency,firstWait=Config.sessiontimeoutwarning>0?Math.min(1e3*(Config.sessiontimeout-Config.sessiontimeoutwarning),5*checkFrequency):5*checkFrequency,alertModal=null,redirectUrl=null,connectionRetries=0,timeoutSessionExpired=function(modal){sessionTimeout=!0,warningDisplayed=!1,closeModal(modal),displaySessionExpired()},closeModal=function(modal){modal.destroy()},displaySessionExpired=function(){return Ajax.call([{methodname:"core_session_time_remaining",args:{}}],!0,!0,!0)[0].then((function(args){return!(1e3*args.timeremaining>warningLimit)&&Str.get_strings([{key:"sessionexpired",component:"error"},{key:"sessionerroruser",component:"error"},{key:"loginagain",component:"moodle"},{key:"cancel",component:"moodle"}]).then((function(strings){return Notification.confirm(strings[0],strings[1],strings[2],strings[3],(function(){return location.reload(),!0})),!0})).catch(Notification.exception)}))},touchSession=function(){return sessionTimeout?displaySessionExpired():Ajax.call([{methodname:"core_session_touch",args:{}}],!0,!0,!1,requestTimeout)[0].then((function(){return window.dispatchEvent(new CustomEvent(NetworkEvents.sessionTouched)),!0})).catch((function(){window.dispatchEvent(new CustomEvent(NetworkEvents.unstableConnection))}))},checkSession=function(){return sessionTimeout=!1,Ajax.call([{methodname:"core_session_time_remaining",args:{}}],!0,!0,!0)[0].then((function(args){return!(args.userid<=0)&&(args.timeremaining<=0?displaySessionExpired():(1e3*args.timeremaining<=warningLimit&&!warningDisplayed?(warningDisplayed=!0,Str.get_strings([{key:"norecentactivity",component:"moodle"},{key:"sessiontimeoutsoon",component:"moodle"},{key:"extendsession",component:"moodle"},{key:"cancel",component:"moodle"}]).then((function(strings){return Notification.confirm(strings[0],strings[1],strings[2],strings[3],(function(){return touchSession(),warningDisplayed=!1,setTimeout(checkSession,firstWait),!0}),(function(){setTimeout(checkSession,checkFrequency)}))})).then((modal=>{setTimeout(timeoutSessionExpired,1e3*args.timeremaining,modal)})).catch(Notification.exception)):setTimeout(checkSession,checkFrequency),!0))}))},start=function(){keepAliveFrequency>0?setTimeout(touchSession,keepAliveFrequency):setTimeout(checkSession,firstWait)};const isMoodleIframe=function(){if(window.parent===window)return!1;let parentUrl;try{parentUrl=window.parent.location.href}catch(e){return!1}return parentUrl.startsWith(M.cfg.wwwroot)};var leavePage=function(){"string"!=typeof redirectUrl?window.location.reload():window.location.replace(redirectUrl)},handleUnstableConnection=function(){if(connectionRetries<1)return alertModal.show(),setTimeout(touchSession,keepAliveFrequency),void connectionRetries++;null!==alertModal&&(alertModal.getRoot().find('[data-action="cancel"]').hide(),alertModal.show().then((function(){return alertModal.getRoot().on(ModalEvents.hidden,leavePage),!0})).catch(Notification.exception))},handleSessionTouched=function(){keepAliveFrequency>0&&(setTimeout(touchSession,keepAliveFrequency),connectionRetries=0)};return{keepalive:async function(freq,timeout,identifier,component){let redirect=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;if(started)window.console.warn("Ignoring session keep-alive. The core/network module was already initialised.");else if(started=!0,isMoodleIframe())window.console.warn("Ignoring session keep-alive in this iframe inside another Moodle page.");else{window.console.log("Starting Moodle session keep-alive."),keepAliveFrequency=1e3*freq,keepAliveMessage=await Str.get_string(identifier,component),requestTimeout=1e3*timeout,redirectUrl=redirect;var strings=await Str.get_strings([{key:"unstablenetwork",component:"mod_scorm"},{key:"leavepage",component:"mod_scorm"},{key:"refresh",component:"moodle"}]);(alertModal=await SaveCancelModal.create({title:strings[0],body:keepAliveMessage,buttons:{save:"string"==typeof redirectUrl?strings[1]:strings[2]},removeOnClose:!1,show:!1})).getRoot().on(ModalEvents.save,leavePage),window.addEventListener(NetworkEvents.sessionTouched,handleSessionTouched),window.addEventListener(NetworkEvents.unstableConnection,handleUnstableConnection),start()}},init:function(){started||(started=!0,isMoodleIframe()?window.console.log("Not starting Moodle session timeout warning in this iframe."):(window.console.log("Starting Moodle session timeout warning."),start()))}}}));
//# sourceMappingURL=network.min.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
define("core/network_events",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={sessionTouched:"network:sessiontouched",unstableConnection:"network:unstableconnection"},_exports.default}));
//# sourceMappingURL=network_events.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"network_events.min.js","sources":["../src/network_events.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Contains the events fired by the networking module.\n *\n * @module core/network_events\n * @copyright 2025 Sam Smucker <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n sessionTouched: 'network:sessiontouched',\n unstableConnection: 'network:unstableconnection',\n};\n"],"names":["sessionTouched","unstableConnection"],"mappings":"qKAsBe,CACXA,eAAgB,yBAChBC,mBAAoB"}
+105 -7
View File
@@ -20,8 +20,26 @@
* @copyright 2019 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/config', 'core/notification', 'core/str'],
function($, Ajax, Config, Notification, Str) {
define([
'jquery',
'core/ajax',
'core/config',
'core/notification',
'core/str',
'core/modal_save_cancel',
'core/modal_events',
'core/network_events',
],
function(
$,
Ajax,
Config,
Notification,
Str,
SaveCancelModal,
ModalEvents,
NetworkEvents,
) {
var started = false;
var warningDisplayed = false;
@@ -36,6 +54,12 @@ define(['jquery', 'core/ajax', 'core/config', 'core/notification', 'core/str'],
// First wait is minimum of remaining time or half of the session timeout.
var firstWait = (Config.sessiontimeoutwarning > 0) ?
Math.min((Config.sessiontimeout - Config.sessiontimeoutwarning) * 1000, checkFrequency * 5) : checkFrequency * 5;
// Modal to display when the connection is unstable.
var alertModal = null;
// URL to redirect to if network is unstable.
var redirectUrl = null;
// Number of times we have tried to touch the session after a network request failure.
var connectionRetries = 0;
/**
* The session time has expired - we can't extend it now.
* @param {Modal} modal
@@ -109,12 +133,10 @@ define(['jquery', 'core/ajax', 'core/config', 'core/notification', 'core/str'],
return displaySessionExpired();
} else {
return Ajax.call([request], true, true, false, requestTimeout)[0].then(function() {
if (keepAliveFrequency > 0) {
setTimeout(touchSession, keepAliveFrequency);
}
window.dispatchEvent(new CustomEvent(NetworkEvents.sessionTouched));
return true;
}).catch(function() {
Notification.alert('', keepAliveMessage);
window.dispatchEvent(new CustomEvent(NetworkEvents.unstableConnection));
});
}
};
@@ -231,6 +253,56 @@ define(['jquery', 'core/ajax', 'core/config', 'core/notification', 'core/str'],
start();
};
/**
* Leave the page, either by redirecting or reloading.
*/
var leavePage = function() {
if (typeof redirectUrl === 'string') {
window.location.replace(redirectUrl);
return;
}
window.location.reload();
};
/**
* When the network connection is unstable show the user an alert.
* If the unstable connection persists, force the user to exit the
* current page.
*/
var handleUnstableConnection = function() {
// Show the alert modal warning the user that their
// connection is unstable but allow them to continue
// interacting with the page if they choose.
if (connectionRetries < 1) {
alertModal.show();
setTimeout(touchSession, keepAliveFrequency);
connectionRetries++;
return;
}
// If we have failed to touch the session twice in a row,
// show the alert modal (if not already visible) but remove
// any option for the user to continue on this page.
if (alertModal !== null) {
alertModal.getRoot().find('[data-action="cancel"]').hide();
alertModal.show().then(function() {
alertModal.getRoot().on(ModalEvents.hidden, leavePage);
return true;
}).catch(Notification.exception);
}
};
/**
* When the session is touched, set a timeout to touch it again
* and reset the number of failed connection retries.
*/
var handleSessionTouched = function() {
if (keepAliveFrequency > 0) {
setTimeout(touchSession, keepAliveFrequency);
connectionRetries = 0;
}
};
/**
* Start polling with more specific values for the frequency, timeout and message.
*
@@ -238,8 +310,9 @@ define(['jquery', 'core/ajax', 'core/config', 'core/notification', 'core/str'],
* @param {number} timeout The time to wait for each request to the server.
* @param {string} identifier The string identifier for the message to show if session is going to time out.
* @param {string} component The string component for the message to show if session is going to time out.
* @param {string|null} redirect A url to redirect to in case of a lost connection to the server.
*/
var keepalive = async function(freq, timeout, identifier, component) {
var keepalive = async function(freq, timeout, identifier, component, redirect = null) {
// We only allow one concurrent instance of this checker.
if (started) {
window.console.warn('Ignoring session keep-alive. The core/network module was already initialised.');
@@ -257,6 +330,31 @@ define(['jquery', 'core/ajax', 'core/config', 'core/notification', 'core/str'],
keepAliveFrequency = freq * 1000;
keepAliveMessage = await Str.get_string(identifier, component);
requestTimeout = timeout * 1000;
redirectUrl = redirect;
var strings = await Str.get_strings([
{key: 'unstablenetwork', component: 'mod_scorm'},
{key: 'leavepage', component: 'mod_scorm'},
{key: 'refresh', component: 'moodle'},
]);
alertModal = await SaveCancelModal.create({
title: strings[0],
body: keepAliveMessage,
buttons: {
save: typeof redirectUrl === 'string' ? strings[1] : strings[2],
},
removeOnClose: false,
show: false
});
// The save button is renamed and will redirect the user or
// reload the page when clicked.
alertModal.getRoot().on(ModalEvents.save, leavePage);
window.addEventListener(NetworkEvents.sessionTouched, handleSessionTouched);
window.addEventListener(NetworkEvents.unstableConnection, handleUnstableConnection);
start();
};
+26
View File
@@ -0,0 +1,26 @@
// 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/>.
/**
* Contains the events fired by the networking module.
*
* @module core/network_events
* @copyright 2025 Sam Smucker <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default {
sessionTouched: 'network:sessiontouched',
unstableConnection: 'network:unstableconnection',
};
+15 -3
View File
@@ -1209,9 +1209,16 @@ class manager {
* @param string $component The string component for the message to show on failure.
* @param int $frequency The update frequency in seconds.
* @param int $timeout The timeout of each request in seconds.
* @throws \coding_exception IF the frequency is longer than the session lifetime.
* @param \moodle_url|string|null $redirect A URL to redirect to if connection is lost.
* @throws \coding_exception If the frequency is longer than the session lifetime.
*/
public static function keepalive($identifier = 'sessionerroruser', $component = 'error', $frequency = null, $timeout = 0) {
public static function keepalive(
$identifier = 'sessionerroruser',
$component = 'error',
$frequency = null,
$timeout = 0,
$redirect = null
) {
global $CFG, $PAGE;
if ($frequency) {
@@ -1224,11 +1231,16 @@ class manager {
$frequency = $CFG->sessiontimeout / 10;
}
if ($redirect instanceof \moodle_url) {
$redirect = $redirect->out();
}
$PAGE->requires->js_call_amd('core/network', 'keepalive', array(
$frequency,
$timeout,
$identifier,
$component
$component,
$redirect,
));
}
@@ -0,0 +1,58 @@
<?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/>.
/**
* Networking steps definitions
*
* @package core
* @category test
* @copyright 2025 Sam Smucker <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../behat/behat_base.php');
/**
* Steps definitions to trigger Javascript events in the network AMD module.
*
* @package core
* @category test
* @copyright 2025 Sam Smucker <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_networking extends behat_base {
/**
* Trigger a custom Javascript event indicating network instability.
*
* @When my network is unstable
*/
public function my_network_is_unstable() {
$this->getSession()
->getDriver()
->evaluateScript("window.dispatchEvent(new CustomEvent('network:unstableconnection'));");
}
/**
* Trigger a custom Javascript event indicating that the session has been touched.
*
* @When the session is touched
*/
public function the_session_is_touched() {
$this->getSession()
->getDriver()
->evaluateScript("window.dispatchEvent(new CustomEvent('network:sessiontouched'));");
}
}
+4 -2
View File
@@ -263,6 +263,7 @@ $string['lastattempt'] = 'Last completed attempt';
$string['lastattemptlock'] = 'Lock after final attempt';
$string['lastattemptlock_help'] = 'If enabled, a student is prevented from launching the SCORM player after using up all their allocated attempts.';
$string['lastattemptlockdesc'] = 'If enabled, a student is prevented from launching the SCORM player after using up all their allocated attempts.';
$string['leavepage'] = 'Leave page';
$string['location'] = 'Show the location bar';
$string['max'] = 'Max score';
$string['maximumattempts'] = 'Number of attempts';
@@ -299,8 +300,8 @@ There are 3 options:
$string['navdesc'] = 'This setting specifies whether to show or hide navigation buttons and their position.';
$string['navpositionleft'] = 'Position of navigation buttons from left in pixels.';
$string['navpositiontop'] = 'Position of navigation buttons from top in pixels.';
$string['networkdropped'] = 'The SCORM player has determined that your Internet connection is unreliable or has been interrupted. If you continue in this SCORM activity, your progress may not be saved.<br />
You should exit the activity now, and return when you have a dependable Internet connection.';
$string['networkdropped'] = 'It looks like you have an unstable internet connection or your session has timed out. Your progress may not be saved. Please exit and relaunch this activity.';
$string['networkdroppedmobile'] = 'It looks like you have an unstable internet connection or your session has timed out. Your progress may not be saved. Please exit and relaunch this activity. You may wish to return to this activity in the mobile app for a more stable connection.';
$string['newattempt'] = 'Start a new attempt';
$string['next'] = 'Continue';
$string['noactivity'] = 'Nothing to report';
@@ -439,6 +440,7 @@ $string['typeexternal'] = 'External SCORM manifest';
$string['typelocal'] = 'Uploaded package';
$string['typelocalsync'] = 'Downloaded package';
$string['undercontent'] = 'Under content';
$string['unstablenetwork'] = 'Unstable network';
$string['unziperror'] = 'An error occurs during package unzip';
$string['updatefreq'] = 'Auto-update frequency';
$string['updatefreq_error'] = 'Auto-update frequency can only be set when the package file is hosted externally';
+3 -1
View File
@@ -309,7 +309,9 @@ if (file_exists($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'.php'))
}
// Add the keepalive system to keep checking for a connection.
\core\session\manager::keepalive('networkdropped', 'mod_scorm', 30, 10);
$redirecturl = new moodle_url('/mod/scorm/view.php', ['id' => $cm->id]);
$stringname = get_config('core', 'enablemobilewebservice') ? 'networkdroppedmobile' : 'networkdropped';
\core\session\manager::keepalive($stringname, 'mod_scorm', 30, 10, $redirecturl);
echo $OUTPUT->footer();
@@ -0,0 +1,69 @@
@mod @mod_scorm
Feature: Unstable network
In order to avoid losing progress in a SCORM package
As a learner
I need to be notified if my network is unstable
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
And the following "activities" exist:
| activity | course | name | popup |
| scorm | C1 | Same window SCORM | 0 |
| scorm | C1 | New window SCORM | 1 |
@javascript
Scenario: The unstable network modal renders on the SCORM player page when network is unstable
When I am on the "Same window SCORM" "scorm activity" page logged in as "student1"
And I press "Enter"
And my network is unstable
Then I should see "It looks like you have an unstable internet connection or your session has timed out"
And I should see "Cancel" in the "Unstable network" "dialogue"
@javascript
Scenario: The unstable network modal can be closed without a redirect the first time it is shown
When I am on the "Same window SCORM" "scorm activity" page logged in as "student1"
And I press "Enter"
And my network is unstable
And I click on "Cancel" "button" in the "Unstable network" "dialogue"
Then I should not see "It looks like you have an unstable internet connection or your session has timed out"
And I should not see "Enter"
@javascript
Scenario: The unstable network modal cannot be closed without a redirect the second time it is shown
When I am on the "Same window SCORM" "scorm activity" page logged in as "student1"
And I press "Enter"
And my network is unstable
And my network is unstable
Then I should not see "Cancel" in the "Unstable network" "dialogue"
And I click on "Leave page" "button" in the "Unstable network" "dialogue"
And I should see "Enter"
@javascript
Scenario: Unstable network must be detected twice consecutively before a user must leave the SCORM activity
When I am on the "Same window SCORM" "scorm activity" page logged in as "student1"
And I press "Enter"
And my network is unstable
And I click on "Cancel" "button" in the "Unstable network" "dialogue"
And the session is touched
And my network is unstable
Then I should see "Cancel" in the "Unstable network" "dialogue"
And my network is unstable
And I should not see "Cancel" in the "Unstable network" "dialogue"
@javascript @_switch_window
Scenario: Unstable network modal redirects correctly when SCORM opens in new window
When I am on the "New window SCORM" "scorm activity" page logged in as "student1"
And I press "Enter"
And I switch to a second window
And my network is unstable
And I click on "Leave page" "button" in the "Unstable network" "dialogue"
Then I should see "Enter"
And I should see "New window SCORM"