commit
1f81ea3513
|
@ -63,3 +63,24 @@ function notify_on_error_exit {
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function get_behat_plugin_changes_diff {
|
||||||
|
i=0
|
||||||
|
previoushash=""
|
||||||
|
currenthash=`git rev-parse HEAD`
|
||||||
|
initialhash=`git rev-list HEAD | tail -n 1`
|
||||||
|
totalcommits=`git log --oneline | wc -l`
|
||||||
|
repositoryname=`echo $GITHUB_REPOSITORY | sed "s/\\//\\\\\\\\\\//"`
|
||||||
|
|
||||||
|
((totalcommits--))
|
||||||
|
while [ $i -lt $totalcommits ] && [[ -z $previoushash ]]; do
|
||||||
|
previoushash=`git rev-list --format=%B --max-count=1 HEAD~$i | grep -o "https:\/\/github\.com\/$repositoryname\/compare\/[^.]\+\.\.\.[^.]\+" | sed "s/https:\/\/github\.com\/$repositoryname\/compare\/[^.]\+\.\.\.//"`
|
||||||
|
((i++))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z $previoushash ]]; then
|
||||||
|
previoushash=$initialhash
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$previoushash...$currenthash"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
#!/bin/bash
|
||||||
|
source "./.github/scripts/functions.sh"
|
||||||
|
|
||||||
|
if [ -z $GIT_TOKEN ] || [ -z $BEHAT_PLUGIN_GITHUB_REPOSITORY ] || [ -z $BEHAT_PLUGIN_BRANCH ]; then
|
||||||
|
print_error "Env vars not correctly defined"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $BEHAT_PLUGIN_BRANCH != $GITHUB_REF_NAME ]]; then
|
||||||
|
echo "Script disabled for this branch"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clone plugin repository.
|
||||||
|
print_title "Cloning Behat plugin repository..."
|
||||||
|
|
||||||
|
git clone https://$GIT_TOKEN@github.com/$BEHAT_PLUGIN_GITHUB_REPOSITORY.git tmp/local_moodleappbehat -b integration
|
||||||
|
pluginversion=$(cat tmp/local_moodleappbehat/version.php | grep "\$plugin->version" | grep -o -E "[0-9]+")
|
||||||
|
|
||||||
|
# Auto-generate plugin.
|
||||||
|
print_title "Building Behat plugin..."
|
||||||
|
|
||||||
|
if [ -z $BEHAT_PLUGIN_EXCLUDE_FEATURES ]; then
|
||||||
|
scripts/build-behat-plugin.js tmp/local_moodleappbehat
|
||||||
|
else
|
||||||
|
scripts/build-behat-plugin.js tmp/local_moodleappbehat --exclude-features
|
||||||
|
fi
|
||||||
|
notify_on_error_exit "Unsuccessful build, stopping..."
|
||||||
|
|
||||||
|
# Check if there are any changes (ignoring plugin version).
|
||||||
|
print_title "Checking changes..."
|
||||||
|
|
||||||
|
newpluginversion=$(cat tmp/local_moodleappbehat/version.php | grep "\$plugin->version" | grep -o -E "[0-9]+")
|
||||||
|
sed -i s/\$plugin-\>version\ =\ [0-9]\\+\;/\$plugin-\>version\ =\ $pluginversion\;/ tmp/local_moodleappbehat/version.php
|
||||||
|
|
||||||
|
if [[ -z `git -C tmp/local_moodleappbehat/ status --short` ]]; then
|
||||||
|
echo "There weren't any changes to apply!"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $pluginversion -eq $newpluginversion ]]; then
|
||||||
|
((newpluginversion++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
sed -i s/\$plugin-\>version\ =\ [0-9]\\+\;/\$plugin-\>version\ =\ $newpluginversion\;/ tmp/local_moodleappbehat/version.php
|
||||||
|
|
||||||
|
# Apply new changes
|
||||||
|
print_title "Applying changes to repository..."
|
||||||
|
|
||||||
|
cd tmp/local_moodleappbehat
|
||||||
|
|
||||||
|
diff=`get_behat_plugin_changes_diff`
|
||||||
|
|
||||||
|
# Set up Github Actions bot user
|
||||||
|
# See https://github.community/t/github-actions-bot-email-address/17204/6
|
||||||
|
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config --local user.name "github-actions[bot]"
|
||||||
|
git add .
|
||||||
|
git commit -m "[auto-generated] Update plugin files
|
||||||
|
Check out the commits that caused these changes: https://github.com/$GITHUB_REPOSITORY/compare/$diff
|
||||||
|
"
|
||||||
|
notify_on_error_exit "Unsuccessful commit, stopping..."
|
||||||
|
|
||||||
|
echo "Pushing changes..."
|
||||||
|
git push
|
||||||
|
notify_on_error_exit "Unsuccessful push, stopping..."
|
||||||
|
|
||||||
|
echo "Behat plugin updated!"
|
|
@ -1,10 +1,10 @@
|
||||||
name: Behat tests
|
name: Acceptance tests (Behat)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tags:
|
behat_tags:
|
||||||
description: 'Execute tags'
|
description: 'Behat tags to execute'
|
||||||
required: true
|
required: true
|
||||||
default: '~@performance'
|
default: '~@performance'
|
||||||
moodle_branch:
|
moodle_branch:
|
||||||
|
@ -15,6 +15,10 @@ on:
|
||||||
description: 'Moodle repository'
|
description: 'Moodle repository'
|
||||||
required: true
|
required: true
|
||||||
default: 'https://github.com/moodle/moodle'
|
default: 'https://github.com/moodle/moodle'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- integration
|
||||||
|
- unscheduled
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
behat:
|
behat:
|
||||||
|
@ -23,6 +27,9 @@ jobs:
|
||||||
MOODLE_DOCKER_DB: pgsql
|
MOODLE_DOCKER_DB: pgsql
|
||||||
MOODLE_DOCKER_BROWSER: chrome
|
MOODLE_DOCKER_BROWSER: chrome
|
||||||
MOODLE_DOCKER_PHP_VERSION: 7.3
|
MOODLE_DOCKER_PHP_VERSION: 7.3
|
||||||
|
MOODLE_BRANCH: ${{ github.event.inputs.moodle_branch || 'master' }}
|
||||||
|
MOODLE_REPOSITORY: ${{ github.event.inputs.moodle_repository || 'https://github.com/moodle/moodle' }}
|
||||||
|
BEHAT_TAGS: ${{ github.event.inputs.behat_tags || '~@performance' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- id: nvmrc
|
- id: nvmrc
|
||||||
|
@ -32,7 +39,7 @@ jobs:
|
||||||
node-version: '${{ steps.nvmrc.outputs.node_version }}'
|
node-version: '${{ steps.nvmrc.outputs.node_version }}'
|
||||||
- name: Additional checkouts
|
- name: Additional checkouts
|
||||||
run: |
|
run: |
|
||||||
git clone --branch ${{ github.event.inputs.moodle_branch }} --depth 1 ${{ github.event.inputs.moodle_repository }} $GITHUB_WORKSPACE/moodle
|
git clone --branch $MOODLE_BRANCH --depth 1 $MOODLE_REPOSITORY $GITHUB_WORKSPACE/moodle
|
||||||
git clone --branch master --depth 1 https://github.com/moodlehq/moodle-docker $GITHUB_WORKSPACE/moodle-docker
|
git clone --branch master --depth 1 https://github.com/moodlehq/moodle-docker $GITHUB_WORKSPACE/moodle-docker
|
||||||
- name: Install npm packages
|
- name: Install npm packages
|
||||||
run: npm ci --no-audit
|
run: npm ci --no-audit
|
||||||
|
@ -48,9 +55,9 @@ jobs:
|
||||||
$GITHUB_WORKSPACE/moodle-docker/bin/moodle-docker-compose pull
|
$GITHUB_WORKSPACE/moodle-docker/bin/moodle-docker-compose pull
|
||||||
$GITHUB_WORKSPACE/moodle-docker/bin/moodle-docker-compose up -d
|
$GITHUB_WORKSPACE/moodle-docker/bin/moodle-docker-compose up -d
|
||||||
$GITHUB_WORKSPACE/moodle-docker/bin/moodle-docker-wait-for-db
|
$GITHUB_WORKSPACE/moodle-docker/bin/moodle-docker-wait-for-db
|
||||||
- name: Compile & launch production app with Docker
|
- name: Compile & launch app with Docker
|
||||||
run: |
|
run: |
|
||||||
docker build -t moodlehq/moodleapp:behat .
|
docker build --build-arg build_command="npm run build:test" -t moodlehq/moodleapp:behat .
|
||||||
docker run -d --rm --name moodleapp moodlehq/moodleapp:behat
|
docker run -d --rm --name moodleapp moodlehq/moodleapp:behat
|
||||||
docker network connect moodle-docker_default moodleapp --alias moodleapp
|
docker network connect moodle-docker_default moodleapp --alias moodleapp
|
||||||
- name: Init Behat
|
- name: Init Behat
|
||||||
|
@ -60,4 +67,4 @@ jobs:
|
||||||
- name: Run Behat tests
|
- name: Run Behat tests
|
||||||
run: |
|
run: |
|
||||||
export MOODLE_DOCKER_WWWROOT=$GITHUB_WORKSPACE/moodle
|
export MOODLE_DOCKER_WWWROOT=$GITHUB_WORKSPACE/moodle
|
||||||
$GITHUB_WORKSPACE/moodle-docker/bin/moodle-docker-compose exec -T webserver sh -c "php admin/tool/behat/cli/run.php --verbose --tags='@app&&${{ github.event.inputs.tags }}' --auto-rerun"
|
$GITHUB_WORKSPACE/moodle-docker/bin/moodle-docker-compose exec -T webserver sh -c "php admin/tool/behat/cli/run.php --verbose --tags='@app&&$BEHAT_TAGS' --auto-rerun"
|
||||||
|
|
|
@ -40,9 +40,9 @@ jobs:
|
||||||
$GITHUB_WORKSPACE/moodle-docker/bin/moodle-docker-compose pull
|
$GITHUB_WORKSPACE/moodle-docker/bin/moodle-docker-compose pull
|
||||||
$GITHUB_WORKSPACE/moodle-docker/bin/moodle-docker-compose up -d
|
$GITHUB_WORKSPACE/moodle-docker/bin/moodle-docker-compose up -d
|
||||||
$GITHUB_WORKSPACE/moodle-docker/bin/moodle-docker-wait-for-db
|
$GITHUB_WORKSPACE/moodle-docker/bin/moodle-docker-wait-for-db
|
||||||
- name: Compile & launch production app with Docker
|
- name: Compile & launch app with Docker
|
||||||
run: |
|
run: |
|
||||||
docker build -t moodlehq/moodleapp:performance .
|
docker build --build-arg build_command="npm run build:test" -t moodlehq/moodleapp:performance .
|
||||||
docker run -d --rm --name moodleapp moodlehq/moodleapp:performance
|
docker run -d --rm --name moodleapp moodlehq/moodleapp:performance
|
||||||
docker network connect moodle-docker_default moodleapp --alias moodleapp
|
docker network connect moodle-docker_default moodleapp --alias moodleapp
|
||||||
- name: Init Behat
|
- name: Init Behat
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
name: Update Behat plugin
|
||||||
|
|
||||||
|
on: ['push']
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
- name: Install npm packages
|
||||||
|
run: npm ci --no-audit
|
||||||
|
- name: Update Behat plugin
|
||||||
|
env:
|
||||||
|
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
|
||||||
|
BEHAT_PLUGIN_GITHUB_REPOSITORY: ${{ secrets.BEHAT_PLUGIN_GITHUB_REPOSITORY }}
|
||||||
|
BEHAT_PLUGIN_BRANCH: ${{ secrets.BEHAT_PLUGIN_BRANCH }}
|
||||||
|
run: ./.github/scripts/update_behat_plugin.sh
|
18
.travis.yml
18
.travis.yml
|
@ -68,21 +68,3 @@ jobs:
|
||||||
homebrew:
|
homebrew:
|
||||||
packages:
|
packages:
|
||||||
- jq
|
- jq
|
||||||
- stage: test
|
|
||||||
name: "End to end tests (mod_forum and mod_messages)"
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
if: type = cron
|
|
||||||
script: scripts/test_e2e.sh "@app&&@mod_forum" "@app&&@mod_messages"
|
|
||||||
- stage: test
|
|
||||||
name: "End to end tests (mod_course, core_course and mod_courses)"
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
if: type = cron
|
|
||||||
script: scripts/test_e2e.sh "@app&&@mod_course" "@app&&@core_course" "@app&&@mod_courses"
|
|
||||||
- stage: test
|
|
||||||
name: "End to end tests (others)"
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
if: type = cron
|
|
||||||
script: scripts/test_e2e.sh "@app&&~@mod_forum&&~@mod_messages&&~@mod_course&&~@core_course&&~@mod_courses"
|
|
||||||
|
|
|
@ -46,6 +46,12 @@
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/testing/testing.module.ts",
|
||||||
|
"with": "src/testing/testing.module.prod.ts"
|
||||||
|
}
|
||||||
|
],
|
||||||
"optimization": {
|
"optimization": {
|
||||||
"scripts": false,
|
"scripts": false,
|
||||||
"styles": true
|
"styles": true
|
||||||
|
|
|
@ -71,5 +71,5 @@ gulp.task('watch', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('watch-behat', () => {
|
gulp.task('watch-behat', () => {
|
||||||
gulp.watch(['./src/**/*.feature', './local-moodleappbehat'], { interval: 500 }, gulp.parallel('behat'));
|
gulp.watch(['./src/**/*.feature', './local_moodleappbehat'], { interval: 500 }, gulp.parallel('behat'));
|
||||||
});
|
});
|
||||||
|
|
|
@ -96,9 +96,8 @@ class behat_app extends behat_app_helper {
|
||||||
public function i_wait_the_app_to_restart() {
|
public function i_wait_the_app_to_restart() {
|
||||||
// Wait window to reload.
|
// Wait window to reload.
|
||||||
$this->spin(function() {
|
$this->spin(function() {
|
||||||
$result = $this->js("return !window.behat;");
|
if ($this->runtime_js('hasInitialized()')) {
|
||||||
|
// Behat runtime shouldn't be initialized after reload.
|
||||||
if (!$result) {
|
|
||||||
throw new DriverException('Window is not reloading properly.');
|
throw new DriverException('Window is not reloading properly.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,25 +114,25 @@ class behat_app extends behat_app_helper {
|
||||||
* @Then /^I should( not)? find (".+")( inside the .+)? in the app$/
|
* @Then /^I should( not)? find (".+")( inside the .+)? in the app$/
|
||||||
* @param bool $not Whether assert that the element was not found
|
* @param bool $not Whether assert that the element was not found
|
||||||
* @param string $locator Element locator
|
* @param string $locator Element locator
|
||||||
* @param string $containerName Container name
|
* @param string $container Container name
|
||||||
*/
|
*/
|
||||||
public function i_find_in_the_app(bool $not, string $locator, string $containerName = '') {
|
public function i_find_in_the_app(bool $not, string $locator, string $container = '') {
|
||||||
$locator = $this->parse_element_locator($locator);
|
$locator = $this->parse_element_locator($locator);
|
||||||
if (!empty($containerName)) {
|
if (!empty($container)) {
|
||||||
preg_match('/^ inside the (.+)$/', $containerName, $matches);
|
preg_match('/^ inside the (.+)$/', $container, $matches);
|
||||||
$containerName = $matches[1];
|
$container = $matches[1];
|
||||||
}
|
}
|
||||||
$containerName = json_encode($containerName);
|
$options = json_encode(['containerName' => $container]);
|
||||||
|
|
||||||
$this->spin(function() use ($not, $locator, $containerName) {
|
$this->spin(function() use ($not, $locator, $options) {
|
||||||
$result = $this->js("return window.behat.find($locator, $containerName);");
|
$result = $this->runtime_js("find($locator, $options)");
|
||||||
|
|
||||||
if ($not && $result === 'OK') {
|
if ($not && $result === 'OK') {
|
||||||
throw new DriverException('Error, found an item that should not be found');
|
throw new DriverException('Error, found an element that should not be found');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$not && $result !== 'OK') {
|
if (!$not && $result !== 'OK') {
|
||||||
throw new DriverException('Error finding item - ' . $result);
|
throw new DriverException('Error finding element - ' . $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -152,10 +151,10 @@ class behat_app extends behat_app_helper {
|
||||||
$locator = $this->parse_element_locator($locator);
|
$locator = $this->parse_element_locator($locator);
|
||||||
|
|
||||||
$this->spin(function() use ($locator) {
|
$this->spin(function() use ($locator) {
|
||||||
$result = $this->js("return window.behat.scrollTo($locator);");
|
$result = $this->runtime_js("scrollTo($locator)");
|
||||||
|
|
||||||
if ($result !== 'OK') {
|
if ($result !== 'OK') {
|
||||||
throw new DriverException('Error finding item - ' . $result);
|
throw new DriverException('Error finding element - ' . $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -175,7 +174,7 @@ class behat_app extends behat_app_helper {
|
||||||
*/
|
*/
|
||||||
public function i_load_more_items_in_the_app(bool $not = false) {
|
public function i_load_more_items_in_the_app(bool $not = false) {
|
||||||
$this->spin(function() use ($not) {
|
$this->spin(function() use ($not) {
|
||||||
$result = $this->js('return await window.behat.loadMoreItems();');
|
$result = $this->runtime_js('loadMoreItems()');
|
||||||
|
|
||||||
if ($not && $result !== 'ERROR: All items are already loaded.') {
|
if ($not && $result !== 'ERROR: All items are already loaded.') {
|
||||||
throw new DriverException('It should not have been possible to load more items');
|
throw new DriverException('It should not have been possible to load more items');
|
||||||
|
@ -200,7 +199,7 @@ class behat_app extends behat_app_helper {
|
||||||
public function i_swipe_in_the_app(string $direction) {
|
public function i_swipe_in_the_app(string $direction) {
|
||||||
$method = 'swipe' . ucwords($direction);
|
$method = 'swipe' . ucwords($direction);
|
||||||
|
|
||||||
$this->js("window.behat.getAngularInstance('ion-content', 'CoreSwipeNavigationDirective').$method()");
|
$this->runtime_js("getAngularInstance('ion-content', 'CoreSwipeNavigationDirective').$method()");
|
||||||
|
|
||||||
$this->wait_for_pending_js();
|
$this->wait_for_pending_js();
|
||||||
|
|
||||||
|
@ -219,21 +218,21 @@ class behat_app extends behat_app_helper {
|
||||||
$locator = $this->parse_element_locator($locator);
|
$locator = $this->parse_element_locator($locator);
|
||||||
|
|
||||||
$this->spin(function() use ($locator, $not) {
|
$this->spin(function() use ($locator, $not) {
|
||||||
$result = $this->js("return window.behat.isSelected($locator);");
|
$result = $this->runtime_js("isSelected($locator)");
|
||||||
|
|
||||||
switch ($result) {
|
switch ($result) {
|
||||||
case 'YES':
|
case 'YES':
|
||||||
if ($not) {
|
if ($not) {
|
||||||
throw new ExpectationException("Item was selected and shouldn't have", $this->getSession()->getDriver());
|
throw new ExpectationException("Element was selected and shouldn't have", $this->getSession()->getDriver());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'NO':
|
case 'NO':
|
||||||
if (!$not) {
|
if (!$not) {
|
||||||
throw new ExpectationException("Item wasn't selected and should have", $this->getSession()->getDriver());
|
throw new ExpectationException("Element wasn't selected and should have", $this->getSession()->getDriver());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new DriverException('Error finding item - ' . $result);
|
throw new DriverException('Error finding element - ' . $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -326,7 +325,7 @@ class behat_app extends behat_app_helper {
|
||||||
$this->login($username);
|
$this->login($username);
|
||||||
}
|
}
|
||||||
|
|
||||||
$mycoursesfound = $this->js("return window.behat.find({ text: 'My courses', selector: 'ion-tab-button'});");
|
$mycoursesfound = $this->runtime_js("find({ text: 'My courses', selector: 'ion-tab-button'})");
|
||||||
|
|
||||||
if ($mycoursesfound !== 'OK') {
|
if ($mycoursesfound !== 'OK') {
|
||||||
// My courses not present enter from Dashboard.
|
// My courses not present enter from Dashboard.
|
||||||
|
@ -382,7 +381,7 @@ class behat_app extends behat_app_helper {
|
||||||
*/
|
*/
|
||||||
public function i_press_the_standard_button_in_the_app(string $button) {
|
public function i_press_the_standard_button_in_the_app(string $button) {
|
||||||
$this->spin(function() use ($button) {
|
$this->spin(function() use ($button) {
|
||||||
$result = $this->js("return await window.behat.pressStandard('$button');");
|
$result = $this->runtime_js("pressStandard('$button')");
|
||||||
|
|
||||||
if ($result !== 'OK') {
|
if ($result !== 'OK') {
|
||||||
throw new DriverException('Error pressing standard button - ' . $result);
|
throw new DriverException('Error pressing standard button - ' . $result);
|
||||||
|
@ -420,7 +419,7 @@ class behat_app extends behat_app_helper {
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->js("window.behat.notificationClicked($notification)");
|
$this->zone_js("pushNotifications.notificationClicked($notification)", true);
|
||||||
$this->wait_for_pending_js();
|
$this->wait_for_pending_js();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,7 +507,7 @@ class behat_app extends behat_app_helper {
|
||||||
*/
|
*/
|
||||||
public function i_close_the_popup_in_the_app() {
|
public function i_close_the_popup_in_the_app() {
|
||||||
$this->spin(function() {
|
$this->spin(function() {
|
||||||
$result = $this->js("return window.behat.closePopup();");
|
$result = $this->runtime_js('closePopup()');
|
||||||
|
|
||||||
if ($result !== 'OK') {
|
if ($result !== 'OK') {
|
||||||
throw new DriverException('Error closing popup - ' . $result);
|
throw new DriverException('Error closing popup - ' . $result);
|
||||||
|
@ -536,7 +535,7 @@ class behat_app extends behat_app_helper {
|
||||||
* Clicks on / touches something that is visible in the app.
|
* 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
|
* 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.
|
* distinguish visible elements and the app always has many non-visible elements in the DOM.
|
||||||
*
|
*
|
||||||
* @When /^I press (".+") in the app$/
|
* @When /^I press (".+") in the app$/
|
||||||
* @param string $locator Element locator
|
* @param string $locator Element locator
|
||||||
|
@ -546,7 +545,7 @@ class behat_app extends behat_app_helper {
|
||||||
$locator = $this->parse_element_locator($locator);
|
$locator = $this->parse_element_locator($locator);
|
||||||
|
|
||||||
$this->spin(function() use ($locator) {
|
$this->spin(function() use ($locator) {
|
||||||
$result = $this->js("return await window.behat.press($locator);");
|
$result = $this->runtime_js("press($locator)");
|
||||||
|
|
||||||
if ($result !== 'OK') {
|
if ($result !== 'OK') {
|
||||||
throw new DriverException('Error pressing item - ' . $result);
|
throw new DriverException('Error pressing item - ' . $result);
|
||||||
|
@ -578,6 +577,33 @@ class behat_app extends behat_app_helper {
|
||||||
$this->wait_for_pending_js();
|
$this->wait_for_pending_js();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if elements can be pressed in the app.
|
||||||
|
*
|
||||||
|
* @Then /^I should( not)? be able to press (".+") in the app$/
|
||||||
|
* @param bool $not Whether to assert that the element cannot be pressed
|
||||||
|
* @param string $locator Element locator
|
||||||
|
*/
|
||||||
|
public function i_should_be_able_to_press_in_the_app(bool $not, string $locator) {
|
||||||
|
$locator = $this->parse_element_locator($locator);
|
||||||
|
|
||||||
|
$this->spin(function() use ($not, $locator) {
|
||||||
|
$result = $this->runtime_js("find($locator, { onlyClickable: true })");
|
||||||
|
|
||||||
|
if ($not && $result === 'OK') {
|
||||||
|
throw new DriverException('Error, found a clickable element that should not be found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$not && $result !== 'OK') {
|
||||||
|
throw new DriverException('Error finding clickable element - ' . $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->wait_for_pending_js();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select an item from a list of options, such as a radio button.
|
* Select an item from a list of options, such as a radio button.
|
||||||
*
|
*
|
||||||
|
@ -596,23 +622,23 @@ class behat_app extends behat_app_helper {
|
||||||
|
|
||||||
$this->spin(function() use ($selectedtext, $selected, $locator) {
|
$this->spin(function() use ($selectedtext, $selected, $locator) {
|
||||||
// Don't do anything if the item is already in the expected state.
|
// Don't do anything if the item is already in the expected state.
|
||||||
$result = $this->js("return window.behat.isSelected($locator);");
|
$result = $this->runtime_js("isSelected($locator)");
|
||||||
|
|
||||||
if ($result === $selected) {
|
if ($result === $selected) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Press item.
|
// Press element.
|
||||||
$result = $this->js("return await window.behat.press($locator);");
|
$result = $this->runtime_js("press($locator)");
|
||||||
|
|
||||||
if ($result !== 'OK') {
|
if ($result !== 'OK') {
|
||||||
throw new DriverException('Error pressing item - ' . $result);
|
throw new DriverException('Error pressing element - ' . $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that it worked as expected.
|
// Check that it worked as expected.
|
||||||
$this->wait_for_pending_js();
|
$this->wait_for_pending_js();
|
||||||
|
|
||||||
$result = $this->js("return window.behat.isSelected($locator);");
|
$result = $this->runtime_js("isSelected($locator)");
|
||||||
|
|
||||||
switch ($result) {
|
switch ($result) {
|
||||||
case 'YES':
|
case 'YES':
|
||||||
|
@ -646,7 +672,7 @@ class behat_app extends behat_app_helper {
|
||||||
$value = addslashes_js($value);
|
$value = addslashes_js($value);
|
||||||
|
|
||||||
$this->spin(function() use ($field, $value) {
|
$this->spin(function() use ($field, $value) {
|
||||||
$result = $this->js("return await window.behat.setField(\"$field\", \"$value\");");
|
$result = $this->runtime_js("setField('$field', '$value')");
|
||||||
|
|
||||||
if ($result !== 'OK') {
|
if ($result !== 'OK') {
|
||||||
throw new DriverException('Error setting field - ' . $result);
|
throw new DriverException('Error setting field - ' . $result);
|
||||||
|
@ -685,7 +711,7 @@ class behat_app extends behat_app_helper {
|
||||||
*/
|
*/
|
||||||
public function the_header_should_be_in_the_app(string $text) {
|
public function the_header_should_be_in_the_app(string $text) {
|
||||||
$this->spin(function() use ($text) {
|
$this->spin(function() use ($text) {
|
||||||
$result = $this->js('return window.behat.getHeader();');
|
$result = $this->runtime_js('getHeader()');
|
||||||
|
|
||||||
if (substr($result, 0, 3) !== 'OK:') {
|
if (substr($result, 0, 3) !== 'OK:') {
|
||||||
throw new DriverException('Error getting header - ' . $result);
|
throw new DriverException('Error getting header - ' . $result);
|
||||||
|
@ -766,7 +792,7 @@ class behat_app extends behat_app_helper {
|
||||||
* @When I run cron tasks in the app
|
* @When I run cron tasks in the app
|
||||||
*/
|
*/
|
||||||
public function i_run_cron_tasks_in_the_app() {
|
public function i_run_cron_tasks_in_the_app() {
|
||||||
$this->js('await window.behat.forceSyncExecution()');
|
$this->zone_js('cronDelegate.forceSyncExecution()');
|
||||||
$this->wait_for_pending_js();
|
$this->wait_for_pending_js();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -776,7 +802,7 @@ class behat_app extends behat_app_helper {
|
||||||
* @When I wait loading to finish in the app
|
* @When I wait loading to finish in the app
|
||||||
*/
|
*/
|
||||||
public function i_wait_loading_to_finish_in_the_app() {
|
public function i_wait_loading_to_finish_in_the_app() {
|
||||||
$this->js('await window.behat.waitLoadingToFinish()');
|
$this->runtime_js('waitLoadingToFinish()');
|
||||||
$this->wait_for_pending_js();
|
$this->wait_for_pending_js();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -798,7 +824,7 @@ class behat_app extends behat_app_helper {
|
||||||
$this->getSession()->switchToWindow($names[1]);
|
$this->getSession()->switchToWindow($names[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->js('window.close();');
|
$this->js('window.close()');
|
||||||
$this->getSession()->switchToWindow($names[0]);
|
$this->getSession()->switchToWindow($names[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -810,7 +836,7 @@ class behat_app extends behat_app_helper {
|
||||||
* @throws DriverException If the navigator.online mode is not available
|
* @throws DriverException If the navigator.online mode is not available
|
||||||
*/
|
*/
|
||||||
public function i_switch_offline_mode(string $offline) {
|
public function i_switch_offline_mode(string $offline) {
|
||||||
$this->js("window.behat.network.setForceOffline($offline);");
|
$this->runtime_js("network.setForceOffline($offline)");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -313,12 +313,12 @@ class behat_app_helper extends behat_base {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Init Behat JavaScript runtime.
|
// Init Behat JavaScript runtime.
|
||||||
|
$initoptions = json_encode([
|
||||||
|
'skipOnBoarding' => $options['skiponboarding'] ?? true,
|
||||||
|
'configOverrides' => $this->appconfig,
|
||||||
|
]);
|
||||||
|
|
||||||
$initOptions = new StdClass();
|
$this->runtime_js("init($initoptions)");
|
||||||
$initOptions->skipOnBoarding = $options['skiponboarding'] ?? true;
|
|
||||||
$initOptions->configOverrides = $this->appconfig;
|
|
||||||
|
|
||||||
$this->js('window.behatInit(' . json_encode($initOptions) . ');');
|
|
||||||
} catch (Exception $error) {
|
} catch (Exception $error) {
|
||||||
throw new DriverException('Moodle App not running or not running on Automated mode.');
|
throw new DriverException('Moodle App not running or not running on Automated mode.');
|
||||||
}
|
}
|
||||||
|
@ -456,7 +456,7 @@ class behat_app_helper extends behat_base {
|
||||||
|
|
||||||
$res = $this->evaluate_script("Promise.resolve($script)
|
$res = $this->evaluate_script("Promise.resolve($script)
|
||||||
.then(result => window.$promisevariable = result)
|
.then(result => window.$promisevariable = result)
|
||||||
.catch(error => window.$promisevariable = 'Async code rejected: ' + error?.message);");
|
.catch(error => window.$promisevariable = 'Async code rejected: ' + error?.message)");
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (microtime(true) - $start > $timeout) {
|
if (microtime(true) - $start > $timeout) {
|
||||||
|
@ -465,15 +465,42 @@ class behat_app_helper extends behat_base {
|
||||||
|
|
||||||
// 0.1 seconds.
|
// 0.1 seconds.
|
||||||
usleep(100000);
|
usleep(100000);
|
||||||
} while (!$this->evaluate_script("return '$promisevariable' in window;"));
|
} while (!$this->evaluate_script("'$promisevariable' in window"));
|
||||||
|
|
||||||
$result = $this->evaluate_script("return window.$promisevariable;");
|
$result = $this->evaluate_script("window.$promisevariable");
|
||||||
|
|
||||||
$this->evaluate_script("delete window.$promisevariable;");
|
$this->evaluate_script("delete window.$promisevariable");
|
||||||
|
|
||||||
|
if (is_string($result) && strrpos($result, 'Async code rejected:') === 0) {
|
||||||
|
throw new DriverException($result);
|
||||||
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate and execute methods from the Behat runtime.
|
||||||
|
*
|
||||||
|
* @param string $script
|
||||||
|
* @return mixed Result.
|
||||||
|
*/
|
||||||
|
protected function runtime_js(string $script) {
|
||||||
|
return $this->js("window.behat.$script");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate and execute methods from the Behat runtime inside the Angular zone.
|
||||||
|
*
|
||||||
|
* @param string $script
|
||||||
|
* @param bool $blocking
|
||||||
|
* @return mixed Result.
|
||||||
|
*/
|
||||||
|
protected function zone_js(string $script, bool $blocking = false) {
|
||||||
|
$blockingjson = json_encode($blocking);
|
||||||
|
|
||||||
|
return $this->runtime_js("runInZone(() => window.behat.$script, $blockingjson)");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a custom URL for automatic login and redirect from the Moodle App (and waits to finish.)
|
* Opens a custom URL for automatic login and redirect from the Moodle App (and waits to finish.)
|
||||||
*
|
*
|
||||||
|
@ -513,9 +540,10 @@ class behat_app_helper extends behat_base {
|
||||||
|
|
||||||
// Generate custom URL.
|
// Generate custom URL.
|
||||||
$parsed_url = parse_url($CFG->behat_wwwroot);
|
$parsed_url = parse_url($CFG->behat_wwwroot);
|
||||||
$domain = $parsed_url['host'] ?? '';
|
$site = $parsed_url['host'] ?? '';
|
||||||
$rootpath = $parsed_url['path'] ?? '';
|
$site .= isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
|
||||||
$url = $this->get_mobile_url_scheme() . "://$username@$domain$rootpath?token=$token&privatetoken=$privatetoken";
|
$site .= $parsed_url['path'] ?? '';
|
||||||
|
$url = $this->get_mobile_url_scheme() . "://$username@$site?token=$token&privatetoken=$privatetoken";
|
||||||
|
|
||||||
if (!empty($path)) {
|
if (!empty($path)) {
|
||||||
$url .= '&redirect='.urlencode($CFG->behat_wwwroot.$path);
|
$url .= '&redirect='.urlencode($CFG->behat_wwwroot.$path);
|
||||||
|
@ -548,8 +576,7 @@ class behat_app_helper extends behat_base {
|
||||||
* @param string $successXPath The XPath of the element to lookat after navigation.
|
* @param string $successXPath The XPath of the element to lookat after navigation.
|
||||||
*/
|
*/
|
||||||
protected function handle_url(string $customurl, string $successXPath = '') {
|
protected function handle_url(string $customurl, string $successXPath = '') {
|
||||||
// Instead of using evaluate_async_script, we wait for the path to load.
|
$result = $this->zone_js("customUrlSchemes.handleCustomURL('$customurl')");
|
||||||
$result = $this->js("return await window.behat.handleCustomURL('$customurl');");
|
|
||||||
|
|
||||||
if ($result !== 'OK') {
|
if ($result !== 'OK') {
|
||||||
throw new DriverException('Error handling url - ' . $result);
|
throw new DriverException('Error handling url - ' . $result);
|
|
@ -178,7 +178,7 @@ class performance_measure implements behat_app_listener {
|
||||||
* @return int Current time in milliseconds.
|
* @return int Current time in milliseconds.
|
||||||
*/
|
*/
|
||||||
private function now(): int {
|
private function now(): int {
|
||||||
return $this->driver->evaluateScript('Date.now();');
|
return $this->driver->evaluateScript('Date.now()');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -229,12 +229,31 @@
|
||||||
"uri-js": "^4.2.2"
|
"uri-js": "^4.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"core-js": {
|
"core-js": {
|
||||||
"version": "3.6.4",
|
"version": "3.6.4",
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz",
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz",
|
||||||
"integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==",
|
"integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"open": {
|
"open": {
|
||||||
"version": "7.0.4",
|
"version": "7.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/open/-/open-7.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/open/-/open-7.0.4.tgz",
|
||||||
|
@ -3889,6 +3908,16 @@
|
||||||
"strip-json-comments": "^3.1.1"
|
"strip-json-comments": "^3.1.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.3.1",
|
"version": "4.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
|
||||||
|
@ -3913,6 +3942,15 @@
|
||||||
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
|
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
@ -11772,12 +11810,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0"
|
||||||
"concat-map": "0.0.1"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"braces": {
|
"braces": {
|
||||||
|
@ -12327,6 +12365,16 @@
|
||||||
"integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
|
"integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||||
|
@ -12447,6 +12495,17 @@
|
||||||
"table": "^5.2.3",
|
"table": "^5.2.3",
|
||||||
"text-table": "^0.2.0",
|
"text-table": "^0.2.0",
|
||||||
"v8-compile-cache": "^2.0.3"
|
"v8-compile-cache": "^2.0.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"minimatch": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslint-scope": {
|
"eslint-scope": {
|
||||||
|
@ -13433,7 +13492,7 @@
|
||||||
"concat-map": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||||
},
|
},
|
||||||
"concat-stream": {
|
"concat-stream": {
|
||||||
"version": "1.6.2",
|
"version": "1.6.2",
|
||||||
|
@ -16449,6 +16508,16 @@
|
||||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
|
||||||
|
@ -16493,6 +16562,15 @@
|
||||||
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
|
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
@ -16625,6 +16703,16 @@
|
||||||
"tsconfig-paths": "^3.9.0"
|
"tsconfig-paths": "^3.9.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"doctrine": {
|
"doctrine": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
|
||||||
|
@ -16634,6 +16722,15 @@
|
||||||
"esutils": "^2.0.2",
|
"esutils": "^2.0.2",
|
||||||
"isarray": "^1.0.0"
|
"isarray": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -17635,6 +17732,16 @@
|
||||||
"color-convert": "^1.9.0"
|
"color-convert": "^1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"braces": {
|
"braces": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
|
||||||
|
@ -17766,6 +17873,15 @@
|
||||||
"to-regex": "^3.0.2"
|
"to-regex": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "5.7.1",
|
"version": "5.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||||
|
@ -18326,6 +18442,25 @@
|
||||||
"minimatch": "^3.0.4",
|
"minimatch": "^3.0.4",
|
||||||
"once": "^1.3.0",
|
"once": "^1.3.0",
|
||||||
"path-is-absolute": "^1.0.0"
|
"path-is-absolute": "^1.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"requires": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
|
"requires": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"glob-base": {
|
"glob-base": {
|
||||||
|
@ -19962,6 +20097,25 @@
|
||||||
"integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
|
"integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimatch": "^3.0.4"
|
"minimatch": "^3.0.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"requires": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
|
"requires": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"image-size": {
|
"image-size": {
|
||||||
|
@ -20959,6 +21113,16 @@
|
||||||
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
|
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
|
@ -20968,6 +21132,15 @@
|
||||||
"ansi-styles": "^4.1.0",
|
"ansi-styles": "^4.1.0",
|
||||||
"supports-color": "^7.1.0"
|
"supports-color": "^7.1.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -23349,11 +23522,12 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.0.4",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
|
||||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
|
@ -25138,6 +25312,16 @@
|
||||||
"which": "^1.3.1"
|
"which": "^1.3.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"cacache": {
|
"cacache": {
|
||||||
"version": "12.0.4",
|
"version": "12.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
|
||||||
|
@ -25200,6 +25384,15 @@
|
||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"minipass": {
|
"minipass": {
|
||||||
"version": "2.9.0",
|
"version": "2.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
|
||||||
|
@ -28127,6 +28320,27 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimatch": "3.0.4"
|
"minimatch": "3.0.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"reflect-metadata": {
|
"reflect-metadata": {
|
||||||
|
@ -31856,6 +32070,27 @@
|
||||||
"@istanbuljs/schema": "^0.1.2",
|
"@istanbuljs/schema": "^0.1.2",
|
||||||
"glob": "^7.1.4",
|
"glob": "^7.1.4",
|
||||||
"minimatch": "^3.0.4"
|
"minimatch": "^3.0.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"text-table": {
|
"text-table": {
|
||||||
|
|
|
@ -172,6 +172,7 @@
|
||||||
"jest": "^26.5.2",
|
"jest": "^26.5.2",
|
||||||
"jest-preset-angular": "^8.3.1",
|
"jest-preset-angular": "^8.3.1",
|
||||||
"jsonc-parser": "^2.3.1",
|
"jsonc-parser": "^2.3.1",
|
||||||
|
"minimatch": "^5.1.0",
|
||||||
"native-run": "^1.4.0",
|
"native-run": "^1.4.0",
|
||||||
"patch-package": "^6.4.7",
|
"patch-package": "^6.4.7",
|
||||||
"terser-webpack-plugin": "^4.2.3",
|
"terser-webpack-plugin": "^4.2.3",
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
const minimatch = require('minimatch');
|
||||||
const { existsSync, readFileSync, writeFileSync, statSync, renameSync, rmSync } = require('fs');
|
const { existsSync, readFileSync, writeFileSync, statSync, renameSync, rmSync } = require('fs');
|
||||||
const { readdir } = require('fs').promises;
|
const { readdir } = require('fs').promises;
|
||||||
const { mkdirSync, copySync } = require('fs-extra');
|
const { mkdirSync, copySync } = require('fs-extra');
|
||||||
|
@ -21,12 +22,22 @@ const { resolve, extname, dirname, basename, relative } = require('path');
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const pluginPath = process.argv[2] || guessPluginPath() || fail('Folder argument missing!');
|
const pluginPath = process.argv[2] || guessPluginPath() || fail('Folder argument missing!');
|
||||||
|
const excludeFeatures = process.argv.some(arg => arg === '--exclude-features');
|
||||||
|
const exclusions = excludeFeatures
|
||||||
|
? [
|
||||||
|
'*.feature',
|
||||||
|
'**/js/mobile/index.js',
|
||||||
|
'**/db/mobile.php',
|
||||||
|
'**/classes/output/mobile.php',
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
if (!existsSync(pluginPath)) {
|
if (!existsSync(pluginPath)) {
|
||||||
mkdirSync(pluginPath);
|
mkdirSync(pluginPath);
|
||||||
} else {
|
} else {
|
||||||
// Empty directory, except the excluding list.
|
// Empty directory, except the excluding list.
|
||||||
const excludeFromErase = [
|
const excludeFromErase = [
|
||||||
|
...exclusions,
|
||||||
'.git',
|
'.git',
|
||||||
'.gitignore',
|
'.gitignore',
|
||||||
'README.md',
|
'README.md',
|
||||||
|
@ -34,7 +45,7 @@ async function main() {
|
||||||
|
|
||||||
const files = await readdir(pluginPath, { withFileTypes: true });
|
const files = await readdir(pluginPath, { withFileTypes: true });
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (excludeFromErase.indexOf(file.name) >= 0) {
|
if (isExcluded(file.name, excludeFromErase)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,13 +54,17 @@ async function main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Copy plugin template.
|
// Copy plugin template.
|
||||||
const { version: appVersion } = require(projectPath('package.json'));
|
const { version: appVersion } = require(projectPath('package.json'));
|
||||||
const templatePath = projectPath('local-moodleappbehat');
|
const templatePath = projectPath('local_moodleappbehat');
|
||||||
|
|
||||||
|
for await (const file of getDirectoryFiles(templatePath)) {
|
||||||
|
if (isExcluded(file, exclusions)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
copySync(templatePath, pluginPath);
|
copySync(file, file.replace(templatePath, pluginPath));
|
||||||
|
}
|
||||||
|
|
||||||
// Update version.php
|
// Update version.php
|
||||||
const pluginFilePath = pluginPath + '/version.php';
|
const pluginFilePath = pluginPath + '/version.php';
|
||||||
|
@ -62,28 +77,30 @@ async function main() {
|
||||||
writeFileSync(pluginFilePath, replaceArguments(fileContents, replacements));
|
writeFileSync(pluginFilePath, replaceArguments(fileContents, replacements));
|
||||||
|
|
||||||
// Copy feature files.
|
// Copy feature files.
|
||||||
const behatTempFeaturesPath = `${pluginPath}/behat-tmp`;
|
if (!excludeFeatures) {
|
||||||
copySync(projectPath('src'), behatTempFeaturesPath, { filter: isFeatureFileOrDirectory });
|
const behatTempFeaturesPath = `${pluginPath}/behat-tmp`;
|
||||||
|
copySync(projectPath('src'), behatTempFeaturesPath, { filter: isFeatureFileOrDirectory });
|
||||||
|
|
||||||
const behatFeaturesPath = `${pluginPath}/tests/behat`;
|
const behatFeaturesPath = `${pluginPath}/tests/behat`;
|
||||||
if (!existsSync(behatFeaturesPath)) {
|
if (!existsSync(behatFeaturesPath)) {
|
||||||
mkdirSync(behatFeaturesPath, {recursive: true});
|
mkdirSync(behatFeaturesPath, {recursive: true});
|
||||||
}
|
|
||||||
|
|
||||||
for await (const featureFile of getDirectoryFiles(behatTempFeaturesPath)) {
|
|
||||||
const featurePath = dirname(featureFile);
|
|
||||||
if (!featurePath.endsWith('/tests/behat')) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const newPath = featurePath.substring(0, featurePath.length - ('/tests/behat'.length));
|
for await (const featureFile of getDirectoryFiles(behatTempFeaturesPath)) {
|
||||||
const searchRegExp = new RegExp('/', 'g');
|
const featurePath = dirname(featureFile);
|
||||||
const prefix = relative(behatTempFeaturesPath, newPath).replace(searchRegExp,'-') || 'core';
|
if (!featurePath.endsWith('/tests/behat')) {
|
||||||
const featureFilename = prefix + '-' + basename(featureFile);
|
continue;
|
||||||
renameSync(featureFile, behatFeaturesPath + '/' + featureFilename);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
rmSync(behatTempFeaturesPath, {recursive: true});
|
const newPath = featurePath.substring(0, featurePath.length - ('/tests/behat'.length));
|
||||||
|
const searchRegExp = new RegExp('/', 'g');
|
||||||
|
const prefix = relative(behatTempFeaturesPath, newPath).replace(searchRegExp,'-') || 'core';
|
||||||
|
const featureFilename = prefix + '-' + basename(featureFile);
|
||||||
|
renameSync(featureFile, behatFeaturesPath + '/' + featureFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
rmSync(behatTempFeaturesPath, {recursive: true});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isFeatureFileOrDirectory(src) {
|
function isFeatureFileOrDirectory(src) {
|
||||||
|
@ -92,6 +109,10 @@ function isFeatureFileOrDirectory(src) {
|
||||||
return stats.isDirectory() || extname(src) === '.feature';
|
return stats.isDirectory() || extname(src) === '.feature';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isExcluded(file, exclusions) {
|
||||||
|
return exclusions.some(exclusion => minimatch(file, exclusion));
|
||||||
|
}
|
||||||
|
|
||||||
function fail(message) {
|
function fail(message) {
|
||||||
console.error(message);
|
console.error(message);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
source "scripts/functions.sh"
|
|
||||||
|
|
||||||
# Prepare variables
|
|
||||||
basedir="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../" && pwd )"
|
|
||||||
dockerscripts="$HOME/moodle-docker/bin/"
|
|
||||||
dockercompose="$dockerscripts/moodle-docker-compose"
|
|
||||||
|
|
||||||
export MOODLE_DOCKER_DB=pgsql
|
|
||||||
export MOODLE_DOCKER_BROWSER=chrome
|
|
||||||
export MOODLE_DOCKER_WWWROOT="$HOME/moodle"
|
|
||||||
export MOODLE_DOCKER_PHP_VERSION=7.4
|
|
||||||
export MOODLE_DOCKER_APP_PATH=$basedir
|
|
||||||
|
|
||||||
# Prepare dependencies
|
|
||||||
print_title "Preparing dependencies"
|
|
||||||
git clone --branch master --depth 1 git://github.com/moodle/moodle $HOME/moodle
|
|
||||||
git clone --branch ionic5 --depth 1 git://github.com/moodlehq/moodle-local_moodlemobileapp $HOME/moodle/local/moodlemobileapp
|
|
||||||
|
|
||||||
# TODO replace for moodlehq/moodle-docker after merging https://github.com/moodlehq/moodle-docker/pull/156
|
|
||||||
git clone --branch MOBILE-3738 --depth 1 git://github.com/NoelDeMartin/moodle-docker $HOME/moodle-docker
|
|
||||||
|
|
||||||
cp $HOME/moodle-docker/config.docker-template.php $HOME/moodle/config.php
|
|
||||||
|
|
||||||
# Build app
|
|
||||||
print_title "Building app"
|
|
||||||
npm ci
|
|
||||||
|
|
||||||
# Start containers
|
|
||||||
print_title "Starting containers"
|
|
||||||
$dockercompose pull
|
|
||||||
$dockercompose up -d
|
|
||||||
$dockerscripts/moodle-docker-wait-for-db
|
|
||||||
$dockerscripts/moodle-docker-wait-for-app
|
|
||||||
|
|
||||||
$dockercompose exec -T webserver sh -c "php admin/tool/behat/cli/init.php"
|
|
||||||
notify_on_error_exit "e2e failed initializing behat"
|
|
||||||
|
|
||||||
print_title "Running e2e tests"
|
|
||||||
|
|
||||||
# Run tests
|
|
||||||
for tags in "$@"
|
|
||||||
do
|
|
||||||
$dockercompose exec -T webserver sh -c "php admin/tool/behat/cli/run.php --tags=\"$tags\" --auto-rerun"
|
|
||||||
notify_on_error_exit "Some e2e tests are failing, please review"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
$dockercompose down
|
|
|
@ -45,8 +45,9 @@ Feature: Users can manage entries in database activities
|
||||||
|
|
||||||
Scenario: Browse entry
|
Scenario: Browse entry
|
||||||
Given I entered the data activity "Web links" on course "Course 1" as "student1" in the app
|
Given I entered the data activity "Web links" on course "Course 1" as "student1" in the app
|
||||||
|
|
||||||
# TODO Create and use a generator for database entries.
|
# TODO Create and use a generator for database entries.
|
||||||
And I press "Add entries" in the app
|
When I press "Add entries" in the app
|
||||||
And I set the following fields to these values in the app:
|
And I set the following fields to these values in the app:
|
||||||
| URL | https://moodle.org/ |
|
| URL | https://moodle.org/ |
|
||||||
| Description | Moodle community site |
|
| Description | Moodle community site |
|
||||||
|
@ -59,16 +60,19 @@ Feature: Users can manage entries in database activities
|
||||||
And I press "Save" near "Web links" in the app
|
And I press "Save" near "Web links" in the app
|
||||||
And I press "More" near "Moodle community site" in the app
|
And I press "More" near "Moodle community site" in the app
|
||||||
Then I should find "Moodle community site" in the app
|
Then I should find "Moodle community site" in the app
|
||||||
And I should not find "Next" in the app
|
And I should be able to press "Previous" in the app
|
||||||
And I should find "Previous" in the app
|
But I should not be able to press "Next" in the app
|
||||||
And I press "Previous" in the app
|
|
||||||
And I should find "Moodle Cloud" in the app
|
When I press "Previous" in the app
|
||||||
And I should find "Next" in the app
|
Then I should find "Moodle Cloud" in the app
|
||||||
And I should not find "Previous" in the app
|
And I should be able to press "Next" in the app
|
||||||
And I press "Next" in the app
|
But I should not be able to press "Previous" in the app
|
||||||
And I should find "Moodle community site" in the app
|
|
||||||
And I should not find "Moodle Cloud" in the app
|
When I press "Next" in the app
|
||||||
And I press the back button in the app
|
Then I should find "Moodle community site" in the app
|
||||||
|
But I should not find "Moodle Cloud" in the app
|
||||||
|
|
||||||
|
When I press the back button in the app
|
||||||
And I should find "Moodle community site" in the app
|
And I should find "Moodle community site" in the app
|
||||||
And I should find "Moodle Cloud" in the app
|
And I should find "Moodle Cloud" in the app
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { AfterViewInit, ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
|
import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core';
|
||||||
import { IonRouterOutlet } from '@ionic/angular';
|
import { IonRouterOutlet } from '@ionic/angular';
|
||||||
import { BackButtonEvent, ScrollDetail } from '@ionic/core';
|
import { BackButtonEvent, ScrollDetail } from '@ionic/core';
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||||
import { CoreEvents } from '@singletons/events';
|
import { CoreEvents } from '@singletons/events';
|
||||||
import { NgZone, SplashScreen, Translate } from '@singletons';
|
import { NgZone, SplashScreen, Translate } from '@singletons';
|
||||||
import { CoreNetwork } from '@services/network';
|
import { CoreNetwork } from '@services/network';
|
||||||
import { CoreApp, CoreAppProvider } from '@services/app';
|
import { CoreApp } from '@services/app';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreSubscriptions } from '@singletons/subscriptions';
|
import { CoreSubscriptions } from '@singletons/subscriptions';
|
||||||
|
@ -38,10 +38,6 @@ import { CorePlatform } from '@services/platform';
|
||||||
const MOODLE_VERSION_PREFIX = 'version-';
|
const MOODLE_VERSION_PREFIX = 'version-';
|
||||||
const MOODLEAPP_VERSION_PREFIX = 'moodleapp-';
|
const MOODLEAPP_VERSION_PREFIX = 'moodleapp-';
|
||||||
|
|
||||||
type AutomatedTestsWindow = Window & {
|
|
||||||
changeDetector?: ChangeDetectorRef;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: 'app.component.html',
|
templateUrl: 'app.component.html',
|
||||||
|
@ -54,12 +50,6 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||||
protected lastUrls: Record<string, number> = {};
|
protected lastUrls: Record<string, number> = {};
|
||||||
protected lastInAppUrl?: string;
|
protected lastInAppUrl?: string;
|
||||||
|
|
||||||
constructor(changeDetector: ChangeDetectorRef) {
|
|
||||||
if (CoreAppProvider.isAutomated()) {
|
|
||||||
(window as AutomatedTestsWindow).changeDetector = changeDetector;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component being initialized.
|
* Component being initialized.
|
||||||
*
|
*
|
||||||
|
|
|
@ -32,7 +32,7 @@ import { JitCompilerFactory } from '@angular/platform-browser-dynamic';
|
||||||
import { CoreCronDelegate } from '@services/cron';
|
import { CoreCronDelegate } from '@services/cron';
|
||||||
import { CoreSiteInfoCronHandler } from '@services/handlers/site-info-cron';
|
import { CoreSiteInfoCronHandler } from '@services/handlers/site-info-cron';
|
||||||
import { moodleTransitionAnimation } from '@classes/page-transition';
|
import { moodleTransitionAnimation } from '@classes/page-transition';
|
||||||
import { BehatTestingModule } from '@/testing/behat-testing.module';
|
import { TestingModule } from '@/testing/testing.module';
|
||||||
|
|
||||||
// For translate loader. AoT requires an exported function for factories.
|
// For translate loader. AoT requires an exported function for factories.
|
||||||
export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
|
export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
|
||||||
|
@ -60,7 +60,7 @@ export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
CoreModule,
|
CoreModule,
|
||||||
AddonsModule,
|
AddonsModule,
|
||||||
BehatTestingModule,
|
TestingModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
|
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
|
||||||
|
|
|
@ -220,22 +220,9 @@ Feature: Test basic usage of comments in app
|
||||||
|
|
||||||
Scenario: Add comments & Delete comments (blogs)
|
Scenario: Add comments & Delete comments (blogs)
|
||||||
# Create blog as a teacher
|
# Create blog as a teacher
|
||||||
Given the following "blocks" exist:
|
Given the following "core_blog > entries" exist:
|
||||||
| blockname | contextlevel | reference | pagetypepattern | defaultregion | configdata |
|
| subject | body | user |
|
||||||
| blog_menu | Course | C1 | course-view-* | site-pre | |
|
| Blog test | Blog body | teacher1 |
|
||||||
And I entered the course "Course 1" as "teacher1" in the app
|
|
||||||
And I press "Course summary" in the app
|
|
||||||
# TODO Create and use a generator blog entries.
|
|
||||||
And I press "Open in browser" in the app
|
|
||||||
And I switch to the browser tab opened by the app
|
|
||||||
And I log in as "teacher1"
|
|
||||||
And I click on "Open block drawer" "button"
|
|
||||||
And I click on "Add an entry about this course" "link" in the "Blog menu" "block"
|
|
||||||
And I set the following fields to these values:
|
|
||||||
| Entry title | Blog test |
|
|
||||||
| Blog entry body | Blog body |
|
|
||||||
And I press "Save changes"
|
|
||||||
And I close the browser tab opened by the app
|
|
||||||
|
|
||||||
# Create and delete comments as a student
|
# Create and delete comments as a student
|
||||||
When I entered the app as "student1"
|
When I entered the app as "student1"
|
||||||
|
@ -263,21 +250,9 @@ Feature: Test basic usage of comments in app
|
||||||
|
|
||||||
Scenario: Add comments offline & Delete comments offline & Sync comments (blogs)
|
Scenario: Add comments offline & Delete comments offline & Sync comments (blogs)
|
||||||
# Create blog as a teacher
|
# Create blog as a teacher
|
||||||
Given the following "blocks" exist:
|
Given the following "core_blog > entries" exist:
|
||||||
| blockname | contextlevel | reference | pagetypepattern | defaultregion | configdata |
|
| subject | body | user |
|
||||||
| blog_menu | Course | C1 | course-view-* | site-pre | |
|
| Blog test | Blog body | teacher1 |
|
||||||
And I entered the course "Course 1" as "teacher1" in the app
|
|
||||||
And I press "Course summary" in the app
|
|
||||||
And I press "Open in browser" in the app
|
|
||||||
And I switch to the browser tab opened by the app
|
|
||||||
And I log in as "teacher1"
|
|
||||||
And I click on "Open block drawer" "button"
|
|
||||||
And I click on "Add an entry about this course" "link" in the "Blog menu" "block"
|
|
||||||
And I set the following fields to these values:
|
|
||||||
| Entry title | Blog test |
|
|
||||||
| Blog entry body | Blog body |
|
|
||||||
And I press "Save changes"
|
|
||||||
And I close the browser tab opened by the app
|
|
||||||
|
|
||||||
# Create and delete comments as a student
|
# Create and delete comments as a student
|
||||||
When I entered the app as "student1"
|
When I entered the app as "student1"
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
|
||||||
import { CoreAppProvider } from '@services/app';
|
|
||||||
import { TestsBehatBlockingService } from './services/behat-blocking';
|
|
||||||
import { BehatTestsWindow, TestsBehatRuntime } from './services/behat-runtime';
|
|
||||||
|
|
||||||
function initializeBehatTestsWindow(window: BehatTestsWindow) {
|
|
||||||
// Make functions publicly available for Behat to call.
|
|
||||||
window.behatInit = TestsBehatRuntime.init;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
providers:
|
|
||||||
CoreAppProvider.isAutomated()
|
|
||||||
? [
|
|
||||||
{ provide: APP_INITIALIZER, multi: true, useValue: () => initializeBehatTestsWindow(window) },
|
|
||||||
TestsBehatBlockingService,
|
|
||||||
]
|
|
||||||
: [],
|
|
||||||
})
|
|
||||||
export class BehatTestingModule {}
|
|
|
@ -15,13 +15,13 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { makeSingleton, NgZone } from '@singletons';
|
import { makeSingleton, NgZone } from '@singletons';
|
||||||
import { BehatTestsWindow, TestsBehatRuntime } from './behat-runtime';
|
import { BehatTestsWindow, TestingBehatRuntime } from './behat-runtime';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Behat block JS manager.
|
* Behat block JS manager.
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class TestsBehatBlockingService {
|
export class TestingBehatBlockingService {
|
||||||
|
|
||||||
protected waitingBlocked = false;
|
protected waitingBlocked = false;
|
||||||
protected recentMutation = false;
|
protected recentMutation = false;
|
||||||
|
@ -48,7 +48,7 @@ export class TestsBehatBlockingService {
|
||||||
win.M.util = win.M.util ?? {};
|
win.M.util = win.M.util ?? {};
|
||||||
win.M.util.pending_js = win.M.util.pending_js ?? [];
|
win.M.util.pending_js = win.M.util.pending_js ?? [];
|
||||||
|
|
||||||
TestsBehatRuntime.log('Initialized!');
|
TestingBehatRuntime.log('Initialized!');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -90,7 +90,7 @@ export class TestsBehatBlockingService {
|
||||||
}
|
}
|
||||||
this.pendingList.push(key);
|
this.pendingList.push(key);
|
||||||
|
|
||||||
TestsBehatRuntime.log('PENDING+: ' + this.pendingList);
|
TestingBehatRuntime.log('PENDING+: ' + this.pendingList);
|
||||||
|
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@ export class TestsBehatBlockingService {
|
||||||
// Remove the key immediately.
|
// Remove the key immediately.
|
||||||
this.pendingList = this.pendingList.filter((x) => x !== key);
|
this.pendingList = this.pendingList.filter((x) => x !== key);
|
||||||
|
|
||||||
TestsBehatRuntime.log('PENDING-: ' + this.pendingList);
|
TestingBehatRuntime.log('PENDING-: ' + this.pendingList);
|
||||||
|
|
||||||
// If the only thing left is DELAY, then remove that as well, later...
|
// If the only thing left is DELAY, then remove that as well, later...
|
||||||
if (this.pendingList.length === 1) {
|
if (this.pendingList.length === 1) {
|
||||||
|
@ -124,7 +124,7 @@ export class TestsBehatBlockingService {
|
||||||
// Only remove it if the pending array is STILL empty after all that.
|
// Only remove it if the pending array is STILL empty after all that.
|
||||||
if (this.pendingList.length === 1) {
|
if (this.pendingList.length === 1) {
|
||||||
this.pendingList = [];
|
this.pendingList = [];
|
||||||
TestsBehatRuntime.log('PENDING-: ' + this.pendingList);
|
TestingBehatRuntime.log('PENDING-: ' + this.pendingList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,16 +221,16 @@ export class TestsBehatBlockingService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Add to the list of pending requests.
|
// Add to the list of pending requests.
|
||||||
TestsBehatBlocking.block(key);
|
TestingBehatBlocking.block(key);
|
||||||
|
|
||||||
// Detect when it finishes and remove it from the list.
|
// Detect when it finishes and remove it from the list.
|
||||||
this.addEventListener('loadend', () => {
|
this.addEventListener('loadend', () => {
|
||||||
TestsBehatBlocking.unblock(key);
|
TestingBehatBlocking.unblock(key);
|
||||||
});
|
});
|
||||||
|
|
||||||
return realOpen.apply(this, args);
|
return realOpen.apply(this, args);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
TestsBehatBlocking.unblock(key);
|
TestingBehatBlocking.unblock(key);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -239,4 +239,4 @@ export class TestsBehatBlockingService {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TestsBehatBlocking = makeSingleton(TestsBehatBlockingService);
|
export const TestingBehatBlocking = makeSingleton(TestingBehatBlockingService);
|
||||||
|
|
|
@ -12,10 +12,11 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
import { CorePromisedValue } from '@classes/promised-value';
|
import { CorePromisedValue } from '@classes/promised-value';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { NgZone } from '@singletons';
|
import { makeSingleton, NgZone } from '@singletons';
|
||||||
import { TestBehatElementLocator } from './behat-runtime';
|
import { TestingBehatElementLocator, TestingBehatFindOptions } from './behat-runtime';
|
||||||
|
|
||||||
// Containers that block containers behind them.
|
// Containers that block containers behind them.
|
||||||
const blockingContainers = ['ION-ALERT', 'ION-POPOVER', 'ION-ACTION-SHEET', 'CORE-USER-TOURS-USER-TOUR', 'ION-PAGE'];
|
const blockingContainers = ['ION-ALERT', 'ION-POPOVER', 'ION-ACTION-SHEET', 'CORE-USER-TOURS-USER-TOUR', 'ION-PAGE'];
|
||||||
|
@ -23,7 +24,8 @@ const blockingContainers = ['ION-ALERT', 'ION-POPOVER', 'ION-ACTION-SHEET', 'COR
|
||||||
/**
|
/**
|
||||||
* Behat Dom Utils helper functions.
|
* Behat Dom Utils helper functions.
|
||||||
*/
|
*/
|
||||||
export class TestsBehatDomUtils {
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class TestingBehatDomUtilsService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if an element is visible.
|
* Check if an element is visible.
|
||||||
|
@ -32,7 +34,7 @@ export class TestsBehatDomUtils {
|
||||||
* @param container Container.
|
* @param container Container.
|
||||||
* @return Whether the element is visible or not.
|
* @return Whether the element is visible or not.
|
||||||
*/
|
*/
|
||||||
static isElementVisible(element: HTMLElement, container: HTMLElement): boolean {
|
isElementVisible(element: HTMLElement, container: HTMLElement): boolean {
|
||||||
if (element.getAttribute('aria-hidden') === 'true' || getComputedStyle(element).display === 'none') {
|
if (element.getAttribute('aria-hidden') === 'true' || getComputedStyle(element).display === 'none') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -56,7 +58,7 @@ export class TestsBehatDomUtils {
|
||||||
* @param container Container.
|
* @param container Container.
|
||||||
* @return Whether the element is selected or not.
|
* @return Whether the element is selected or not.
|
||||||
*/
|
*/
|
||||||
static isElementSelected(element: HTMLElement, container: HTMLElement): boolean {
|
isElementSelected(element: HTMLElement, container: HTMLElement): boolean {
|
||||||
const ariaCurrent = element.getAttribute('aria-current');
|
const ariaCurrent = element.getAttribute('aria-current');
|
||||||
if (
|
if (
|
||||||
(ariaCurrent && ariaCurrent !== 'false') ||
|
(ariaCurrent && ariaCurrent !== 'false') ||
|
||||||
|
@ -79,9 +81,14 @@ export class TestsBehatDomUtils {
|
||||||
*
|
*
|
||||||
* @param container Parent element to search the element within
|
* @param container Parent element to search the element within
|
||||||
* @param text Text to look for
|
* @param text Text to look for
|
||||||
|
* @param options Search options.
|
||||||
* @return Elements containing the given text with exact boolean.
|
* @return Elements containing the given text with exact boolean.
|
||||||
*/
|
*/
|
||||||
protected static findElementsBasedOnTextWithinWithExact(container: HTMLElement, text: string): ElementsWithExact[] {
|
protected findElementsBasedOnTextWithinWithExact(
|
||||||
|
container: HTMLElement,
|
||||||
|
text: string,
|
||||||
|
options: TestingBehatFindOptions,
|
||||||
|
): ElementsWithExact[] {
|
||||||
const attributesSelector = `[aria-label*="${text}"], a[title*="${text}"], img[alt*="${text}"], [placeholder*="${text}"]`;
|
const attributesSelector = `[aria-label*="${text}"], a[title*="${text}"], img[alt*="${text}"], [placeholder*="${text}"]`;
|
||||||
|
|
||||||
const elements = Array.from(container.querySelectorAll<HTMLElement>(attributesSelector))
|
const elements = Array.from(container.querySelectorAll<HTMLElement>(attributesSelector))
|
||||||
|
@ -97,16 +104,23 @@ export class TestsBehatDomUtils {
|
||||||
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_DOCUMENT_FRAGMENT | NodeFilter.SHOW_TEXT, // eslint-disable-line no-bitwise
|
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_DOCUMENT_FRAGMENT | NodeFilter.SHOW_TEXT, // eslint-disable-line no-bitwise
|
||||||
{
|
{
|
||||||
acceptNode: node => {
|
acceptNode: node => {
|
||||||
if (node instanceof HTMLStyleElement ||
|
if (
|
||||||
|
node instanceof HTMLStyleElement ||
|
||||||
node instanceof HTMLLinkElement ||
|
node instanceof HTMLLinkElement ||
|
||||||
node instanceof HTMLScriptElement) {
|
node instanceof HTMLScriptElement
|
||||||
|
) {
|
||||||
return NodeFilter.FILTER_REJECT;
|
return NodeFilter.FILTER_REJECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node instanceof HTMLElement &&
|
if (!(node instanceof HTMLElement)) {
|
||||||
(node.getAttribute('aria-hidden') === 'true' ||
|
return NodeFilter.FILTER_ACCEPT;
|
||||||
node.getAttribute('aria-disabled') === 'true' ||
|
}
|
||||||
getComputedStyle(node).display === 'none')) {
|
|
||||||
|
if (options.onlyClickable && (node.getAttribute('aria-disabled') === 'true' || node.hasAttribute('disabled'))) {
|
||||||
|
return NodeFilter.FILTER_REJECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.getAttribute('aria-hidden') === 'true' || getComputedStyle(node).display === 'none') {
|
||||||
return NodeFilter.FILTER_REJECT;
|
return NodeFilter.FILTER_REJECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +174,7 @@ export class TestsBehatDomUtils {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
elements.push(...this.findElementsBasedOnTextWithinWithExact(childNode, text));
|
elements.push(...this.findElementsBasedOnTextWithinWithExact(childNode, text, options));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,7 +189,7 @@ export class TestsBehatDomUtils {
|
||||||
* @param text Text to check.
|
* @param text Text to check.
|
||||||
* @return If text matches any of the label attributes.
|
* @return If text matches any of the label attributes.
|
||||||
*/
|
*/
|
||||||
protected static checkElementLabel(element: HTMLElement, text: string): boolean {
|
protected checkElementLabel(element: HTMLElement, text: string): boolean {
|
||||||
return element.title === text ||
|
return element.title === text ||
|
||||||
element.getAttribute('alt') === text ||
|
element.getAttribute('alt') === text ||
|
||||||
element.getAttribute('aria-label') === text ||
|
element.getAttribute('aria-label') === text ||
|
||||||
|
@ -187,10 +201,15 @@ export class TestsBehatDomUtils {
|
||||||
*
|
*
|
||||||
* @param container Parent element to search the element within.
|
* @param container Parent element to search the element within.
|
||||||
* @param text Text to look for.
|
* @param text Text to look for.
|
||||||
|
* @param options Search options.
|
||||||
* @return Elements containing the given text.
|
* @return Elements containing the given text.
|
||||||
*/
|
*/
|
||||||
protected static findElementsBasedOnTextWithin(container: HTMLElement, text: string): HTMLElement[] {
|
protected findElementsBasedOnTextWithin(
|
||||||
const elements = this.findElementsBasedOnTextWithinWithExact(container, text);
|
container: HTMLElement,
|
||||||
|
text: string,
|
||||||
|
options: TestingBehatFindOptions,
|
||||||
|
): HTMLElement[] {
|
||||||
|
const elements = this.findElementsBasedOnTextWithinWithExact(container, text, options);
|
||||||
|
|
||||||
// Give more relevance to exact matches.
|
// Give more relevance to exact matches.
|
||||||
elements.sort((a, b) => Number(b.exact) - Number(a.exact));
|
elements.sort((a, b) => Number(b.exact) - Number(a.exact));
|
||||||
|
@ -206,7 +225,7 @@ export class TestsBehatDomUtils {
|
||||||
* @param elements Elements list.
|
* @param elements Elements list.
|
||||||
* @return Top ancestors.
|
* @return Top ancestors.
|
||||||
*/
|
*/
|
||||||
protected static getTopAncestors(elements: HTMLElement[]): HTMLElement[] {
|
protected getTopAncestors(elements: HTMLElement[]): HTMLElement[] {
|
||||||
const uniqueElements = new Set(elements);
|
const uniqueElements = new Set(elements);
|
||||||
|
|
||||||
for (const element of uniqueElements) {
|
for (const element of uniqueElements) {
|
||||||
|
@ -230,7 +249,7 @@ export class TestsBehatDomUtils {
|
||||||
* @param element Element.
|
* @param element Element.
|
||||||
* @return Parent element.
|
* @return Parent element.
|
||||||
*/
|
*/
|
||||||
protected static getParentElement(element: HTMLElement): HTMLElement | null {
|
protected getParentElement(element: HTMLElement): HTMLElement | null {
|
||||||
return element.parentElement ||
|
return element.parentElement ||
|
||||||
(element.getRootNode() && (element.getRootNode() as ShadowRoot).host as HTMLElement) ||
|
(element.getRootNode() && (element.getRootNode() as ShadowRoot).host as HTMLElement) ||
|
||||||
null;
|
null;
|
||||||
|
@ -244,7 +263,7 @@ export class TestsBehatDomUtils {
|
||||||
* @param container Topmost container to search within.
|
* @param container Topmost container to search within.
|
||||||
* @return Closest matching element.
|
* @return Closest matching element.
|
||||||
*/
|
*/
|
||||||
protected static getClosestMatching(element: HTMLElement, selector: string, container: HTMLElement | null): HTMLElement | null {
|
protected getClosestMatching(element: HTMLElement, selector: string, container: HTMLElement | null): HTMLElement | null {
|
||||||
if (element.matches(selector)) {
|
if (element.matches(selector)) {
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
@ -262,7 +281,7 @@ export class TestsBehatDomUtils {
|
||||||
* @param containerName Whether to search inside the a container name.
|
* @param containerName Whether to search inside the a container name.
|
||||||
* @return Found top container elements.
|
* @return Found top container elements.
|
||||||
*/
|
*/
|
||||||
protected static getCurrentTopContainerElements(containerName: string): HTMLElement[] {
|
protected getCurrentTopContainerElements(containerName: string): HTMLElement[] {
|
||||||
const topContainers: HTMLElement[] = [];
|
const topContainers: HTMLElement[] = [];
|
||||||
let containers = Array.from(document.querySelectorAll<HTMLElement>([
|
let containers = Array.from(document.querySelectorAll<HTMLElement>([
|
||||||
'ion-alert.hydrated',
|
'ion-alert.hydrated',
|
||||||
|
@ -325,32 +344,33 @@ export class TestsBehatDomUtils {
|
||||||
* Function to find element based on their text or Aria label.
|
* Function to find element based on their text or Aria label.
|
||||||
*
|
*
|
||||||
* @param locator Element locator.
|
* @param locator Element locator.
|
||||||
* @param containerName Whether to search only inside a specific container.
|
* @param options Search options.
|
||||||
* @return First found element.
|
* @return First found element.
|
||||||
*/
|
*/
|
||||||
static findElementBasedOnText(locator: TestBehatElementLocator, containerName = ''): HTMLElement {
|
findElementBasedOnText(
|
||||||
return this.findElementsBasedOnText(locator, containerName, true)[0];
|
locator: TestingBehatElementLocator,
|
||||||
|
options: TestingBehatFindOptions,
|
||||||
|
): HTMLElement {
|
||||||
|
return this.findElementsBasedOnText(locator, options)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to find elements based on their text or Aria label.
|
* Function to find elements based on their text or Aria label.
|
||||||
*
|
*
|
||||||
* @param locator Element locator.
|
* @param locator Element locator.
|
||||||
* @param containerName Whether to search only inside a specific container.
|
* @param options Search options.
|
||||||
* @param stopWhenFound Stop looking in containers once an element is found.
|
|
||||||
* @return Found elements
|
* @return Found elements
|
||||||
*/
|
*/
|
||||||
protected static findElementsBasedOnText(
|
protected findElementsBasedOnText(
|
||||||
locator: TestBehatElementLocator,
|
locator: TestingBehatElementLocator,
|
||||||
containerName = '',
|
options: TestingBehatFindOptions,
|
||||||
stopWhenFound = false,
|
|
||||||
): HTMLElement[] {
|
): HTMLElement[] {
|
||||||
const topContainers = this.getCurrentTopContainerElements(containerName);
|
const topContainers = this.getCurrentTopContainerElements(options.containerName);
|
||||||
let elements: HTMLElement[] = [];
|
let elements: HTMLElement[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < topContainers.length; i++) {
|
for (let i = 0; i < topContainers.length; i++) {
|
||||||
elements = elements.concat(this.findElementsBasedOnTextInContainer(locator, topContainers[i]));
|
elements = elements.concat(this.findElementsBasedOnTextInContainer(locator, topContainers[i], options));
|
||||||
if (stopWhenFound && elements.length) {
|
if (elements.length) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -363,16 +383,18 @@ export class TestsBehatDomUtils {
|
||||||
*
|
*
|
||||||
* @param locator Element locator.
|
* @param locator Element locator.
|
||||||
* @param topContainer Container to search in.
|
* @param topContainer Container to search in.
|
||||||
|
* @param options Search options.
|
||||||
* @return Found elements
|
* @return Found elements
|
||||||
*/
|
*/
|
||||||
protected static findElementsBasedOnTextInContainer(
|
protected findElementsBasedOnTextInContainer(
|
||||||
locator: TestBehatElementLocator,
|
locator: TestingBehatElementLocator,
|
||||||
topContainer: HTMLElement,
|
topContainer: HTMLElement,
|
||||||
|
options: TestingBehatFindOptions,
|
||||||
): HTMLElement[] {
|
): HTMLElement[] {
|
||||||
let container: HTMLElement | null = topContainer;
|
let container: HTMLElement | null = topContainer;
|
||||||
|
|
||||||
if (locator.within) {
|
if (locator.within) {
|
||||||
const withinElements = this.findElementsBasedOnTextInContainer(locator.within, topContainer);
|
const withinElements = this.findElementsBasedOnTextInContainer(locator.within, topContainer, options);
|
||||||
|
|
||||||
if (withinElements.length === 0) {
|
if (withinElements.length === 0) {
|
||||||
throw new Error('There was no match for within text');
|
throw new Error('There was no match for within text');
|
||||||
|
@ -390,7 +412,10 @@ export class TestsBehatDomUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topContainer && locator.near) {
|
if (topContainer && locator.near) {
|
||||||
const nearElements = this.findElementsBasedOnTextInContainer(locator.near, topContainer);
|
const nearElements = this.findElementsBasedOnTextInContainer(locator.near, topContainer, {
|
||||||
|
...options,
|
||||||
|
onlyClickable: false,
|
||||||
|
});
|
||||||
|
|
||||||
if (nearElements.length === 0) {
|
if (nearElements.length === 0) {
|
||||||
throw new Error('There was no match for near text');
|
throw new Error('There was no match for near text');
|
||||||
|
@ -412,7 +437,7 @@ export class TestsBehatDomUtils {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const elements = this.findElementsBasedOnTextWithin(container, locator.text);
|
const elements = this.findElementsBasedOnTextWithin(container, locator.text, options);
|
||||||
|
|
||||||
let filteredElements: HTMLElement[] = elements;
|
let filteredElements: HTMLElement[] = elements;
|
||||||
|
|
||||||
|
@ -442,7 +467,7 @@ export class TestsBehatDomUtils {
|
||||||
*
|
*
|
||||||
* @param element Element.
|
* @param element Element.
|
||||||
*/
|
*/
|
||||||
protected static async ensureElementVisible(element: HTMLElement): Promise<DOMRect> {
|
protected async ensureElementVisible(element: HTMLElement): Promise<DOMRect> {
|
||||||
const initialRect = element.getBoundingClientRect();
|
const initialRect = element.getBoundingClientRect();
|
||||||
|
|
||||||
element.scrollIntoView(false);
|
element.scrollIntoView(false);
|
||||||
|
@ -471,7 +496,7 @@ export class TestsBehatDomUtils {
|
||||||
*
|
*
|
||||||
* @param element Element to press.
|
* @param element Element to press.
|
||||||
*/
|
*/
|
||||||
static async pressElement(element: HTMLElement): Promise<void> {
|
async pressElement(element: HTMLElement): Promise<void> {
|
||||||
await NgZone.run(async () => {
|
await NgZone.run(async () => {
|
||||||
const promise = new CorePromisedValue<void>();
|
const promise = new CorePromisedValue<void>();
|
||||||
|
|
||||||
|
@ -516,7 +541,7 @@ export class TestsBehatDomUtils {
|
||||||
* @param element HTML to set.
|
* @param element HTML to set.
|
||||||
* @param value Value to be set.
|
* @param value Value to be set.
|
||||||
*/
|
*/
|
||||||
static async setElementValue(element: HTMLInputElement | HTMLElement, value: string): Promise<void> {
|
async setElementValue(element: HTMLInputElement | HTMLElement, value: string): Promise<void> {
|
||||||
await NgZone.run(async () => {
|
await NgZone.run(async () => {
|
||||||
const promise = new CorePromisedValue<void>();
|
const promise = new CorePromisedValue<void>();
|
||||||
|
|
||||||
|
@ -581,6 +606,8 @@ export class TestsBehatDomUtils {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TestingBehatDomUtils = makeSingleton(TestingBehatDomUtilsService);
|
||||||
|
|
||||||
type ElementsWithExact = {
|
type ElementsWithExact = {
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
exact: boolean;
|
exact: boolean;
|
||||||
|
|
|
@ -12,127 +12,106 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { TestsBehatDomUtils } from './behat-dom';
|
import { TestingBehatDomUtils } from './behat-dom';
|
||||||
import { TestsBehatBlocking } from './behat-blocking';
|
import { TestingBehatBlocking } from './behat-blocking';
|
||||||
import { CoreCustomURLSchemes } from '@services/urlschemes';
|
import { CoreCustomURLSchemes, CoreCustomURLSchemesProvider } from '@services/urlschemes';
|
||||||
import { CoreLoginHelperProvider } from '@features/login/services/login-helper';
|
import { CoreLoginHelperProvider } from '@features/login/services/login-helper';
|
||||||
import { CoreConfig } from '@services/config';
|
import { CoreConfig } from '@services/config';
|
||||||
import { EnvironmentConfig } from '@/types/config';
|
import { EnvironmentConfig } from '@/types/config';
|
||||||
import { NgZone } from '@singletons';
|
import { makeSingleton, NgZone } from '@singletons';
|
||||||
import { CoreNetwork } from '@services/network';
|
import { CoreNetwork, CoreNetworkService } from '@services/network';
|
||||||
import {
|
import { CorePushNotifications, CorePushNotificationsProvider } from '@features/pushnotifications/services/pushnotifications';
|
||||||
CorePushNotifications,
|
import { CoreCronDelegate, CoreCronDelegateService } from '@services/cron';
|
||||||
CorePushNotificationsNotificationBasicData,
|
|
||||||
} from '@features/pushnotifications/services/pushnotifications';
|
|
||||||
import { CoreCronDelegate } from '@services/cron';
|
|
||||||
import { CoreLoadingComponent } from '@components/loading/loading';
|
import { CoreLoadingComponent } from '@components/loading/loading';
|
||||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||||
import { CoreDom } from '@singletons/dom';
|
import { CoreDom } from '@singletons/dom';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
import { CoreCoursesDashboardPage } from '@features/courses/pages/dashboard/dashboard';
|
import { CoreCoursesDashboardPage } from '@features/courses/pages/dashboard/dashboard';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Behat runtime servive with public API.
|
* Behat runtime servive with public API.
|
||||||
*/
|
*/
|
||||||
export class TestsBehatRuntime {
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class TestingBehatRuntimeService {
|
||||||
|
|
||||||
|
protected initialized = false;
|
||||||
|
|
||||||
|
get cronDelegate(): CoreCronDelegateService {
|
||||||
|
return CoreCronDelegate.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
get customUrlSchemes(): CoreCustomURLSchemesProvider {
|
||||||
|
return CoreCustomURLSchemes.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
get network(): CoreNetworkService {
|
||||||
|
return CoreNetwork.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
get pushNotifications(): CorePushNotificationsProvider {
|
||||||
|
return CorePushNotifications.instance;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init behat functions and set options like skipping onboarding.
|
* Init behat functions and set options like skipping onboarding.
|
||||||
*
|
*
|
||||||
* @param options Options to set on the app.
|
* @param options Options to set on the app.
|
||||||
*/
|
*/
|
||||||
static init(options?: TestsBehatInitOptions): void {
|
init(options: TestingBehatInitOptions = {}): void {
|
||||||
TestsBehatBlocking.init();
|
if (this.initialized) {
|
||||||
|
|
||||||
(window as BehatTestsWindow).behat = {
|
|
||||||
closePopup: TestsBehatRuntime.closePopup,
|
|
||||||
find: TestsBehatRuntime.find,
|
|
||||||
getAngularInstance: TestsBehatRuntime.getAngularInstance,
|
|
||||||
getHeader: TestsBehatRuntime.getHeader,
|
|
||||||
isSelected: TestsBehatRuntime.isSelected,
|
|
||||||
loadMoreItems: TestsBehatRuntime.loadMoreItems,
|
|
||||||
log: TestsBehatRuntime.log,
|
|
||||||
press: TestsBehatRuntime.press,
|
|
||||||
pressStandard: TestsBehatRuntime.pressStandard,
|
|
||||||
pullToRefresh: TestsBehatRuntime.pullToRefresh,
|
|
||||||
scrollTo: TestsBehatRuntime.scrollTo,
|
|
||||||
setField: TestsBehatRuntime.setField,
|
|
||||||
handleCustomURL: TestsBehatRuntime.handleCustomURL,
|
|
||||||
notificationClicked: TestsBehatRuntime.notificationClicked,
|
|
||||||
forceSyncExecution: TestsBehatRuntime.forceSyncExecution,
|
|
||||||
waitLoadingToFinish: TestsBehatRuntime.waitLoadingToFinish,
|
|
||||||
network: CoreNetwork.instance,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!options) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.skipOnBoarding === true) {
|
this.initialized = true;
|
||||||
|
TestingBehatBlocking.init();
|
||||||
|
|
||||||
|
if (options.skipOnBoarding) {
|
||||||
CoreConfig.set(CoreLoginHelperProvider.ONBOARDING_DONE, 1);
|
CoreConfig.set(CoreLoginHelperProvider.ONBOARDING_DONE, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.configOverrides) {
|
if (options.configOverrides) {
|
||||||
// Set the cookie so it's maintained between reloads.
|
// Set the cookie so it's maintained between reloads.
|
||||||
document.cookie = 'MoodleAppConfig=' + JSON.stringify(options.configOverrides);
|
document.cookie = 'MoodleAppConfig=' + JSON.stringify(options.configOverrides);
|
||||||
CoreConfig.patchEnvironment(options.configOverrides);
|
CoreConfig.patchEnvironment(options.configOverrides, { patchDefault: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a custom URL.
|
* Check whether the service has been initialized or not.
|
||||||
*
|
*
|
||||||
* @param url Url to open.
|
* @returns Whether the service has been initialized or not.
|
||||||
|
*/
|
||||||
|
hasInitialized(): boolean {
|
||||||
|
return this.initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run an operation inside the angular zone and return result.
|
||||||
|
*
|
||||||
|
* @param operation Operation callback.
|
||||||
* @return OK if successful, or ERROR: followed by message.
|
* @return OK if successful, or ERROR: followed by message.
|
||||||
*/
|
*/
|
||||||
static async handleCustomURL(url: string): Promise<string> {
|
async runInZone(operation: () => unknown, blocking: boolean = false): Promise<string> {
|
||||||
|
const blockKey = blocking && TestingBehatBlocking.block();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await NgZone.run(async () => {
|
await NgZone.run(operation);
|
||||||
await CoreCustomURLSchemes.handleCustomURL(url);
|
|
||||||
});
|
|
||||||
|
|
||||||
return 'OK';
|
return 'OK';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return 'ERROR: ' + error.message;
|
return 'ERROR: ' + error.message;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function called when a push notification is clicked. Redirect the user to the right state.
|
|
||||||
*
|
|
||||||
* @param data Notification data.
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
|
||||||
static async notificationClicked(data: CorePushNotificationsNotificationBasicData): Promise<void> {
|
|
||||||
const blockKey = TestsBehatBlocking.block();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await NgZone.run(async () => {
|
|
||||||
await CorePushNotifications.notificationClicked(data);
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
TestsBehatBlocking.unblock(blockKey);
|
blockKey && TestingBehatBlocking.unblock(blockKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Force execution of synchronization cron tasks without waiting for the scheduled time.
|
|
||||||
* Please notice that some tasks may not be executed depending on the network connection and sync settings.
|
|
||||||
*
|
|
||||||
* @return Promise resolved if all handlers are executed successfully, rejected otherwise.
|
|
||||||
*/
|
|
||||||
static async forceSyncExecution(): Promise<void> {
|
|
||||||
await NgZone.run(async () => {
|
|
||||||
await CoreCronDelegate.forceSyncExecution();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait all controlled components to be rendered.
|
* Wait all controlled components to be rendered.
|
||||||
*
|
*
|
||||||
* @return Promise resolved when all components have been rendered.
|
* @return Promise resolved when all components have been rendered.
|
||||||
*/
|
*/
|
||||||
static async waitLoadingToFinish(): Promise<void> {
|
async waitLoadingToFinish(): Promise<void> {
|
||||||
await NgZone.run(async () => {
|
await NgZone.run(async () => {
|
||||||
const elements = Array.from(document.body.querySelectorAll<HTMLElement>('core-loading'))
|
const elements = Array.from(document.body.querySelectorAll<HTMLElement>('core-loading'))
|
||||||
.filter((element) => CoreDom.isElementVisible(element));
|
.filter((element) => CoreDom.isElementVisible(element));
|
||||||
|
@ -148,28 +127,32 @@ export class TestsBehatRuntime {
|
||||||
* @param button Type of button to press.
|
* @param button Type of button to press.
|
||||||
* @return OK if successful, or ERROR: followed by message.
|
* @return OK if successful, or ERROR: followed by message.
|
||||||
*/
|
*/
|
||||||
static async pressStandard(button: string): Promise<string> {
|
async pressStandard(button: string): Promise<string> {
|
||||||
this.log('Action - Click standard button: ' + button);
|
this.log('Action - Click standard button: ' + button);
|
||||||
|
|
||||||
// Find button
|
// Find button
|
||||||
let foundButton: HTMLElement | undefined;
|
let foundButton: HTMLElement | undefined;
|
||||||
|
const options: TestingBehatFindOptions = {
|
||||||
|
onlyClickable: true,
|
||||||
|
containerName: '',
|
||||||
|
};
|
||||||
|
|
||||||
switch (button) {
|
switch (button) {
|
||||||
case 'back':
|
case 'back':
|
||||||
foundButton = TestsBehatDomUtils.findElementBasedOnText({ text: 'Back' });
|
foundButton = TestingBehatDomUtils.findElementBasedOnText({ text: 'Back' }, options);
|
||||||
break;
|
break;
|
||||||
case 'main menu': // Deprecated name.
|
case 'main menu': // Deprecated name.
|
||||||
case 'more menu':
|
case 'more menu':
|
||||||
foundButton = TestsBehatDomUtils.findElementBasedOnText({
|
foundButton = TestingBehatDomUtils.findElementBasedOnText({
|
||||||
text: 'More',
|
text: 'More',
|
||||||
selector: 'ion-tab-button',
|
selector: 'ion-tab-button',
|
||||||
});
|
}, options);
|
||||||
break;
|
break;
|
||||||
case 'user menu' :
|
case 'user menu' :
|
||||||
foundButton = TestsBehatDomUtils.findElementBasedOnText({ text: 'User account' });
|
foundButton = TestingBehatDomUtils.findElementBasedOnText({ text: 'User account' }, options);
|
||||||
break;
|
break;
|
||||||
case 'page menu':
|
case 'page menu':
|
||||||
foundButton = TestsBehatDomUtils.findElementBasedOnText({ text: 'Display options' });
|
foundButton = TestingBehatDomUtils.findElementBasedOnText({ text: 'Display options' }, options);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return 'ERROR: Unsupported standard button type';
|
return 'ERROR: Unsupported standard button type';
|
||||||
|
@ -180,7 +163,7 @@ export class TestsBehatRuntime {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click button
|
// Click button
|
||||||
await TestsBehatDomUtils.pressElement(foundButton);
|
await TestingBehatDomUtils.pressElement(foundButton);
|
||||||
|
|
||||||
return 'OK';
|
return 'OK';
|
||||||
}
|
}
|
||||||
|
@ -190,7 +173,7 @@ export class TestsBehatRuntime {
|
||||||
*
|
*
|
||||||
* @return OK if successful, or ERROR: followed by message
|
* @return OK if successful, or ERROR: followed by message
|
||||||
*/
|
*/
|
||||||
static closePopup(): string {
|
closePopup(): string {
|
||||||
this.log('Action - Close popup');
|
this.log('Action - Close popup');
|
||||||
|
|
||||||
let backdrops = Array.from(document.querySelectorAll('ion-backdrop'));
|
let backdrops = Array.from(document.querySelectorAll('ion-backdrop'));
|
||||||
|
@ -206,7 +189,7 @@ export class TestsBehatRuntime {
|
||||||
backdrop.click();
|
backdrop.click();
|
||||||
|
|
||||||
// Mark busy until the click finishes processing.
|
// Mark busy until the click finishes processing.
|
||||||
TestsBehatBlocking.delay();
|
TestingBehatBlocking.delay();
|
||||||
|
|
||||||
return 'OK';
|
return 'OK';
|
||||||
}
|
}
|
||||||
|
@ -215,20 +198,24 @@ export class TestsBehatRuntime {
|
||||||
* Function to find an arbitrary element based on its text or aria label.
|
* Function to find an arbitrary element based on its text or aria label.
|
||||||
*
|
*
|
||||||
* @param locator Element locator.
|
* @param locator Element locator.
|
||||||
* @param containerName Whether to search only inside a specific container content.
|
* @param options Search options.
|
||||||
* @return OK if successful, or ERROR: followed by message
|
* @return OK if successful, or ERROR: followed by message
|
||||||
*/
|
*/
|
||||||
static find(locator: TestBehatElementLocator, containerName: string): string {
|
find(locator: TestingBehatElementLocator, options: Partial<TestingBehatFindOptions> = {}): string {
|
||||||
this.log('Action - Find', { locator, containerName });
|
this.log('Action - Find', { locator, ...options });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const element = TestsBehatDomUtils.findElementBasedOnText(locator, containerName);
|
const element = TestingBehatDomUtils.findElementBasedOnText(locator, {
|
||||||
|
onlyClickable: false,
|
||||||
|
containerName: '',
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return 'ERROR: No element matches locator to find.';
|
return 'ERROR: No element matches locator to find.';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.log('Action - Found', { locator, containerName, element });
|
this.log('Action - Found', { locator, element, ...options });
|
||||||
|
|
||||||
return 'OK';
|
return 'OK';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -242,11 +229,11 @@ export class TestsBehatRuntime {
|
||||||
* @param locator Element locator.
|
* @param locator Element locator.
|
||||||
* @return OK if successful, or ERROR: followed by message
|
* @return OK if successful, or ERROR: followed by message
|
||||||
*/
|
*/
|
||||||
static scrollTo(locator: TestBehatElementLocator): string {
|
scrollTo(locator: TestingBehatElementLocator): string {
|
||||||
this.log('Action - scrollTo', { locator });
|
this.log('Action - scrollTo', { locator });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let element = TestsBehatDomUtils.findElementBasedOnText(locator);
|
let element = TestingBehatDomUtils.findElementBasedOnText(locator, { onlyClickable: false, containerName: '' });
|
||||||
|
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return 'ERROR: No element matches element to scroll to.';
|
return 'ERROR: No element matches element to scroll to.';
|
||||||
|
@ -269,7 +256,7 @@ export class TestsBehatRuntime {
|
||||||
*
|
*
|
||||||
* @return OK if successful, or ERROR: followed by message
|
* @return OK if successful, or ERROR: followed by message
|
||||||
*/
|
*/
|
||||||
static async loadMoreItems(): Promise<string> {
|
async loadMoreItems(): Promise<string> {
|
||||||
this.log('Action - loadMoreItems');
|
this.log('Action - loadMoreItems');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -316,13 +303,13 @@ export class TestsBehatRuntime {
|
||||||
* @param locator Element locator.
|
* @param locator Element locator.
|
||||||
* @return YES or NO if successful, or ERROR: followed by message
|
* @return YES or NO if successful, or ERROR: followed by message
|
||||||
*/
|
*/
|
||||||
static isSelected(locator: TestBehatElementLocator): string {
|
isSelected(locator: TestingBehatElementLocator): string {
|
||||||
this.log('Action - Is Selected', locator);
|
this.log('Action - Is Selected', locator);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const element = TestsBehatDomUtils.findElementBasedOnText(locator);
|
const element = TestingBehatDomUtils.findElementBasedOnText(locator, { onlyClickable: false, containerName: '' });
|
||||||
|
|
||||||
return TestsBehatDomUtils.isElementSelected(element, document.body) ? 'YES' : 'NO';
|
return TestingBehatDomUtils.isElementSelected(element, document.body) ? 'YES' : 'NO';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return 'ERROR: ' + error.message;
|
return 'ERROR: ' + error.message;
|
||||||
}
|
}
|
||||||
|
@ -334,17 +321,17 @@ export class TestsBehatRuntime {
|
||||||
* @param locator Element locator.
|
* @param locator Element locator.
|
||||||
* @return OK if successful, or ERROR: followed by message
|
* @return OK if successful, or ERROR: followed by message
|
||||||
*/
|
*/
|
||||||
static async press(locator: TestBehatElementLocator): Promise<string> {
|
async press(locator: TestingBehatElementLocator): Promise<string> {
|
||||||
this.log('Action - Press', locator);
|
this.log('Action - Press', locator);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const found = TestsBehatDomUtils.findElementBasedOnText(locator);
|
const found = TestingBehatDomUtils.findElementBasedOnText(locator, { onlyClickable: true, containerName: '' });
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
return 'ERROR: No element matches locator to press.';
|
return 'ERROR: No element matches locator to press.';
|
||||||
}
|
}
|
||||||
|
|
||||||
await TestsBehatDomUtils.pressElement(found);
|
await TestingBehatDomUtils.pressElement(found);
|
||||||
|
|
||||||
return 'OK';
|
return 'OK';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -357,7 +344,7 @@ export class TestsBehatRuntime {
|
||||||
*
|
*
|
||||||
* @return OK if successful, or ERROR: followed by message
|
* @return OK if successful, or ERROR: followed by message
|
||||||
*/
|
*/
|
||||||
static async pullToRefresh(): Promise<string> {
|
async pullToRefresh(): Promise<string> {
|
||||||
this.log('Action - pullToRefresh');
|
this.log('Action - pullToRefresh');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -390,11 +377,11 @@ export class TestsBehatRuntime {
|
||||||
*
|
*
|
||||||
* @return OK: followed by header text if successful, or ERROR: followed by message.
|
* @return OK: followed by header text if successful, or ERROR: followed by message.
|
||||||
*/
|
*/
|
||||||
static getHeader(): string {
|
getHeader(): string {
|
||||||
this.log('Action - Get header');
|
this.log('Action - Get header');
|
||||||
|
|
||||||
let titles = Array.from(document.querySelectorAll<HTMLElement>('.ion-page:not(.ion-page-hidden) > ion-header h1'));
|
let titles = Array.from(document.querySelectorAll<HTMLElement>('.ion-page:not(.ion-page-hidden) > ion-header h1'));
|
||||||
titles = titles.filter((title) => TestsBehatDomUtils.isElementVisible(title, document.body));
|
titles = titles.filter((title) => TestingBehatDomUtils.isElementVisible(title, document.body));
|
||||||
|
|
||||||
if (titles.length > 1) {
|
if (titles.length > 1) {
|
||||||
return 'ERROR: Too many possible titles ('+titles.length+').';
|
return 'ERROR: Too many possible titles ('+titles.length+').';
|
||||||
|
@ -416,18 +403,19 @@ export class TestsBehatRuntime {
|
||||||
* @param value New value
|
* @param value New value
|
||||||
* @return OK or ERROR: followed by message
|
* @return OK or ERROR: followed by message
|
||||||
*/
|
*/
|
||||||
static async setField(field: string, value: string): Promise<string> {
|
async setField(field: string, value: string): Promise<string> {
|
||||||
this.log('Action - Set field ' + field + ' to: ' + value);
|
this.log('Action - Set field ' + field + ' to: ' + value);
|
||||||
|
|
||||||
const found: HTMLElement | HTMLInputElement = TestsBehatDomUtils.findElementBasedOnText(
|
const found: HTMLElement | HTMLInputElement = TestingBehatDomUtils.findElementBasedOnText(
|
||||||
{ text: field, selector: 'input, textarea, [contenteditable="true"], ion-select' },
|
{ text: field, selector: 'input, textarea, [contenteditable="true"], ion-select' },
|
||||||
|
{ onlyClickable: false, containerName: '' },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
return 'ERROR: No element matches field to set.';
|
return 'ERROR: No element matches field to set.';
|
||||||
}
|
}
|
||||||
|
|
||||||
await TestsBehatDomUtils.setElementValue(found, value);
|
await TestingBehatDomUtils.setElementValue(found, value);
|
||||||
|
|
||||||
return 'OK';
|
return 'OK';
|
||||||
}
|
}
|
||||||
|
@ -439,7 +427,7 @@ export class TestsBehatRuntime {
|
||||||
* @param className Constructor class name
|
* @param className Constructor class name
|
||||||
* @return Component instance
|
* @return Component instance
|
||||||
*/
|
*/
|
||||||
static getAngularInstance<T = unknown>(selector: string, className: string): T | null {
|
getAngularInstance<T = unknown>(selector: string, className: string): T | null {
|
||||||
this.log('Action - Get Angular instance ' + selector + ', ' + className);
|
this.log('Action - Get Angular instance ' + selector + ', ' + className);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
@ -456,7 +444,7 @@ export class TestsBehatRuntime {
|
||||||
* Logs information from this Behat runtime JavaScript, including the time and the 'BEHAT'
|
* Logs information from this Behat runtime JavaScript, including the time and the 'BEHAT'
|
||||||
* keyword so we can easily filter for it if needed.
|
* keyword so we can easily filter for it if needed.
|
||||||
*/
|
*/
|
||||||
static log(...args: unknown[]): void {
|
log(...args: unknown[]): void {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const nowFormatted = String(now.getHours()).padStart(2, '0') + ':' +
|
const nowFormatted = String(now.getHours()).padStart(2, '0') + ':' +
|
||||||
String(now.getMinutes()).padStart(2, '0') + ':' +
|
String(now.getMinutes()).padStart(2, '0') + ':' +
|
||||||
|
@ -468,24 +456,29 @@ export class TestsBehatRuntime {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TestingBehatRuntime = makeSingleton(TestingBehatRuntimeService);
|
||||||
|
|
||||||
export type BehatTestsWindow = Window & {
|
export type BehatTestsWindow = Window & {
|
||||||
M?: { // eslint-disable-line @typescript-eslint/naming-convention
|
M?: { // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
util?: {
|
util?: {
|
||||||
pending_js?: string[]; // eslint-disable-line @typescript-eslint/naming-convention
|
pending_js?: string[]; // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
behatInit?: () => void;
|
|
||||||
behat?: unknown;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TestBehatElementLocator = {
|
export type TestingBehatFindOptions = {
|
||||||
|
containerName: string;
|
||||||
|
onlyClickable: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TestingBehatElementLocator = {
|
||||||
text: string;
|
text: string;
|
||||||
within?: TestBehatElementLocator;
|
within?: TestingBehatElementLocator;
|
||||||
near?: TestBehatElementLocator;
|
near?: TestingBehatElementLocator;
|
||||||
selector?: string;
|
selector?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TestsBehatInitOptions = {
|
export type TestingBehatInitOptions = {
|
||||||
skipOnBoarding?: boolean;
|
skipOnBoarding?: boolean;
|
||||||
configOverrides?: Partial<EnvironmentConfig>;
|
configOverrides?: Partial<EnvironmentConfig>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,4 +18,4 @@ import { NgModule } from '@angular/core';
|
||||||
* Stub used in production to avoid including testing code in production bundles.
|
* Stub used in production to avoid including testing code in production bundles.
|
||||||
*/
|
*/
|
||||||
@NgModule({})
|
@NgModule({})
|
||||||
export class BehatTestingModule {}
|
export class TestingModule {}
|
|
@ -12,21 +12,25 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||||
import { CoreAppProvider } from '@services/app';
|
import { CoreAppProvider } from '@services/app';
|
||||||
import { CoreDB, CoreDbProvider } from '@services/db';
|
import { TestingBehatRuntime, TestingBehatRuntimeService } from './services/behat-runtime';
|
||||||
|
|
||||||
type AutomatedTestsWindow = Window & {
|
type AutomatedTestsWindow = Window & {
|
||||||
dbProvider?: CoreDbProvider;
|
behat?: TestingBehatRuntimeService;
|
||||||
};
|
};
|
||||||
|
|
||||||
function initializeAutomatedTestsWindow(window: AutomatedTestsWindow) {
|
function initializeAutomatedTestsWindow(window: AutomatedTestsWindow) {
|
||||||
window.dbProvider = CoreDB.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function(): void {
|
|
||||||
if (!CoreAppProvider.isAutomated()) {
|
if (!CoreAppProvider.isAutomated()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeAutomatedTestsWindow(window);
|
window.behat = TestingBehatRuntime.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
providers: [
|
||||||
|
{ provide: APP_INITIALIZER, multi: true, useValue: () => initializeAutomatedTestsWindow(window) },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class TestingModule {}
|
|
@ -0,0 +1,16 @@
|
||||||
|
@app @javascript
|
||||||
|
Feature: It has a Behat runtime with testing helpers.
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given the following "users" exist:
|
||||||
|
| username |
|
||||||
|
| student1 |
|
||||||
|
|
||||||
|
Scenario: Finds and presses elements
|
||||||
|
Given I entered the app as "student1"
|
||||||
|
When I set the following fields to these values in the app:
|
||||||
|
| Search by activity type or name | Foo bar |
|
||||||
|
Then I should find "Search" "button" in the app
|
||||||
|
And I should find "Clear search" in the app
|
||||||
|
And I should be able to press "Search" "button" in the app
|
||||||
|
But I should not be able to press "Clear search" in the app
|
Loading…
Reference in New Issue