diff --git a/.circleci/config.yml b/.circleci/config.yml index 1e186b99ad..b395612957 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ version: 2.1 executors: rocky8: &rocky8-executor docker: - - image: tools-ext-01.ccr.xdmod.org/xdmod:x86_64-rockylinux8.9.20231119-v11.0.0-1.0-03 + - image: cimg/base:current jobs: build: parameters: @@ -15,95 +15,154 @@ jobs: type: executor install-type: type: string + string_os: + type: string executor: << parameters.os >> environment: COMPOSER_ALLOW_SUPERUSER: 1 XDMOD_REALMS: 'jobs,storage,cloud,resourcespecifications' + QA_BRANCH: 'v2' TRAVIS_COMMIT_RANGE: << pipeline.git.base_revision >>..<> XDMOD_IS_CORE: yes - XDMOD_INSTALL_DIR: /xdmod + XDMOD_INSTALL_DIR: /root/xdmod XDMOD_TEST_MODE: << parameters.install-type >> steps: - checkout + - setup_remote_docker + - run: + name: Docker Compose corresponding OS file + command: docker compose -f ~/project/tests/playwright/Docker/docker-compose.yml up -d - run: - name: install the composer dependencies - command: composer install + name: Generate Key for XDMoD + command: docker exec xdmod openssl genrsa -out /etc/pki/tls/private/localhost.key -rand /proc/cpuinfo:/proc/filesystems:/proc/interrupts:/proc/ioports:/proc/uptime 2048 - run: - name: Create Test Artifact Directories + name: Generate Cert for XDMoD + command: docker exec xdmod /usr/bin/openssl req -new -key /etc/pki/tls/private/localhost.key -x509 -sha256 -days 365 -set_serial $RANDOM -extensions v3_req -out /etc/pki/tls/certs/localhost.crt -subj "/C=XX/L=Default City/O=Default Company Ltd" + - run: + name: Copy Files for Playwright and XDMoD containers command: | + docker cp ~/project xdmod:/root/xdmod + docker cp ~/project playwright:/root/xdmod + - run: + name: Create test result directories + command: | + rm -rf ~/phpunit mkdir ~/phpunit + rm -rf /tmp/screenshots mkdir /tmp/screenshots + - run: + name: Create Test Artifact Directories in XDMoD + command: | + docker exec xdmod mkdir /root/phpunit + docker exec xdmod mkdir /tmp/screenshots + - run: + name: Install XDMoD Composer Dependencies + command: docker exec -w /root/xdmod xdmod composer install - run: name: Build XDMoD RPM - command: ~/bin/buildrpm xdmod + command: docker exec -w /root/xdmod xdmod /root/bin/buildrpm xdmod + - run: + name: Install / Upgrade XDMoD from the newly created RPM + command: docker exec -e XDMOD_TEST_MODE=<< parameters.install-type >> xdmod /root/xdmod/tests/ci/bootstrap.sh + - run: + name: Validate that the install / upgrade went as expected + command: docker exec -w /root/xdmod xdmod /root/xdmod/tests/ci/validate.sh - run: - name: Install / Upgrade XDMoD from RPM - command: ./tests/ci/bootstrap.sh + name: Make sure that the test dependencies are installed + command: docker exec -w /root/xdmod xdmod composer install - run: - name: Validate the newly installed / Upgraded XDMoD - command: ./tests/ci/validate.sh + name: Setup the SimpleSAML server etc. so we can test SSO + command: docker exec xdmod /root/xdmod/tests/ci/samlSetup.sh + - when: + condition: + equal: [ << parameters.install-type >>, 'upgrade' ] + steps: + - run: + name: Checkout QA Repo + command: docker exec -w /root xdmod git clone --depth=1 --branch="$QA_BRANCH" https://github.com/ubccr/xdmod-qa.git /root/.qa + - run: + name: Install QA dependencies + command: docker exec -w /root/xdmod xdmod /root/.qa/scripts/install.sh + - run: + name: Configure xdmod as a safe directory + command: docker exec -w /root/xdmod xdmod git config --global --add safe.directory /xdmod + - run: + name: Setup remote upstream + command: | + docker exec -w /root/xdmod xdmod git config --global --add safe.directory /root/xdmod + docker exec -w /root/xdmod xdmod git remote add upstream https://github.com/ubccr/xdmod.git + - run: + name: Copy portal_settings.ini into place + command: docker exec xdmod cp /etc/xdmod/portal_settings.ini /root/xdmod/configuration/portal_settings.ini + - run: + name: Run QA Tests + command: docker exec -w /root/xdmod xdmod /root/.qa/scripts/build.sh - run: - name: Make sure that the Composer Test Dependencies are installed - command: composer install --no-progress + name: Make sure that the Test Dependencies are installed + command: docker exec -w /root/xdmod xdmod composer install --no-progress + - run: + name: Run Regression Tests + command: docker exec -e XDMOD_TEST_MODE=<< parameters.install-type >> -w /root/xdmod xdmod /root/xdmod/tests/regression/runtests.sh - run: name: Setup Configuration Files for Integration Tests command: | - mv ./configuration/organization.json ./configuration/organization.json.old - mv ./configuration/portal_settings.ini ./configuration/portal_settings.ini.old - cp /etc/xdmod/portal_settings.ini ./configuration/portal_settings.ini - cp /etc/xdmod/organization.json ./configuration/organization.json + docker exec xdmod mv /root/xdmod/configuration/organization.json /root/xdmod/configuration/organization.json.old + docker exec xdmod mv /root/xdmod/configuration/portal_settings.ini /root/xdmod/configuration/portal_settings.ini.old + docker exec xdmod cp /etc/xdmod/portal_settings.ini /root/xdmod/configuration/portal_settings.ini + docker exec xdmod cp /etc/xdmod/organization.json /root/xdmod/configuration/organization.json - run: - name: Setup & Run QA Tests - command: | - ./tests/ci/scripts/qa-test-setup.sh + name: Running Integration Tests + command: docker exec -w /root/xdmod xdmod /root/xdmod/tests/integration/runtests.sh --junit-output-dir /root/phpunit - run: - name: Clear out command-line PHP errors generated by composer installs - command: > - if [ -e /var/log/php_errors.log ]; then - sed -i '/PHP Warning: openssl_x509_parse(): illegal ASN1 data type for timestamp in - on line 4/d' /var/log/php_errors.log; - fi - - run: ./tests/regression/runtests.sh - - run: ./tests/integration/runtests.sh --junit-output-dir ~/phpunit - - run: ./tests/regression/post_ingest_test.sh --junit-output-dir ~/phpunit - - run: ./tests/component/runtests.sh --junit-output-dir ~/phpunit + name: Run Regression Post Ingest Tests + command: docker exec -w /root/xdmod xdmod /root/xdmod/tests/regression/post_ingest_test.sh --junit-output-dir /root/phpunit - run: - name: Ensure that no XDMoD exceptions were logged - command: test ! -f /root/xdmod/logs/exceptions.log + name: Run Component Tests + command: docker exec -w /root/xdmod xdmod /root/xdmod/tests/component/runtests.sh --junit-output-dir /root/phpunit - run: - name: 'Install Chromium 99' - command: | - pushd $HOME && \ - rm -rf chrome-linux && \ - wget -O chrome-linux.zip "https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F961656%2Fchrome-linux.zip?generation=1642723767466615&alt=media" && \ - unzip chrome-linux.zip && \ - ln -s /root/chrome-linux/chrome /usr/local/bin/google-chrome && \ - popd - - run: - name: 'Bodge the nodejs version to run an older one for the webdriver tests' + name: Update the HTTPD config file + command: docker exec xdmod sed -i 's/ServerName localhost/ServerName xdmod/g' /etc/httpd/conf.d/xdmod.conf + - run: + name: Restart XDMoD's services + command: docker exec xdmod /root/bin/services restart + - run: + name: Run Playwright Tests + command: docker exec -w /root/xdmod/tests/playwright playwright /root/xdmod/tests/playwright/runtests.sh -j << parameters.string_os >> + - run: + name: Copy Test Results into Unit command: | - curl https://nodejs.org/dist/v10.24.1/node-v10.24.1-linux-x64.tar.xz | tar -xJC /usr/local - chown root:root -R /usr/local/node-v10.24.1-linux-x64 - - run: pushd ./tests/ui && PATH=/usr/local/node-v10.24.1-linux-x64/bin:$PATH /usr/local/node-v10.24.1-linux-x64/bin/npm install && popd - - run: PATH=/usr/local/node-v10.24.1-linux-x64/bin:$PATH ./tests/ui/runtests.sh --headless --log-junit ~/phpunit - + docker cp xdmod:/root/phpunit ~/phpunit + docker cp xdmod:/tmp/screenshots /tmp/screenshots + mkdir ~/project/log + docker cp xdmod:/var/log/xdmod ~/project/log + docker cp xdmod:/var/log/php-fpm/ ~/project/log + docker cp playwright:/root/xdmod/tests/playwright/test-results /tmp/screenshots - run: - name: 'Run SSO Tests' + name: Copy Test results for Operating System command: | - PATH=/usr/local/node-v10.24.1-linux-x64/bin:$PATH ./tests/ci/samlSetup.sh - PATH=/usr/local/node-v10.24.1-linux-x64/bin:$PATH ./tests/ui/runtests.sh --headless --log-junit ~/phpunit --sso - ./vendor/phpunit/phpunit/phpunit -c ./tests/integration/phpunit.xml.dist --testsuite sso --log-junit ~/phpunit/xdmod-sso-integration.xml + docker cp "playwright:/root/xdmod/tests/playwright/test_results-<< parameters.string_os >>.xml" ~/phpunit - run: - name: Ensure that no unexpected Apache errors were generated ( We expect PHP Deprecated ) + name: Clear out command-line PHP errors generated by composer installs + command: > + if [ -e ~/project/log/php_errors.log ]; then sed -i '/PHP Warning: openssl_x509_parse(): illegal ASN1 data type for timestamp in - on line 4/d' /var/log/php_errors.log; fi + - run: + name: Test to make sure that we don't have any unexpected apache errors command: > - if [ -e /var/log/php-fpm/www-error.log ]; then - test `egrep -v "PHP Deprecated.*vendor\/.*" /var/log/php-fpm/www-error.log | wc -l` = 0; + if [ -e ~/project/log/apache-error.log ]; then + test "$(fgrep -v ' [ssl:warn] ' ~/project/log/apache-error.log | wc -l)" = 0 fi + - run: + name: Ensure that no unexpected Apache errors were generated + command: test ! -e ~/project/log/error.log - run: name: Ensure that no PHP command-line errors were generated command: > - if [ -e /var/log/php_errors.log ]; then - test `egrep -v "PHP Deprecated.*vendor\/.*|PHP Notice: fread()" /var/log/php_errors.log | wc -l` = 0; - fi + if [ -e ~/project/log/php_errors.log ]; then test `fgrep -v 'vendor/phpunit/phpunit/src' ~/project/log/php_errors.log | wc -l` = 0; fi + - run: + name: Run on fail status + command: | + docker cp playwright:/root/xdmod/tests/playwright/test-results /tmp/screenshots + when: on_fail - store_artifacts: path: /tmp/screenshots - store_artifacts: @@ -125,3 +184,4 @@ workflows: parameters: os: [rocky8] install-type: ["fresh_install", "upgrade"] + string_os: [rocky8] diff --git a/.gitignore b/.gitignore index 8fda1cde97..652cb48848 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ logs/ *.msm *.msp +sphinx_venv ### Bower ### bower_components diff --git a/html/gui/js/report_builder/ReportsOverview.js b/html/gui/js/report_builder/ReportsOverview.js index 161fe74c3c..0cd9547c0e 100644 --- a/html/gui/js/report_builder/ReportsOverview.js +++ b/html/gui/js/report_builder/ReportsOverview.js @@ -736,6 +736,7 @@ XDMoD.ReportsOverview = Ext.extend(Ext.Panel, { handler: newReportBasedOn }); + btnNewBasedOn.setVisible(false); var mnuNewBasedOn = new XDMoD.Reporting.ReportCloneMenu({ @@ -748,6 +749,7 @@ XDMoD.ReportsOverview = Ext.extend(Ext.Panel, { } }); + mnuNewBasedOn.setVisible(false); var btnEditReport = new Ext.Button({ diff --git a/tests/ci/bootstrap.sh b/tests/ci/bootstrap.sh index ac3d5a5a70..ed16456663 100755 --- a/tests/ci/bootstrap.sh +++ b/tests/ci/bootstrap.sh @@ -63,20 +63,25 @@ then dnf install -y ~/rpmbuild/RPMS/*/*.rpm mysql_install_db --user mysql - # Make sure that the db config file is setup correctly w/ `sql_mode=` - echo "# this is read by the standalone daemon and embedded servers - [server] - sql_mode= - # this is only for the mysqld standalone daemon - # Settings user and group are ignored when systemd is used. - # If you need to run mysqld under a different user or group, - # customize your systemd unit file for mysqld/mariadb according to the - # instructions in http://fedoraproject.org/wiki/Systemd - [mysqld] - datadir=/var/lib/mysql - socket=/var/lib/mysql/mysql.sock - log-error=/var/log/mariadb/mariadb.log - pid-file=/run/mariadb/mariadb.pid" > /etc/my.cnf.d/mariadb-server.cnf + if [ -f /etc/my.cnf.d/mariadb-server.cnf.rpmsave ]; then + mv /etc/my.cnf.d/mariadb-server.cnf.rpmsave /etc/my.cnf.d/mariadb-server.cnf + fi + if [ -f /etc/my.cnf.d/mariadb-server.cnf ]; then + >/etc/my.cnf.d/mariadb-server.cnf + echo "# this is read by the standalone daemon and embedded servers + [server] + sql_mode= + # this is only for the mysqld standalone daemon + # Settings user and group are ignored when systemd is used. + # If you need to run mysqld under a different user or group, + # customize your systemd unit file for mysqld/mariadb according to the + # instructions in http://fedoraproject.org/wiki/Systemd + [mysqld] + datadir=/var/lib/mysql + socket=/var/lib/mysql/mysql.sock + log-error=/var/log/mariadb/mariadb.log + pid-file=/run/mariadb/mariadb.pid" > /etc/my.cnf.d/mariadb-server.cnf + fi copy_template_httpd_conf ~/bin/services start diff --git a/tests/ci/samlSetup.sh b/tests/ci/samlSetup.sh index 1a902c244d..08e6bcc7fc 100755 --- a/tests/ci/samlSetup.sh +++ b/tests/ci/samlSetup.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Used for docker build, cache file will need to be upgraded if newer version is needed +#Used for docker build, cache file will need to be upgraded if newer version is needed CACHE_FILE='/root/saml-idp.tar.gz' DEFAULT_INSTALL_DIR=/usr/share/xdmod @@ -179,6 +179,7 @@ cat > "$VENDOR_DIR/simplesamlphp/simplesamlphp/config/authsources.php" < "$VENDOR_DIR/simplesamlphp/simplesamlphp/metadata/saml20-idp-remote.php" < "$VENDOR_DIR/simplesamlphp/simplesamlphp/metadata/saml20-idp-remote.php" < 0 => array ( 'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', - 'Location' => 'https://localhost:7000', + 'Location' => 'https://$HOSTNAME:7000', ), 1 => array ( 'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', - 'Location' => 'https://localhost:7000', + 'Location' => 'https://$HOSTNAME:7000', ), ), 'SingleLogoutService' => @@ -207,7 +208,7 @@ cat > "$VENDOR_DIR/simplesamlphp/simplesamlphp/metadata/saml20-idp-remote.php" < 0 => array ( 'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', - 'Location' => 'https://localhost:7000/signout', + 'Location' => 'https://$HOSTNAME:7000/signout', ), ), 'ArtifactResolutionService' => @@ -232,5 +233,5 @@ cat > "$VENDOR_DIR/simplesamlphp/simplesamlphp/metadata/saml20-idp-remote.php" < ); EOF -node app.js --acs https://localhost/simplesaml/module.php/saml/sp/saml2-acs.php/xdmod-sp --aud https://localhost/simplesaml/module.php/saml/sp/metadata.php/xdmod-sp --httpsPrivateKey idp-private-key.pem --httpsCert idp-public-cert.pem --https true > /var/log/xdmod/samlidp.log 2>&1 & +node app.js --acs https://$HOSTNAME/simplesaml/module.php/saml/sp/saml2-acs.php/xdmod-sp --aud https://$HOSTNAME/simplesaml/module.php/saml/sp/metadata.php/xdmod-sp --httpsPrivateKey idp-private-key.pem --httpsCert idp-public-cert.pem --https true > /var/log/xdmod/samlidp.log 2>&1 & httpd -k start diff --git a/tests/ci/scripts/qa-test-setup.sh b/tests/ci/scripts/qa-test-setup.sh old mode 100755 new mode 100644 diff --git a/tests/playwright/.gitignore b/tests/playwright/.gitignore new file mode 100644 index 0000000000..cb21e89fa8 --- /dev/null +++ b/tests/playwright/.gitignore @@ -0,0 +1 @@ +data/*state.json diff --git a/tests/playwright/Docker/.env b/tests/playwright/Docker/.env new file mode 100644 index 0000000000..3811f43608 --- /dev/null +++ b/tests/playwright/Docker/.env @@ -0,0 +1,6 @@ +GIT_USER=ubccr +GIT_BRANCH=main +XDMOD_SOURCE_DIR=/xdmod +XDMOD_HOSTNAME=playwright_xdmod +PLAYWRIGHT_HOSTNAME=playwright +BASE_URL=https://playwright_xdmod diff --git a/tests/playwright/Docker/docker-compose.yml b/tests/playwright/Docker/docker-compose.yml new file mode 100644 index 0000000000..2a0ddefdf7 --- /dev/null +++ b/tests/playwright/Docker/docker-compose.yml @@ -0,0 +1,36 @@ +services: + xdmod: + image: tools-ext-01.ccr.xdmod.org/xdmod:x86_64-rockylinux8.9.20231119-v11.0.0-1.0-03 + container_name: xdmod + hostname: xdmod + networks: + - testing-rocky8 + shm_size: 2g + ports: + - 9006:443 + - 7000:7000 + environment: + XDMOD_REALMS: 'jobs,storage,cloud,resourcespecifications' + XDMOD_IS_CORE: true + stdin_open: true + tty: true + command: sleep infinity + playwright: + image: mcr.microsoft.com/playwright:v1.50.1-jammy + hostname: playwright + container_name: playwright + networks: + - testing-rocky8 + stdin_open: true + tty: true + ipc: host + links: + - xdmod + depends_on: + - xdmod + environment: + BASE_URL: https://xdmod + XDMOD_REALMS: 'jobs,storage,cloud,resourcespecifications' + command: sleep infinity +networks: + testing-rocky8: diff --git a/tests/playwright/Docker/playwright/Dockerfile b/tests/playwright/Docker/playwright/Dockerfile new file mode 100644 index 0000000000..36df5a9919 --- /dev/null +++ b/tests/playwright/Docker/playwright/Dockerfile @@ -0,0 +1,19 @@ +FROM mcr.microsoft.com/playwright:v1.48.2-focal + +ENV GIT_BRANCH=${GIT_BRANCH:-playwright_poc} +ENV GIT_USER=${GIT_USER:-ubccr} +ENV XDMOD_SOURCE_DIR=${XDMOD_SOURCE_DIR:-/xdmod} +ENV XDMOD_REALMS=${XDMOD_REALMS:-jobs,cloud,storage} +ENV BASE_URL=${BASE_URL:-https://xdmod} + +RUN apt update +RUN apt install -y iputils-ping vim procps +RUN wget https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh -P $HOME/ +RUN chmod +x $HOME/wait-for-it.sh +COPY bin /root/bin +RUN git clone --branch=$GIT_BRANCH --depth=1 https://github.com/$GIT_USER/xdmod.git $XDMOD_SOURCE_DIR +RUN npm upgrade -g npm -y +WORKDIR $XDMOD_SOURCE_DIR/tests/playwright +RUN npm install +RUN npx playwright install --with-deps chromium +ENTRYPOINT /root/bin/entrypoint.sh diff --git a/tests/playwright/Docker/playwright/bin/entrypoint.sh b/tests/playwright/Docker/playwright/bin/entrypoint.sh new file mode 100755 index 0000000000..eeb886700a --- /dev/null +++ b/tests/playwright/Docker/playwright/bin/entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +echo "Waiting for XDMoD container to spin up..." +$HOME/wait-for-it.sh -h xdmod -p 443 -- echo "XDMoD is up!" + + +pushd ${XDMOD_SOURCE_DIR}/tests/playwright || exit +echo "Installing -D @playwright/test..." +npm install -D @playwright/test +echo "playwright installed!" +echo "Updating playwright.config.ts..." +sed -i "s|'https://172.17.0.3/'|'https://xdmod'|g" playwright.config.ts +echo "Running playwright tests..." +BASE_URL=https://xdmod npx playwright test ${XDMOD_SOURCE_DIR}/tests/playwright/tests/* --workers=7 +popd || exit +echo "Playwright tests complete!" diff --git a/tests/playwright/Docker/xdmod/conf/xdmod.conf b/tests/playwright/Docker/xdmod/conf/xdmod.conf new file mode 100644 index 0000000000..a223d72978 --- /dev/null +++ b/tests/playwright/Docker/xdmod/conf/xdmod.conf @@ -0,0 +1,137 @@ +# TEMPLATE Apache configuration file for Open XDMoD. This file should +# be copied to the Apache configuration directory and +# edited to specify the correct site-specific settings. +# +# On CentOS 7 and RHEL 7, this file should be copied +# to: +# /etc/httpd/conf.d/xdmod.conf +# +# For other Linux distributions consult the distribtion documentation +# to determine the path to the webserver configuration files. +# +# This template file must be modified to update site specific settings: +# +# The ServerName setting should be updated. +# +# The SSLCertificateFile and SSLCertificateKeyFile settings should +# be updated to specify paths to the valid SSL certificates for the +# site. +# +# Optionally the port number in the VirtualHost section can be updated +# from 443 to the desired listen port. +# +# The server name and port number in the Apache configuration must match +# the site_address and user_manual settings in the Open XDMoD portal_settings.ini +# configuration file. +# + +# If the server is not already configured to listen on port 443 then the +# following Listen command should be uncommented. +#Listen 443 + + + # The ServerName and ServerAdmin parameters should be updated. + ServerName localhost + ServerAdmin postmaster@localhost + + # Production Open XDMoD instances should use HTTPS + SSLEngine on + SSLProtocol all -SSLv2 -SSLv3 + + # Update the SSLCertificateFile and SSLCertificateKeyFile parameters + # to the correct paths to your SSL certificate. + SSLCertificateFile /etc/pki/tls/certs/localhost.crt + SSLCertificateKeyFile /etc/pki/tls/private/localhost.key + + + SSLOptions +StdEnvVars + + + # Use HTTP Strict Transport Security to force client to use secure connections only + Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" + + DocumentRoot /usr/share/xdmod/html + + + Options FollowSymLinks + AllowOverride All + DirectoryIndex index.php + + + Require all granted + + + + + RewriteEngine On + RewriteRule (.*) index.php [L] + + + ## SimpleSAML Single Sign On authentication. + #SetEnv SIMPLESAMLPHP_CONFIG_DIR /etc/xdmod/simplesamlphp/config + #Alias /simplesaml /usr/share/xdmod/vendor/simplesamlphp/simplesamlphp/www + # + # Options FollowSymLinks + # AllowOverride All + # + # Require all granted + # + # + + # Update the path to rotatelogs if it is different on your system. + ErrorLog "|/usr/sbin/rotatelogs -n 5 /var/log/xdmod/apache-error.log 1M" + CustomLog "|/usr/sbin/rotatelogs -n 5 /var/log/xdmod/apache-access.log 1M" combined + + + # The ServerName and ServerAdmin parameters should be updated. + ServerName xdmod + ServerAdmin postmaster@localhost + + # Production Open XDMoD instances should use HTTPS + SSLEngine on + SSLProtocol all -SSLv2 -SSLv3 + + # Update the SSLCertificateFile and SSLCertificateKeyFile parameters + # to the correct paths to your SSL certificate. + SSLCertificateFile /etc/pki/tls/certs/localhost.crt + SSLCertificateKeyFile /etc/pki/tls/private/localhost.key + + + SSLOptions +StdEnvVars + + + # Use HTTP Strict Transport Security to force client to use secure connections only + Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" + + DocumentRoot /usr/share/xdmod/html + + + Options FollowSymLinks + AllowOverride All + DirectoryIndex index.php + + + Require all granted + + + + + RewriteEngine On + RewriteRule (.*) index.php [L] + + + ## SimpleSAML Single Sign On authentication. + #SetEnv SIMPLESAMLPHP_CONFIG_DIR /etc/xdmod/simplesamlphp/config + #Alias /simplesaml /usr/share/xdmod/vendor/simplesamlphp/simplesamlphp/www + # + # Options FollowSymLinks + # AllowOverride All + # + # Require all granted + # + # + + # Update the path to rotatelogs if it is different on your system. + ErrorLog "|/usr/sbin/rotatelogs -n 5 /var/log/xdmod/apache-error.log 1M" + CustomLog "|/usr/sbin/rotatelogs -n 5 /var/log/xdmod/apache-access.log 1M" combined + diff --git a/tests/playwright/config/internal_dashboard/settings.json b/tests/playwright/config/internal_dashboard/settings.json new file mode 100644 index 0000000000..92b6933702 --- /dev/null +++ b/tests/playwright/config/internal_dashboard/settings.json @@ -0,0 +1,26 @@ +[ + { + "label": "E-Mail Address", + "type": "text", + "updated": "btest@test.example.com", + "original": "btest@example.com" + }, + { + "label": "User Type", + "type": "dropdown", + "updated": "Testing", + "original": "External" + }, + { + "label": "Map To", + "type": "dropdown", + "updated": "Auk, Great", + "original": "Unknown, Unknown" + }, + { + "label": "Institution", + "type": "dropdown", + "updated": "Unknown Organization", + "original": "Screwdriver" + } +] diff --git a/tests/playwright/config/xdmod.conf b/tests/playwright/config/xdmod.conf new file mode 100644 index 0000000000..bbbc30d983 --- /dev/null +++ b/tests/playwright/config/xdmod.conf @@ -0,0 +1,126 @@ + + # The ServerName and ServerAdmin parameters should be updated. + ServerName localhost + ServerAdmin postmaster@localhost + + # Production Open XDMoD instances should use HTTPS + SSLEngine on + SSLProtocol all -SSLv2 -SSLv3 + + # Update the SSLCertificateFile and SSLCertificateKeyFile parameters + # to the correct paths to your SSL certificate. + SSLCertificateFile /etc/pki/tls/certs/localhost.crt + SSLCertificateKeyFile /etc/pki/tls/private/localhost.key + + + SSLOptions +StdEnvVars + + + # Use HTTP Strict Transport Security to force client to use secure connections only + Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" + + DocumentRoot /usr/share/xdmod/html + + + Options FollowSymLinks + AllowOverride All + DirectoryIndex index.php + + + Require all granted + + + + + RewriteEngine On + RewriteRule (.*) index.php [L] + + + ## SimpleSAML Single Sign On authentication. + SetEnv SIMPLESAMLPHP_CONFIG_DIR /usr/share/xdmod/vendor/simplesamlphp/simplesamlphp/config + Alias /simplesaml /usr/share/xdmod/vendor/simplesamlphp/simplesamlphp/www + + Options FollowSymLinks + AllowOverride All + + Require all granted + + + + Alias /symfony /usr/share/xdmod/public + + Options FollowSymLinks + AllowOverride All + RewriteEngine On + RewriteRule (.*) index.php [L] + + Require all granted + + + # Update the path to rotatelogs if it is different on your system. + ErrorLog "|/usr/sbin/rotatelogs -n 5 /var/log/xdmod/apache-error.log 1M" + CustomLog "|/usr/sbin/rotatelogs -n 5 /var/log/xdmod/apache-access.log 1M" combined + + + # The ServerName and ServerAdmin parameters should be updated. + ServerName xdmod + ServerAdmin postmaster@localhost + + # Production Open XDMoD instances should use HTTPS + SSLEngine on + SSLProtocol all -SSLv2 -SSLv3 + + # Update the SSLCertificateFile and SSLCertificateKeyFile parameters + # to the correct paths to your SSL certificate. + SSLCertificateFile /etc/pki/tls/certs/localhost.crt + SSLCertificateKeyFile /etc/pki/tls/private/localhost.key + + + SSLOptions +StdEnvVars + + + # Use HTTP Strict Transport Security to force client to use secure connections only + Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" + + DocumentRoot /usr/share/xdmod/html + + + Options FollowSymLinks + AllowOverride All + DirectoryIndex index.php + + + Require all granted + + + + + RewriteEngine On + RewriteRule (.*) index.php [L] + + + ## SimpleSAML Single Sign On authentication. + SetEnv SIMPLESAMLPHP_CONFIG_DIR /usr/share/xdmod/vendor/simplesamlphp/simplesamlphp/config + Alias /simplesaml /usr/share/xdmod/vendor/simplesamlphp/simplesamlphp/www + + Options FollowSymLinks + AllowOverride All + + Require all granted + + + + Alias /symfony /usr/share/xdmod/public + + Options FollowSymLinks + AllowOverride All + RewriteEngine On + RewriteRule (.*) index.php [L] + + Require all granted + + + # Update the path to rotatelogs if it is different on your system. + ErrorLog "|/usr/sbin/rotatelogs -n 5 /var/log/xdmod/apache-error.log 1M" + CustomLog "|/usr/sbin/rotatelogs -n 5 /var/log/xdmod/apache-access.log 1M" combined + diff --git a/tests/playwright/lib/about.page.ts b/tests/playwright/lib/about.page.ts new file mode 100644 index 0000000000..f5c6258046 --- /dev/null +++ b/tests/playwright/lib/about.page.ts @@ -0,0 +1,36 @@ +import {expect} from '@playwright/test'; +import {BasePage} from "./base.page"; +import selectors from "./about.selectors"; + +class About extends BasePage{ + readonly selectors = selectors; + readonly roadMapFrame = selectors.roadMapFrame; + readonly trelloBoard = selectors.trelloBoard; + readonly tabLocator = this.page.locator(selectors.tab); + readonly containerLocator = this.page.locator(selectors.container); + readonly centerLocator = this.page.locator(selectors.center); + readonly lastTabLocator = this.page.locator(selectors.last_tab); + + async checkTab(name){ + let check = selectors.navEntryPath(name); + if (name == 'XDMoD'){ + check = '(' + check + ')[1]'; + } + await expect(this.page.locator(check)).toBeVisible(); + await this.page.click(check); + await this.page.waitForLoadState(); + await expect(this.page.locator(selectors.container)).toBeVisible(); + } + + async checkRoadMap(){ + await expect(this.page.locator(selectors.navEntryPath('Roadmap'))).toBeVisible(); + await this.page.locator(selectors.navEntryPath('Roadmap')).click(); + await expect(this.page.locator(this.roadMapFrame)).toBeVisible(); + await this.page.locator(this.roadMapFrame).isVisible(); + // These lines are being commented out due to the flakiness of displaying Trello boards in an iFrame. + // await expect(this.page.frameLocator(this.roadMapFrame).locator(this.trelloBoard)).toBeVisible({timeout: 20000}); + // await expect(this.page.frameLocator(this.roadMapFrame).locator(this.trelloBoard).innerText()).not.toEqual(null); + } +} + +export default About; diff --git a/tests/playwright/lib/about.selectors.ts b/tests/playwright/lib/about.selectors.ts new file mode 100644 index 0000000000..befa54736d --- /dev/null +++ b/tests/playwright/lib/about.selectors.ts @@ -0,0 +1,19 @@ +const selectors = { + tab: '//ul[contains(@class, "x-tab-strip x-tab-strip-top")]', + container: '//div[@id="about_xdmod"]', + center: '//div[@id="about_xdmod"]//div[contains(@class, "x-panel-body") and contains(@class, "x-border-layout-ct")]/div[contains(@class,"x-panel") and contains(@class,"x-panel-reset") and contains(@class,"x-border-panel")]', + last_tab: '//ul/li[@id="main_tab_panel__about_xdmod"]', + myProfile: '//button[contains(@class, "x-btn-text user_profile_16")]', + role: '//div[@id="user_profile_most_privileged_role"]', + navEntryPath: function (name) { + return '//div[@class="x-tree-root-node"]//div[contains(@class,"x-tree-node-el")]//span[contains(text(),"' + String(name) + '")]'; + }, + roadMapFrame: '//iframe[@id="about_roadmap"]', + trelloBoard: '//div[contains(@class,"full-bleed-trello-board")]', + expiredMessageBox: '.x-window', + continueLogoutButton: '.x-window .x-btn', + logoutLink: '//a[@id="logout_link"]', + signInLink: '//a[@id="sign_in_link"]' +}; + +export default selectors; diff --git a/tests/playwright/lib/base.page.ts b/tests/playwright/lib/base.page.ts new file mode 100644 index 0000000000..40f7136876 --- /dev/null +++ b/tests/playwright/lib/base.page.ts @@ -0,0 +1,26 @@ +import {expect, Locator, Page} from "@playwright/test"; + +export class BasePage { + readonly page: Page; + readonly maskSelector: string; + readonly mask: Locator; + readonly baseUrl: string; + + constructor(page: Page, baseUrl: string) { + this.page = page; + this.maskSelector = '.ext-el-mask-msg'; + this.mask = page.locator(this.maskSelector); + this.baseUrl = baseUrl; + } + + public async verifyLocation(url: string, expectedTitle: string) { + const newUrl = new URL(url, this.baseUrl); + try{ + await this.page.goto(newUrl.toString()); + }catch(error){ + throw new Error(error); + } + const title = await this.page.title(); + expect(title).toEqual(expectedTitle); + } +} diff --git a/tests/playwright/lib/base.test.ts b/tests/playwright/lib/base.test.ts new file mode 100644 index 0000000000..347566182a --- /dev/null +++ b/tests/playwright/lib/base.test.ts @@ -0,0 +1,9 @@ +import { test as base } from '@playwright/test'; + +export type TestOptions = { + role: string; +} + +export const test = base.extend({ + role: null +}); diff --git a/tests/playwright/lib/internal_dashboard.page.ts b/tests/playwright/lib/internal_dashboard.page.ts new file mode 100644 index 0000000000..99c3628e1b --- /dev/null +++ b/tests/playwright/lib/internal_dashboard.page.ts @@ -0,0 +1,41 @@ +import {expect} from "@playwright/test"; +import {BasePage} from "./base.page"; +import {LoginInterface} from "./login.page"; +import selectors from "./internal_dashboard.selectors"; + +class InternalDashboard extends BasePage implements LoginInterface { + static readonly selectors = selectors; + + readonly usernameLocator = this.page.locator(InternalDashboard.selectors.login.username); + readonly passwordLocator = this.page.locator(InternalDashboard.selectors.login.password); + readonly submitLocator = this.page.locator(InternalDashboard.selectors.login.submit); + readonly logoutLinkLocator = this.page.locator(InternalDashboard.selectors.logoutLink); + readonly loggedInDisplayLocator = this.page.locator(InternalDashboard.selectors.loggedInDisplayName); + + async login(username: string, password: string, display: string) { + await this.verifyLocation('/internal_dashboard', 'XDMoD Internal Dashboard'); + await this.usernameLocator.isVisible(); + await this.passwordLocator.isVisible(); + await this.submitLocator.isVisible(); + + await this.usernameLocator.fill(username); + await this.passwordLocator.fill(password); + await this.submitLocator.click(); + await this.submitLocator.isHidden(); + + await this.logoutLinkLocator.isVisible(); + await expect(this.loggedInDisplayLocator).toContainText(display); + } + + async logout() { + console.log('Logging Out!'); + await this.logoutLinkLocator.isVisible(); + await this.logoutLinkLocator.click(); + + await this.usernameLocator.isVisible(); + await this.passwordLocator.isVisible(); + await this.submitLocator.isVisible(); + } +} + +export default InternalDashboard; diff --git a/tests/playwright/lib/internal_dashboard.selectors.ts b/tests/playwright/lib/internal_dashboard.selectors.ts new file mode 100644 index 0000000000..4c49ca3742 --- /dev/null +++ b/tests/playwright/lib/internal_dashboard.selectors.ts @@ -0,0 +1,251 @@ +const selectors = { + login: { + username: '//input[@id="field_username"]', + password: '//input[@id="field_password"]', + submit: '//input[@type="submit" and @value="Log In"]' + }, + logoutLink: '//a[@id="header-logout"]', + loggedInDisplayName: '//div[@id="dashboard-header"]//b', + header: { + tabs: { + summary: function () { + return selectors.tabByText('Summary'); + }, + user_management: function () { + return selectors.tabByText('User Management'); + } + } + }, + summary: { + tabs: { + overview: function () { + return selectors.tabByText('Overview'); + }, + log_data: function () { + return selectors.tabByText('Log Data'); + } + } + }, + user_management: { + tabs: { + account_requests: function () { + return selectors.tabByText('XDMoD Account Requests'); + }, + existing_users: function () { + return selectors.tabByText('Existing Users'); + }, + user_stats: function () { + return selectors.tabByText('User Stats'); + } + } + }, + account_requests: { + toolbar: { + create_manage_users: '#acct_requests_create_manage_users' + } + }, + existing_users: { + toolbar: { + create_manage_users: '#existing_users_create_manage_users' + }, + user_table: '', + table: { + container: '//div[contains(@class, "existing_user_grid")]', + /** + * Retrieve a column via `column_name`, for a user via + * `username` which corresponds to a value located in the + * `Username` column. + * + * @param username {string} + * @param column_name {string} + * @returns {string} + */ + col_for_user: function (username, column_name) { + return `( + //div[contains(@class, "existing_user_grid")]//div[contains(@class, "x-grid3-body")]//table//td[ + count(preceding-sibling::td) + 1 = + count(//div[contains(@class, "existing_user_grid")]//div[contains(@class, "x-grid3-header")]//table//td[.="${column_name}"]/preceding-sibling::td) + 1 + ] + ) [ + count( + //div[contains(@class, "existing_user_grid")]//div[contains(@class, "x-grid3-body")]//table//td[.="${username}"]/preceding::div[contains(@class, 'x-grid3-row')] + ) + 1 + ]`; + } + } + }, + create_manage_users: { + loading_mask: '.admin_panel_editor_mask', + window: '//div[contains(@class, "xdmod_admin_panel")]', + tabs: { + new_user: function () { + return `${selectors.create_manage_users.window}//span[contains(@class, 'x-tab-strip-text') and contains(text(), "New User")]`; + }, + current_users: function () { + return `${selectors.create_manage_users.window}//span[contains(@class, 'x-tab-strip-text') and contains(text(), "Current Users")]`; + } + }, + buttons: { + close: function () { + return `${selectors.create_manage_users.window}//button[contains(@class, "general_btn_close")]`; + }, + create_user: function () { + return `${selectors.create_manage_users.window}//button[contains(@class, "admin_panel_btn_create_user")]`; + }, + save_changes: function () { + return `${selectors.create_manage_users.window}//button[contains(text(), "Save Changes")]`; + } + }, + current_users: { + container: '//div[@id="admin_tab_existing_user"]', + dialogs: { + deleteUser: { + container: '//div[contains(@class, "delete_user") and contains(@class, "x-window")]', + button: function (text) { + return `${selectors.modal.containerByTitle('Delete User')}//button[.="${text}"]`; + } + } + }, + settings: { + container: '//div[@id="admin_panel_user_editor"]', + toolbar: { + container: function () { + return `${selectors.create_manage_users.current_users.settings.container}//div[contains(@class, "x-toolbar")]`; + }, + actions: { + button: function () { + return `${selectors.create_manage_users.current_users.settings.toolbar.container()}//button[.="Actions"]`; + }, + container: '//div[contains(@class, "existing_users_action_menu")]', + itemWithText: function (text) { + return `${selectors.create_manage_users.current_users.settings.toolbar.actions.container}//span[.="${text}"]`; + } + } + }, + inputByLabel: function (labelText, inputType) { + return `${selectors.create_manage_users.current_users.settings.container}//label[contains(text(), "${labelText}")]/parent::*//input[@type="${inputType}"]`; + }, + dropDownTriggerByLabel: function (labelText) { + return `${selectors.create_manage_users.current_users.settings.container}//label[contains(text(), "${labelText}")]/parent::*//img[contains(@class, "x-form-trigger")]`; + }, + noUserSelectedModal: function () { + return `${selectors.create_manage_users.current_users.settings.container}//div[contains(@class, 'ext-el-mask-msg')]//div[contains(text(), 'Select A User From The List To The Left')]`; + } + }, + user_list: { + container: function () { + return `${selectors.create_manage_users.current_users.container}//div[contains(@class, 'admin_panel_existing_user_list')]`; + }, + toolbar: { + container: function () { + return `${selectors.create_manage_users.current_users.user_list.container()}//div[contains(@class, 'x-panel-tbar')]`; + }, + buttonByLabel: function (labelText, buttonText) { + return `${selectors.create_manage_users.current_users.user_list.toolbar.container()}//div[contains(text(), "${labelText}")]/following::*//button[contains(text(), "${buttonText}")]`; + } + }, + dropDownItemByText: function (text) { + return `//div[contains(@class, 'x-menu')]//ul[contains(@class, 'x-menu-list')]//span[contains(text(), '${text}')]/parent::*[contains(@class, 'x-menu-item')]`; + }, + /** + * Retrieve a column via `column_name`, for a user via + * `username` which corresponds to a value located in the + * `Username` column. + * + * @param username {string} + * @returns {string} + */ + col_for_user: function (username) { + return `${selectors.create_manage_users.current_users.user_list.container()}//div[contains(@class, "x-grid3-body")]//table//td[.="${username}"]`; + } + }, + button: function (text) { + return `//div[@id="admin_tab_existing_user"]//button[.="${text}"]`; + } + }, + new_user: { + container: function () { + return `${selectors.create_manage_users.window}//div[@id="admin_tab_create_user"]`; + }, + first_name: function () { + return `${selectors.create_manage_users.new_user.container()}//input[contains(@class, "new_user_first_name")]`; + }, + lastName: function () { + return `${selectors.create_manage_users.new_user.container()}//input[contains(@class, "new_user_last_name")]`; + }, + emailAddress: function () { + return `${selectors.create_manage_users.new_user.container()}//input[contains(@class, "new_user_email_address")]`; + }, + username: function () { + return `${selectors.create_manage_users.new_user.container()}//input[contains(@class, "new_user_username")]`; + }, + mapTo: function () { + return `${selectors.create_manage_users.new_user.container()}//input[contains(@class, "new_user_map_to")]`; + }, + mapToTrigger: function () { + return `${selectors.create_manage_users.new_user.mapTo()}/following-sibling::img[contains(@class, "x-form-trigger")]`; + }, + institution: function () { + return `${selectors.create_manage_users.new_user.container()}//input[contains(@class, "new_user_institution")]`; + }, + institution_trigger: function () { + return `${selectors.create_manage_users.new_user.institution()}/following-sibling::img[contains(@class, "x-form-trigger")]`; + }, + userType: function () { + return `${selectors.create_manage_users.new_user.container()}//input[contains(@class, "new_user_user_type")]`; + }, + aclByName: function (name) { + return `${selectors.create_manage_users.new_user.container()}//div[contains(@class, "admin_panel_section_role_assignment_n")]//table[contains(@class, "x-grid3-row-table")]//td[div="${name}"]/following-sibling::td//div[contains(@class, "x-grid3-cell-inner")]/div`; + }, + dialog: function (text) { + return `//div[contains(@class, "x-window") and contains(@class, "x-notification")]//b[contains(@class, "user_management_message") and contains(text(), "${text}")]`; + } + }, + bottom_bar: { + container: function () { + return `${selectors.create_manage_users.window}//div[contains(@class, "x-panel-bbar")]`; + }, + messageByText: function (text) { + return `${selectors.create_manage_users.bottom_bar.container()}//span[contains(text(), "${text}")]`; + } + } + }, + tabByText: function (name) { + return `//div[@id="dashboard-tabpanel"]//span[contains(@class, "x-tab-strip-text") and contains(text(), "${name}")]`; + }, + combo: { + container: '//div[contains(@class, "x-combo-list") and contains(@style, "visibility: visible")]', + itemByText: function (text) { + return `${selectors.combo.container}//div[contains(@class, "x-combo-list-item") and contains(text(), "${text}")]`; + } + }, + createSuccessNotification: function (username) { + return selectors.modal.containerByTitle('User Management') + + '//b[text()[1][contains(., "User")] and text()[2][contains(., "created successfully")]]/b[text() = "' + + username + '"]/ancestor::node()[1]'; + }, + deleteSuccessNotification: function (username) { + return selectors.modal.containerByTitle('User Management') + + '//b[text()[1][contains(., "User")] and text()[2][contains(., "deleted from the portal")]]/b[text() = "' + + username + '"]/ancestor::node()[1]'; + }, + updateSuccessNotification: function (username) { + return selectors.modal.containerByTitle('User Management') + + '//b[text()[1][contains(., "User")] and text()[2][contains(., "updated successfully")]]/b[text() = "' + + username + '"]/ancestor::node()[1]'; + }, + modal: { + containerByTitle: function (title) { + return `//div[contains(@class, "x-window")]//div[contains(@class, "x-window-header")]//span[contains(@class, "x-window-header-text") and text()="${title}"]/ancestor::node()[5]`; + }, + buttonByText: function (modalTitle, buttonText) { + return `${selectors.modal.containerByTitle(modalTitle)}//button[contains(text(), "${buttonText}")]`; + }, + tools: { + close: function (modalTitle) { + return `${selectors.modal.containerByTitle(modalTitle)}//div[contains(@class, "x-tool-close")]`; + } + } + } +}; +export default selectors; diff --git a/tests/playwright/lib/login.page.ts b/tests/playwright/lib/login.page.ts new file mode 100644 index 0000000000..ba58f6528a --- /dev/null +++ b/tests/playwright/lib/login.page.ts @@ -0,0 +1,80 @@ +import {expect, Locator, Page} from '@playwright/test'; +import {BasePage} from "./base.page"; + +export interface LoginInterface { + page: Page; + + login(username: string, password: string, display: string); + + logout(); +} + +export class LoginPage extends BasePage implements LoginInterface { + readonly logo: Locator; + readonly username: Locator; + readonly password: Locator; + readonly signInButton: Locator; + readonly ssoLoginLink: Locator; + readonly ssoSignInButton: Locator; + readonly loginLink: Locator; + readonly localLoginForm: Locator; + readonly welcomeMessage: Locator; + readonly mainTab: Locator; + readonly logoutLink: Locator; + + readonly loginTitle: string; + readonly adminTitle: string; + + readonly sso:boolean; + + constructor(page: Page, baseUrl: string, sso:boolean) { + super(page, baseUrl); + this.sso = sso; + this.logo = page.locator('#logo'); + this.loginLink = page.locator("//a[@id='sign_in_link']"); + this.localLoginForm = page.locator('//div[@id="local_login_form"]'); + this.username = page.locator('#txt_login_username'); + this.password = page.locator('#txt_login_password'); + this.signInButton = page.locator("//table[@id='btn_sign_in']//button"); + this.ssoLoginLink = page.locator('#SSOLoginLink'); + this.ssoSignInButton = page.locator('//button[@id="btn-sign-in"]'); + this.welcomeMessage = page.locator('#welcome_message'); + this.mainTab = page.locator('#main_tab_panel__about_xdmod'); + this.logoutLink = page.locator('#logout_link'); + + this.loginTitle = 'Open XDMoD'; + this.adminTitle = 'XDMoD Internal Dashboard'; + } + + async login(username: string, password: string, display: string) { + //false means sign in with a local XDMoD and true means with XSEDE (sso) + await this.verifyLocation('/', this.loginTitle); + await expect(this.loginLink).toBeVisible(); + await this.loginLink.click(); + if (this.sso) { + await expect(this.ssoLoginLink).toBeVisible(); + await this.ssoLoginLink.click(); + await expect(this.ssoSignInButton).toBeVisible(); + await this.ssoSignInButton.click(); + } else { + await this.localLoginForm.click(); + await expect(this.signInButton).toBeVisible(); + await this.username.click(); + await this.username.fill(username); + await this.password.click(); + await this.password.fill(password); + await this.signInButton.click(); + await expect(this.signInButton).toBeHidden(); + } + await this.welcomeMessage.isVisible(); + await expect(this.welcomeMessage).toContainText(display); + await expect(this.mainTab).toBeVisible(); + } + + async logout() { + await this.logoutLink.isVisible(); + await this.logoutLink.click(); + await this.loginLink.isVisible(); + await this.mainTab.isVisible(); + } +} diff --git a/tests/playwright/lib/mainToolbar.page.ts b/tests/playwright/lib/mainToolbar.page.ts new file mode 100644 index 0000000000..69f16401e7 --- /dev/null +++ b/tests/playwright/lib/mainToolbar.page.ts @@ -0,0 +1,58 @@ +import {expect, Page} from '@playwright/test'; +import {BasePage} from "./base.page"; +import selectors from "./mainToolbar.selectors"; + +class MainToolbar extends BasePage{ + readonly selectors = selectors; + readonly toolbarCloseLocator = this.page.locator(selectors.toolbarClose); + readonly toolbarAboutLocator = this.page.locator(selectors.toolbarAbout); + readonly contactusLocator = this.page.locator(selectors.contactus); + readonly helpLocator = this.page.locator(selectors.help); + readonly aboutLocator = this.page.locator(selectors.about); + readonly containerLocator = this.page.locator(selectors.container); + readonly headerLocator = this.page.locator(selectors.header); + readonly floatlayerLocator = this.page.locator(selectors.floatlayer); + readonly noteLocator = this.page.locator(selectors.note); + + async helpFunc(type, mainTab){ + const helpTypesLoc = this.page.locator(selectors.helpTypes[type]); + await this.helpLocator.click(); + await this.page.locator(selectors.floatlayer).waitFor({state:'visible'}); + await expect(this.floatlayerLocator).toBeVisible(); + await expect(helpTypesLoc).toBeVisible(); + let context = this.page.context(); + const [newPage]:Page = await Promise.all([ + context.waitForEvent('page'), + await helpTypesLoc.click(), + ]); + await newPage.waitForLoadState(); + let ids = context.pages(); + let id = ids.length; + await expect(id).toEqual(2); + await newPage.close(); + await expect(context.pages().length).toEqual(1); + } + + async contactFunc(type){ + const contactTypesLocator = this.page.locator(selectors.contactTypes[type]); + await this.noteLocator.waitFor({state:'hidden'}); + await this.contactusLocator.click(); + await this.floatlayerLocator.waitFor({state:'visible'}); + await contactTypesLocator.waitFor({state:'visible'}); + await contactTypesLocator.click(); + await this.floatlayerLocator.waitFor({state:'hidden'}); + await this.containerLocator.waitFor({state:'visible'}); + const content = await this.headerLocator.textContent(); + await expect(content).toEqual(type); + await this.toolbarCloseLocator.waitFor({state:'visible'}); + await this.toolbarCloseLocator.click(); + await expect(this.aboutLocator).toBeVisible(); + } + + async checkAbout(){ + await this.toolbarAboutLocator.isVisible(); + await this.toolbarAboutLocator.click(); + await this.aboutLocator.isVisible(); + } +} +export default MainToolbar; diff --git a/tests/playwright/lib/mainToolbar.selectors.ts b/tests/playwright/lib/mainToolbar.selectors.ts new file mode 100644 index 0000000000..6cb2b4466d --- /dev/null +++ b/tests/playwright/lib/mainToolbar.selectors.ts @@ -0,0 +1,22 @@ +const selectors= { + helpTypes: { + Manual: '//div[contains(@class, "x-menu x-menu-floating x-layer")]//a[@id="global-toolbar-help-user-manual"]' + }, + contactTypes: { + 'Send Message': '//div[contains(@class, "x-menu x-menu-floating x-layer") and contains(@style,"visibility: visible")]//a[contains(., "Send Message")]', + 'Request Feature': '//div[contains(@class, "x-menu x-menu-floating x-layer") and contains(@style,"visibility: visible")]//a[contains(., "Request Feature")]', + 'Submit Support Request': '//div[contains(@class, "x-menu x-menu-floating x-layer") and contains(@style,"visibility: visible")]//a[contains(., "Submit Support Request")]' + }, + toolbarClose: '//div[contains(@class, "x-window")]//div//div//div//div[contains(@class,"x-window-header x-unselectable x-panel-icon ")]//div', + toolbarAbout: '//table[@id="global-toolbar-about"]//button', + contactus:'//table[@id="global-toolbar-contact-us"]//button', + help: '//div[contains(@class, "x-toolbar x-small-editor x-toolbar-layout-ct")]//table[@id="help_button"]', + about: '//ul/li[@id="main_tab_panel__about_xdmod"]', + container: '//div[contains(@class, "x-window") and contains(@style, "visibility: visible")]', + header: '//div[contains(@class, "x-window")]//div//div//div//div[contains(@class,"x-window-header x-unselectable x-panel-icon ")]//span', + floatlayer: '//div[@class="x-menu x-menu-floating x-layer"]', + note: '.x-window.x-notification', + role: '//span[@id="profile_editor_most_privileged_role"]', + logoutLink: '//a[@id="logout_link"]', +} +export default selectors; diff --git a/tests/playwright/lib/metricExplorer.page.ts b/tests/playwright/lib/metricExplorer.page.ts new file mode 100644 index 0000000000..c721adfe10 --- /dev/null +++ b/tests/playwright/lib/metricExplorer.page.ts @@ -0,0 +1,652 @@ +/** + * Metric Explorer test class + */ +import {expect} from '@playwright/test'; +import {BasePage} from "./base.page"; +import selectors from './metricExplorer.selectors'; +import {instructions} from '../tests/helpers/instructions'; + +class MetricExplorer extends BasePage{ + readonly selectors = selectors; + + readonly mask = selectors.mask; + readonly logoutLink = selectors.logoutLink; + readonly logo = selectors.logo; + readonly newChart = selectors.newChart; + readonly startDate = selectors.startDate; + readonly endDate = selectors.endDate; + readonly toolbar = selectors.toolbar; + readonly container = selectors.container; + readonly load = selectors.load; + readonly filterToolbar = selectors.filters.toolbar; + readonly dataSeriesDef = selectors.dataSeriesDefinition; + readonly filterMenu = selectors.filterMenu; + readonly filterByDialogBox = selectors.filterMenu.filterByDialogBox; + readonly selectedCheckboxes = selectors.filterMenu.selectedCheckboxes(); + readonly firstSelectedCheckbox = selectors.filterMenu.firstSelectedCheckbox(); + readonly okButton = selectors.filterMenu.okButton(); + readonly deleteChart = selectors.deleteChart; + readonly addData = selectors.addData; + readonly addDataButton = selectors.addData.button; + readonly addDataSecondLevel = selectors.addData.secondLevel; + readonly addDataSecondLevelChild = selectors.addData.secondLevelChild(); + readonly dataInput = selectors.data.modal.groupBy.input; + readonly optionsAggregate = selectors.options.aggregate; + readonly optionsButton = selectors.options.button; + readonly optionsSwap = selectors.options.swap; + readonly optionsTitle = selectors.options.title; + readonly chart = selectors.chart; + readonly svg = selectors.chart.svg; + readonly chartContextMenu = selectors.chart.contextMenu; + readonly subtitle = selectors.chart.subtitle(); + readonly catalog = selectors.catalog; + readonly catalogChartMenu = selectors.catalog.addToChartMenu; + readonly buttonMenuFirstLevel = selectors.buttonMenu.firstLevel; + readonly buttonMenuFirstLevelChild = selectors.buttonMenu.firstLevelChild(); + readonly grid = selectors.filters.grid; + readonly apply = selectors.filters.toolbar.apply; + readonly checkbox = selectors.filters.toolbar.checkBox; + readonly firstCheckbox = selectors.filters.toolbar.firstCheckBox; + readonly cancel = selectors.filters.toolbar.cancel; + readonly undo = selectors.undo; + + readonly containerLocator = this.page.locator(selectors.container); + readonly catalogContainerLocator = this.page.locator(selectors.catalog.container); + readonly catalogChartContainerLocator = this.page.locator(selectors.catalog.addToChartMenu.container); + readonly collapseButtonLocator = this.page.locator(selectors.catalog.collapseButton); + readonly startDateLocator = this.page.locator(selectors.startDate); + readonly endDateLocator = this.page.locator(selectors.endDate); + + readonly originalTitle:string = '(untitled query 2)'; + readonly newTitle:string = '"& (untitled query) 2 &"'; + readonly possible:string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + + /** + * Create a new Chart based on a given dataset and plot type + * + * @param {String} chartName name for new chart + * @param {String} datasetType dataset type for new chart + * @param {String} plotType plot type for new chart + */ + async createNewChart(chartName, datasetType, plotType){ + await this.page.click(this.toolbar.buttonByName('New Chart')); + await expect(this.page.locator(this.newChart.topMenuByText(datasetType))).toBeVisible(); + await this.page.click(this.newChart.topMenuByText(datasetType)); + await expect(this.page.locator(this.newChart.subMenuByText(datasetType, plotType))).toBeVisible(); + await this.page.click(this.newChart.subMenuByText(datasetType, plotType)); + await expect(this.page.locator(this.newChart.modalDialog.textBox())).toBeVisible(); + await this.page.click(this.newChart.modalDialog.textBox()); + await this.page.fill(this.newChart.modalDialog.textBox(), chartName); + await this.page.click(this.newChart.modalDialog.ok()); + await expect(this.page.locator(this.newChart.modalDialog.box)).toBeHidden(); + await expect(this.page.locator(this.newChart.modalDialog.noDataMessage)).toBeVisible(); + await expect(this.page.locator(this.mask)).toBeHidden(); + } + + /** + * Waits for catalog container and collapse button to be visible + */ + async waitForLoaded() { + await expect(this.containerLocator).toBeVisible(); + await expect(this.catalogContainerLocator).toBeVisible(); + await expect(this.collapseButtonLocator).toBeVisible(); + } + + /** + * Sets the date of a chart + * + * @param {String} start the start date for the chart + * @param {String} end the end date for the chart + */ + async setDateRange(start, end) { + await this.clickSelector(this.startDate); + await this.page.fill(this.startDate, start); + await expect(this.endDateLocator).toBeVisible(); + await this.endDateLocator.click(); + await this.page.fill(this.endDate, end); + await this.page.click(this.toolbar.buttonByName('Refresh')); + await this.page.locator(this.mask).isHidden(); + } + + /** + * Adds data from catalog option + * + * @param {String} realm the realm of data source + * @param {String} statistic the category name under the realm + * @param {String} groupby the name of the group filter for the data + */ + async addDataViaCatalog(realm, statistic, groupby){ + await expect(this.catalogContainerLocator).toBeVisible(); + await this.page.click(this.catalog.rootNodeByName(realm)); + await this.page.click(this.catalog.nodeByPath(realm, statistic)); + await expect(this.catalogChartContainerLocator).toBeVisible(); + await this.page.click(this.catalogChartMenu.itemByName(groupby)); + } + + /** + * Clicks on the save button + */ + async saveChanges() { + await this.page.click(this.toolbar.buttonByName('Save')); + await this.page.click(this.toolbar.saveChanges); + } + + /** + * Selects a filter from the toolbar + * + * @param {String} filter name of filter option in filter menu + */ + async addFiltersFromToolbar(filter){ + await this.clickSelector(this.toolbar.buttonByName('Add Filter')); + await this.page.click(this.filterMenu.addFilterMenuOption(filter)); + await expect(this.page.locator(this.firstSelectedCheckbox)).toBeVisible(); + let checkboxes = await this.page.$$(this.selectedCheckboxes); + if (checkboxes.length !== 0){ + for (const box of checkboxes) { + await box.click(); + } + } + await this.page.click(this.okButton); + await expect(this.page.locator(this.subtitle)).toBeVisible(); + } + + /** + * Selects a filter by name from toolbar + * + * @param {String} name Name of intended filter to edit + */ + async editFiltersFromToolbar(name){ + for (let i = 0; i < 100; i++){ + if (await this.page.isHidden(this.grid)){ + await this.clickSelector(this.toolbar.buttonByName('Filters')); + } else { + await expect(this.page.locator(this.grid)).toBeVisible(); + break; + } + } + await expect(this.page.locator(this.filterToolbar.byName(name))).toBeVisible(); + await this.page.locator(this.filterToolbar.byName(name)).click(); + await expect(this.page.locator(this.filterToolbar.byName(name))).toBeHidden(); + await this.page.click(this.apply); + await expect(this.page.locator(this.chart.subtitleName(name))).toBeHidden(); + } + + /** + * Select filters from toolbar menu and then cancel + */ + async cancelFiltersFromToolbar() { + await this.clickLogo(); + await this.page.click(this.toolbar.buttonByName('Filters')); + await expect(this.page.locator(this.firstCheckbox)).toBeVisible(); + let checkboxes = await this.page.locator(this.checkbox); + if (checkboxes.length !== 0){ + for (let i = 0; i < 2; i++){ + await checkboxes.nth(i).click(); + } + } + await this.page.click(this.cancel); + await expect(this.page.locator(this.checkbox).length).toEqual(checkboxes.length); + await this.clickLogo(); + } + + /** + * Select first data point in chart and open data information + */ + async openDataSeriesDefinitionFromDataPoint() { + await this.clickLogo(); + await this.clickFirstDataPoint(); + + // Wait for the context menu to be visible. + await expect(this.page.locator('//div[contains(@class, "x-menu-floating") and contains(@style, "visible")]//span[@class="menu-title"]')).toBeVisible(); + await this.page.locator('//div[contains(@class, "x-menu x-menu-floating") and contains(@style , "visibility: visible;")]//ul//li/a//span[text()="Edit Dataset"]//ancestor::a').click({clickCount: 2}) + await expect(this.page.locator('//div[contains(@class, "x-window-body")]//div[contains(@class, "x-panel-header")]//span[@class="x-panel-header-text"]')).toBeVisible(); + } + + /** + * Add a name from filter list from a data series of a data point + * + * @param {String} filter name of category filter from list of filters + * @param {String} name name of filter to add + */ + async addFiltersFromDataSeriesDefinition(filter, name) { + await this.clickLogo(); + await this.openDataSeriesDefinitionFromDataPoint(); + + let addFilter = this.page.locator(this.dataSeriesDef.addFilter()) + await addFilter.isVisible(); + await this.page.screenshot({path: 'add_filter_visible.png'}); + await addFilter.click(); + await this.page.click(this.dataSeriesDef.filter(filter)); + await expect(this.page.locator(this.dataSeriesDef.name(name))).toBeVisible(); + await this.page.click(this.dataSeriesDef.name(name)); + await this.page.click(this.dataSeriesDef.ok); + await this.page.click(this.dataSeriesDef.addButton); + await expect(this.page.locator(this.chart.legendContent(name))).toBeVisible(); + } + + /** + * Edit filter from data series from data point + * + * @param {String} name name of filter from list of filters + */ + async editFiltersFromDataSeriesDefinition(name){ + await this.clickLogo(); + await this.openDataSeriesDefinitionFromDataPoint(); + await this.page.click(this.dataSeriesDef.filterButton()); + await this.page.click(this.dataSeriesDef.box); + await this.page.click(this.dataSeriesDef.apply); + await this.page.click(this.dataSeriesDef.header()); + await this.page.click(this.dataSeriesDef.addButton); + await this.page.locator(this.chart.legendContent(name)).waitFor({state:'detached'}); + } + + /** + * Select filter from data series from data point and then cancel + */ + async cancelFiltersFromDataSeriesDefinition() { + await this.clickLogo(); + await this.openDataSeriesDefinitionFromDataPoint(); + await this.page.locator(this.dataSeriesDef.filterButton()).waitFor({state:'visible'}); + await this.page.click(this.dataSeriesDef.filterButton()); + await this.page.locator(this.dataSeriesDef.checkbox).waitFor({state:'visible'}); + let checkboxes = await this.page.locator(this.dataSeriesDef.checkbox); + if (checkboxes.length !== 0) { + for (let i = 0; i < checkboxes.length; i++) { + await checkboxes.nth(i).click(); + } + } + await this.page.click(this.dataSeriesDef.cancel); + const checkboxLoc = await this.page.locator(this.dataSeriesDef.checkbox); + await expect(checkboxLoc.length).toEqual(checkboxes.length); + await this.page.click(this.dataSeriesDef.header()); + await this.page.click(this.dataSeriesDef.addButton); + } + + /** + * Refresh the page + */ + async clear() { + await this.page.reload(); + await this.page.isVisible(this.logoutLink); + } + + /** + * Helper function to generate a unique title for chart + * + * @param {Number} size length of resulting title + * @return {String} result a randomly generated string for a title + */ + generateTitle(size) { + let result = ''; + for (let i = 0; i < size; i++) { + result += this.possible.charAt(Math.floor(Math.random() * this.possible.length)); + } + return result; + } + + /** + * Set the display type to pie for the chart + */ + async setToPie() { + await this.page.evaluate("return document.getElementsByName('display_type')[0];").click(); + await this.page.evaluate("return document.querySelector('div.x-layer.x-combo-list[style*=\"visibility: visible\"] div.x-combo-list-inner div.x-combo-list-item:last-child');").click(); + } + + /** + * Ensure that the error received/displayed is as expected + */ + async verifyError() { + const invalidChart = await this.page.evaluate("return document.querySelectorAll('div.x-window.x-window-plain.x-window-dlg[style*=\"visibility: visible\"] span.x-window-header-text')[0];").textContent(); + await expect(invalidChart).toEqual('Invalid Chart Display Type'); + const errorText = await this.page.evaluate("return document.querySelectorAll('div.x-window.x-window-plain.x-window-dlg[style*=\"visibility: visible\"] span.ext-mb-text')[0];").textContent(); + await expect(errorText).toContainText('You cannot display timeseries data in a pie chart.'); + await expect(errorText).toContainText('Please change the dataset or display type.'); + } + + /** + * Select options and check cursor position to check pressing the up arrow key + */ + async arrowKeys() { + await this.page.locator(this.optionsButton).waitFor({state:'visible'}).click(); + await this.page.locator(this.optionsTitle).waitFor({state:'visible'}).click(); + const cursorPosition = await this.page.evaluate('return document.getElementById("me_chart_title").selectionStart;'); + await expect(cursorPosition._status).toEqual(0); + await expect(cursorPosition.value).toEqual(this.originalTitle.length, 'Cursor Position not at end'); + await this.page.keyboard.press('ArrowUp'); + const newPosition = await this.page.evaluate('return document.getElementById("me_chart_title").selectionStart;'); + await expect(newPosition._status).toEqual(0); + await expect(newPosition.value).toEqual(0, 'Cursor Position not at begining'); + await this.page.click(this.optionsButton); + } + + /** + * Add data from a specificed name under Jobs + * + * @param {String} maskName selector for mask to ensure hidden status + * @param {String} n name of type for data to be grouped by + */ + async addDataViaMenu(maskName, n) { + await this.page.locator(maskName).isHidden(); + await this.catalogContainerLocator.isVisible(); + await this.page.click(this.addDataButton); + await this.page.click(this.toolbar.addData('Jobs')); + await this.page.click(this.toolbar.addDataGroupBy(n)); + await expect(this.page.locator(this.dataSeriesDef.dialogBox)).toBeVisible(); + } + + /** + * Select add Button from data series + */ + async addDataSeriesByDefinition() { + await this.page.click(this.dataSeriesDef.addButton); + await expect(this.page.locator(this.dataSeriesDef.dialogBox)).toBeHidden(); + } + + /** + * Load an existing chart based on a given name + * + * @param {String} name title of chart + */ + async loadExistingChartByName(name) { + await this.collapseButtonLocator.waitFor({state:'visible'}); + await expect(this.collapseButtonLocator).toBeVisible(); + await this.page.locator(this.toolbar.buttonByName('Load Chart')).waitFor({state:'visible'}); + await expect(this.page.locator(this.toolbar.buttonByName('Load Chart'))).toBeVisible(); + await this.page.click(this.toolbar.buttonByName('Load Chart'), {delay:250}); + await this.page.locator(this.load.dialog).waitFor({state:'visible'}); + await expect(this.page.locator(this.load.dialog)).toBeVisible(); + await this.page.click(this.load.chartByName(name)); + await this.page.locator(this.load.dialog).waitFor({state:'hidden'}); + await expect(this.page.locator(this.load.dialog)).toBeHidden(); + await this.page.locator(this.catalog.expandButton).waitFor({state:'visible', timeout: 10000}); + await expect(this.page.locator(this.catalog.expandButton)).toBeVisible(); + } + + /** + * Check if the chart matches a title, y-axis, legend, and its validity + * + * @param {String} chartTitle name to match the chart to + * @param {String} yAxisLabel name to match the y-axis label to + * @param {String} legend name to match the legend to + * @param {Boolean} isValidChart determine if the chart is valid or not + */ + async checkChart(chartTitle, yAxisLabel, legend, isValidChart = true) { + await this.clickLogo(); + await this.page.locator(this.chart.titleByText(chartTitle)).waitFor({state:'visible'}); + let selToCheck; + if (isValidChart) { + selToCheck = this.chart.credits(); + } else { + selToCheck = this.chart.titleByText(chartTitle, true); + } + await this.page.locator(selToCheck).waitFor({state:'visible'}); + await this.page.click(selToCheck); + + if (yAxisLabel) { + await this.checkChartYAxisLabel(yAxisLabel); + } + + if (legend) { + await this.checkChartLegend(legend); + } + } + + /** + * Helper function for checkChart() for the yAxisLabel + * + * @param {String} yAxisLabel name to match the y-axis label to + */ + async checkChartYAxisLabel(yAxisLabel) { + await this.page.locator(this.chart.yAxisTitle()).waitFor({state:'visible'}); + const yAxisElems = await this.page.$$(this.chart.yAxisTitle()); + if (typeof yAxisLabel === 'string') { + await expect(yAxisElems.length).toEqual(1); + const result = await Promise.all(yAxisElems.map((elem) => { + return elem.textContent(); + })); + await expect(result[0]).toEqual(yAxisLabel); + } else { + await expect(yAxisElems.length).toEqual(yAxisLabel.length); + for (let i = 0; i < legend.length; i++) { + await expect(yAxisElems[i]).toEqual(yAxisLabel[i]); + } + } + } + + /** + * Helper function for checkChart() for the legend + * + * @param {String} legend name to match the legend to + */ + async checkChartLegend(legend) { + await this.page.locator(this.chart.firstLegendContent()).waitFor({state:'visible'}); + const legendElems = await this.page.locator(this.chart.legend()); + if (typeof legend === 'string') { + await expect(legendElems).toBeVisible(); + const result = await legendElems.textContent(); + await expect(result).toEqual(legend); + } else { + const num = await legendElems.count(); + await expect(num).toEqual(legend.length); + for (let i = 0; i < legend.length; i++) { + const computed = await legendElems.nth(i).textContent(); + await expect(computed).toContain(legend[i]); + } + } + } + + /** + * Set the name of the chart from the options menu + * + * @param {String} title name of chart to set to + */ + async setTitleWithOptionsMenu(title) { + await this.page.click(this.optionsButton); + await expect(this.page.locator(this.optionsTitle)).toBeVisible(); + await this.page.fill(this.optionsTitle, ''); + await this.page.fill(this.optionsTitle, title); + } + + /** + * Check if the name of the chart matches a given title + * + * @param {String} title name to check if chart matches + */ + async verifyHighChartsTitle(title) { + const execReturn = await this.page.evaluate('return Ext.util.Format.htmlDecode(document.querySelector("' + this.chart.title() + '").textContent);'); + await expect(execReturn._status).toEqual(0); + await expect(typeof(execReturn.value)).toEqual('string'); + await expect(execReturn.value).toEqual(title); + } + + /** + * Check if the chart matches the readonly newTitle variable + */ + async verifyEditChartTitle() { + await this.page.click(this.chart.title()); + await expect(this.page.isVisible(this.chart.titleInput)); + const titleValue = await this.page.locator(this.chart.titleInput).inputValue(); + await expect(typeof(titleValue)).toEqual('string'); + await expect(titleValue).toEqual(this.newTitle); + await this.page.click(this.chart.titleOkButton); + } + + /** + * Check if the instructions when there is no data matches + * the instructions helper class + */ + async verifyInstructions() { + await this.page.locator(this.newChart.modalDialog.noDataMessage).waitFor({state:'visible'}); + const boo = await instructions(this.page, 'metricExplorer', this.container); + await expect(boo).toBeTruthy(); + } + + /** + * Clears and sets the title of the chart + * + * @param {String} title name of chart to set title to + */ + async setChartTitleViaChart(title) { + await this.page.click(this.chart.title()); + await expect(this.page.locator(this.chart.titleInput)).isVisible(); + await this.page.clearElement(this.chart.titleInput); + await this.page.fill(this.chart.titleInput, title); + await this.page.click(this.chart.titleOkButton); + } + + /** + * Sets group by option to resource + */ + async setGroupByToResource() { + await this.page.click(this.dataInput); + await this.page.evaluate("return document.querySelectorAll('div.x-layer.x-combo-list[style*=\"visibility: visible\"] .x-combo-list-item:nth-child(10)')[0];").click(); + } + + /** + * Swap the axes of the chart + */ + async axisSwap() { + let axisFirstChildText = ''; + let axisSecondChildText = ''; + axisFirstChildText = await this.page.locator(this.chart.axisText()).textContent(); + await this.page.click(this.optionsButton); + await this.page.click(this.optionsSwap); + axisSecondChildText = await this.page.locator(this.chart.axisText()).textContent(); + await this.page.click(this.optionsButton); + await expect(axisFirstChildText[1]).not.toEqual(axisSecondChildText[1]); + } + + /** + * Update title of chart in options + */ + async chartTitleInOptionsUpdated() { + await this.page.click(this.optionsButton); + await expect(this.page.locator(this.optionsTitle)).toBeVisible(); + const optionsTitle1 = await this.page.locator(this.optionsTitle).textContent(); + await expect(typeof(optionsTitle1)).toEqual('string'); + const optionsTitle2 = await this.page.evaluate(function (text) { + // For now, EXT will be used + // eslint-disable-next-line no-undef + return Ext.util.Format.htmlDecode(text); + }, optionsTitle1); + await expect(optionsTitle2.value).toEqual(this.newTitle); + await this.page.click(this.optionsButton); + } + + /** + * Attempt to delete chart + */ + async attemptDeleteScratchpad() { + const title = this.page.locator(this.chart.title()).textContent(); + await expect(this.page.locator(this.toolbar.buttonByName('Delete'))).toBeVisible(); + await this.page.click(this.toolbar.buttonByName('Delete')); + await expect(this.page.locator(this.deleteChart.dialogBox)).toBeVisible(); + await this.page.click(this.deleteChart.buttonByLabel('Yes')); + await expect(this.page.locator(this.deleteChart.dialogBox)).toBeHidden(); + await this.collapseButtonLocator.waitFor({state:'visible'}); + await this.page.locator(this.toolbar.buttonByName('Load Chart')).waitFor({state:'visible'}); + await this.page.click(this.toolbar.buttonByName('Load Chart')); + await this.page.locator(this.load.dialog).waitFor({state:'visible'}); + await expect(this.page.locator(this.load.chartByName(title)).waitFor({state:'detached'})).toBeTruthy(); + } + + /** + * Load a chart based on given chart number + * + * @param {Number} chartNumber chart to load based on number + */ + async actionLoadChart(chartNumber) { + await this.page.click(this.load.button()); + await this.page.click(this.load.chartNum(chartNumber)); + } + + /** + * Add data from "Jobs", grouped by "CPU Hours: Per Job" + */ + async addDataViaToolbar() { + await this.page.click(this.addDataButton); + await this.page.locator(this.toolbar.addDataMenu).waitFor({state:'visible'}); + await this.page.click(this.toolbar.addData('Jobs')); + await this.page.locator(this.toolbar.groupByMenu).waitFor({state:'visible'}); + await this.page.click(this.toolbar.addDataGroupBy('CPU Hours: Per Job')); + await this.addDataSeriesByDefinition(); + } + + /** + * Add data from "CPU Hours: Total" under "Jobs" + */ + async genericStartingPoint() { + await this.page.click(this.addDataButton); + // Click on Jobs (5 on original site) + await this.page.click(this.buttonMenuFirstLevelChild); + // click on CPU Hours: Total + await this.page.click(this.addDataSecondLevelChild); + await this.addDataSeriesByDefinition(); + } + + /** + * Check if the chart title was correctly changed based on a given title + * + * @param {String} largeTitle name for chart title to match to + */ + async confirmChartTitleChange(largeTitle) { + const titleChange = await this.page.evaluate('return document.querySelector("' + this.chart.title() + '").textContent;'); + await expect(titleChange._status).toEqual(0); + await expect(typeof(titleChange.value)).toEqual('string'); + await expect(titleChange.value).toEqual(largeTitle); + } + + /** + * Switch chart options to aggregate + */ + async switchToAggregate() { + await this.page.click(this.optionsButton); + await this.page.click(this.optionsAggregate); + await this.clickLogo(); + await expect(this.page.locator(this.optionsAggregate)).toBeHidden(); + await expect(this.page.locator(this.mask)).toBeHidden(); + } + + /** + * Undo Aggregate option from chart + * + * @param {String} container selector for Metric Explorer page + */ + async undoAggregateOrTrendLine(container) { + await this.page.click(this.undo); + // The mouse stays and causes a hover, lets move the mouse somewhere else + await this.clickLogo(); + } + + /** + * Click on the first data point + */ + async clickFirstDataPoint() { + const elems = await this.page.locator(this.chart.seriesMarkers(0)); + // Data points are returned in reverse order. + // for some unknown reason the first point click gets intercepted by the series + // menu. + await elems.nth(0).click({force: true}); + // const num = await elems.count(); + // await elems.nth(num - 1).click({force: true}); + } + + /** + * Best effort to try to wait until the load mask has been and gone. + * Then click on a given selector + * + * @param {String} selector Selector to wait for and click + */ + async clickSelector(selector) { + await expect(this.page.locator(selector)).toBeVisible(); + await this.page.click(selector, {delay:500}); + } + + /** + * Click on the page's logo + */ + async clickLogo() { + await this.clickSelector(this.logo); + } +} +export default MetricExplorer; diff --git a/tests/playwright/lib/metricExplorer.selectors.ts b/tests/playwright/lib/metricExplorer.selectors.ts new file mode 100644 index 0000000000..5c3255e40e --- /dev/null +++ b/tests/playwright/lib/metricExplorer.selectors.ts @@ -0,0 +1,237 @@ +const selectors = { + mask: '.ext-el-mask', + logoutLink: '#logout_link', + logo: '.xtb-text.logo93', + tab: '#main_tab_panel__metric_explorer', + startDate: '//div[@id="metric_explorer"]//input[contains(@id,"start_field")]', + endDate: '//div[@id="metric_explorer"]//input[contains(@id,"end_field")]', + toolbar: { + toolbars: function () { + return selectors.container + ' .x-toolbar'; + }, + configureTime: { + frameButton: "(//div[@id='main_tab_panel']//div[@id='metric_explorer']//table[@class='x-toolbar-ct']/tbody/tr/td[@class='x-toolbar-left']/table/tbody/tr[@class='x-toolbar-left-row']//tbody[@class='x-btn-small x-btn-icon-small-left'])[1]", + UserDefinedSelect: '//div[@class="x-menu x-menu-floating x-layer x-menu-nosep"]//ul//li//a//span[text()="User Defined"]' + }, + buttonByName: function (name){ + return '//div[@id="metric_explorer"]//table[@class="x-toolbar-ct"]//button[text()="' + name + '"]/ancestor::node()[5]'; + }, + saveChanges:'//span[@class="x-menu-item-text" and contains(text(),"Save Changes")]', + addDataMenu: '//div[@id="metric-explorer-chartoptions-add-data-menu"]', + addData: function (name){ + return '//div[@id="metric-explorer-chartoptions-add-data-menu"]//span[contains(text(), "' + name + '")]'; + }, + groupByMenu: '//div[@class="x-menu x-menu-floating x-layer"]', + addDataGroupBy: function (groupBy){ + return "//div[contains(@class, 'x-menu')][contains(@class, 'x-menu-floating')][contains(@class, 'x-layer')][contains(@style, 'visibility: visible')]//span[contains(text(), '" + groupBy + "')]"; + }, + cannedDatePicker: function() { + return selectors.container + ' table[id^=canned_dates]'; + } + }, + container: '#metric_explorer', + load: { + button: function meLoadButtonId() { + return 'button=Load Chart'; + }, + firstSaved: '.x-menu-floating:not(.x-hide-offsets) .x-grid3-body .x-grid3-row-first', + chartNum: function meChartByIndex(number) { + const mynumber = number + 1; + return '.x-menu-floating:not(.x-hide-offsets) .x-grid3-body > div:nth-child(' + mynumber + ')'; + }, + dialog: '//div[contains(@class,"x-grid3-header-inner")]//div[contains(@class,"x-grid3-hd-name") and text() = "Chart Name"]/ancestor::node()[8]', + chartByName: function (name) { + return selectors.load.dialog + '//div[contains(@class,"x-grid3-cell-inner") and text() = "' + name + '"]'; + } + }, + newChart: { + topMenuByText: function (name) { + return '//div[@id="me_new_chart_menu"]//span[@class="x-menu-item-text" and text() = "' + name + '"]'; + }, + subMenuByText: function (topText, name) { + return '//div[@id="me_new_chart_submenu_' + topText + '"]//span[@class="x-menu-item-text" and contains(text(),"' + name + '")]'; + }, + modalDialog: { + box: '//span[@class="x-window-header-text" and text() = "New Chart"]/ancestor::node()[5]', + textBox: function () { + return selectors.newChart.modalDialog.box + '//input[contains(@class,"x-form-text")]'; + }, + checkBox: function () { + return selectors.newChart.modalDialog.box + '//input[contains(@class,"x-form-checkbox")]'; + }, + ok: function () { + return selectors.newChart.modalDialog.box + '//button[text() = "Ok"]'; + }, + cancel: function () { + return selectors.newChart.modalDialog.box + '//button[text() = "Cancel"]'; + }, + noDataMessage: '//div[@class="x-grid-empty"]/b[text()="No data is available for viewing"]' + } + }, + dataSeriesDefinition: { + dialogBox: '//div[contains(@class,"x-panel-header")]/span[@class="x-panel-header-text" and contains(text(),"Data Series Definition")]/ancestor::node()[4]', + header: function(){ + return selectors.dataSeriesDefinition.dialogBox + '//div[contains(@class, "x-panel-header")]'; + }, + addButton: '#adp_submit_button', + addFilter: function() { + return selectors.dataSeriesDefinition.dialogBox + '//button[contains(@class, "add_filter") and text() = "Add Filter"]'; + }, + filterButton: function() { + return selectors.dataSeriesDefinition.dialogBox + '//button[contains(@class, "filter") and contains(text(), "Filters")]'; + }, + box: '//div[contains(@class, "x-menu x-menu-floating") and contains(@style, "visibility: visible;")]//td[contains(@class, "x-grid3-check-col-td")]', + checkbox: '//div[contains(@class, "x-menu x-menu-floating") and contains(@style, "visibility: visible;")]//div[contains(@class, "x-grid3-check-col-on")]', + apply: '//div[contains(@class, "x-menu x-menu-floating") and contains(@style, "visibility: visible;")]//button[@class=" x-btn-text" and contains(text(), "Apply")]', + cancel: '//div[contains(@class, "x-menu x-menu-floating") and contains(@style,"visibility: visible;")]//button[@class=" x-btn-text" and contains(text(), "Cancel")]', + filter: function(filter) { + return `//div[contains(@class, "x-menu x-menu-floating") and contains(@style, "visibility: visible;")]//li/a//span[text()="${filter}"]`; + }, + name: function(name) { + return `//div[contains(@class, "x-menu x-menu-floating") and contains(@style, "visibility: visible;")]//div[contains(text(), "${name}")]`; + }, + ok: '//div[contains(@class, "x-menu x-menu-floating") and contains(@style, "visibility: visible;")]//button[@class=" x-btn-text" and contains(text(), "Ok")]', + }, + filterMenu: { + filterByDialogBox: '//div[contains(@class,"x-panel-header")]/span[@class="x-panel-header-text" and contains(text(),"Filter by")]/ancestor::node()[4]', + addFilterMenuOption: function(filter) { + return `//div[@id="metric-explorer-chartoptions-add-filter-menu"]//span[@class="x-menu-item-text" and text() = "${filter}"]`; + }, + selectedCheckboxes: function() { + return selectors.filterMenu.filterByDialogBox + '//div[@class="x-grid3-check-col x-grid3-cc-checked"]'; + }, + firstSelectedCheckbox: function() { + return '(' + selectors.filterMenu.selectedCheckboxes() + ')[1]'; + }, + okButton: function() { + return selectors.filterMenu.filterByDialogBox + '//button[@class=" x-btn-text" and contains(text(), "Ok")]'; + }, + }, + deleteChart: { + dialogBox: '//div[contains(@class,"x-window-header")]/span[@class="x-window-header-text" and contains(text(),"Delete Selected Chart")]/ancestor::node()[5]', + buttonByLabel: function (label) { + return selectors.deleteChart.dialogBox + '//button[text()="' + label + '"]'; + } + }, + addData: { + button: '.x-btn-text.add_data', + secondLevel: '.x-menu-floating:not(.x-hide-offsets):not(.x-menu-nosep)', + secondLevelChild: function () { + return selectors.addData.secondLevel + ' ul li:nth-child(3)'; + } + }, + data: { + button: 'button=Data', + container: '', + modal: { + updateButton: 'button=Update', + groupBy: { + input: 'input[name=dimension]' + } + } + }, + options: { + aggregate: '#aggregate_cb', + button: '#metric_explorer button.chartoptions', + menu: 'div.x-menu.x-menu-floating.x-layer.x-menu-nosep[style*="visibility: visible"]', + trendLine: '#me_trend_line', + swap: '#me_chart_swap_xy', + title: 'div.x-menu.x-menu-floating.x-layer.x-menu-nosep[style*="visibility: visible"] #me_chart_title' + }, + chart: { + svg: '//div[@id="metric_explorer"]//div[contains(@class, "plot-container")]//*[local-name() = "svg"]', + subtitle: function() { + return selectors.chart.svg + '/*[name()="g" and contains(@class, "infolayer")]//*[name()="g" and contains(@class, "annotation") and @data-index="1"]'; + }, + subtitleName: function(name) { + return selectors.chart.subtitle() + `//*[contains(text(), "${name}")]`; + }, + titleByText: function (title, zero=false) { + return selectors.chart.svg + '/*[name()="g" and contains(@class, "infolayer")]//*[name()="g" and contains(@class, "annotation") and @data-index="0"]//*[local-name() = "text" and contains(text(),"' + title +'")]'; + }, + credits: function () { + return selectors.chart.svg + '/*[name()="g" and contains(@class, "infolayer")]//*[name()="g" and contains(@class, "annotation") and @data-index="2"]//*[name()="text"and contains(text(),"Powered by XDMoD")]'; + }, + yAxisTitle: function () { + return selectors.chart.svg + '//*[name() = "g" and contains(@class, "g-ytitle")]/*[name() = "text" and contains(@class,"ytitle")]'; + }, + legend: function () { + return selectors.chart.svg + '//*[name() = "g" and contains(@class, "legend")]//*[name()="text" and contains(@class, "legendtext")]'; + }, + firstLegendContent: function () { + return '(' + selectors.chart.legend() + ')[1]'; + }, + legendContent: function(name){ + return selectors.chart.svg + `//*[name() = "text" and contains(@class, "legendtext") and contains(text(), "${name}")]`; + }, + seriesMarkers: function (seriesId) { + switch (seriesId) { + case 0: + return selectors.chart.svg + '/*[name()="g" and @class="cartesianlayer"]//*[name()="g" and @class="points"]//*[name()="path" and @class="point"]'; + default: + return selectors.chart.svg + '/*[name()="g" and contains(@class, "xy' + seriesId + '")]//*[name()="path" and @class="point"]'; + } + }, + title: function() { + return `(${selectors.chart.svg})[2]//*[name()="g" and @data-index="0" and contains(@class, "annotation")]//*[name()="text"]` + }, + titleInput: 'div.x-menu.x-menu-floating.x-layer.x-menu-nosep[style*="visibility: visible"] input[type=text]', + titleOkButton: 'div.x-menu.x-menu-floating.x-layer.x-menu-nosep[style*="visibility: visible"] table.x-btn.x-btn-noicon.x-box-item:first-child button', + titleCancelButton: 'div.x-menu.x-menu-floating.x-layer.x-menu-nosep[style*="visibili ty: visible"] table.x-btn.x-btn-noicon.x-box-item:last-child button', + contextMenu: { + menuByTitle: function (title) { + return '//div[contains(@class, "x-menu x-menu-floating") and contains(@style , "visibility: visible;")]//span[contains(@class, "menu-title") and contains(text(), "' + title + '" )]//ancestor::node()[4]/ul'; + }, + menuItemByText: function (menuTitle, itemText) { + return selectors.chart.contextMenu.menuByTitle(menuTitle) + '//li/a//span[text()="' + itemText + '"]'; + }, + container: '#metric-explorer-chartoptions-context-menu', + legend: '#metric-explorer-chartoptions-legend', + addData: '#metric-explorer-chartoptions-add-data', + addFilter: '#metric-explorer-chartoptions-add-filter' + }, + axis: '#metric_explorer .highcharts-yaxis-labels', + axisText: function () { + return selectors.chart.axis + ' text'; + } + }, + catalog: { + panel: '//div[@id="metric_explorer"]//div[contains(@class,"x-panel")]//span[text()="Metric Catalog"]/ancestor::node()[2]', + collapseButton: '//div[@id="metric_explorer"]//div[contains(@class,"x-tool-collapse-west")]', + expandButton: '//div[@id="metric_explorer"]//div[contains(@class,"x-panel")]//div[contains(@class,"x-tool-expand-west")]', + container: '#metric_explorer > div > .x-panel-body-noborder > .x-border-panel:not(.x-panel-noborder)', + tree: '#metric_explorer > div > .x-panel-body-noborder > .x-border-panel:not(.x-panel-noborder) .x-tree-root-ct', + rootNodeByName: function (name) { + return '//div[@id="metric_explorer"]//div[@class="x-tree-root-node"]/li/div[contains(@class,"x-tree-node-el")]//span[text() = "' + name + '"]'; + }, + nodeByPath: function (topname, childname) { + return selectors.catalog.rootNodeByName(topname) + '/ancestor::node()[3]//span[text() = "' + childname + '"]'; + }, + addToChartMenu: { + container: '//span[@class="x-menu-text"]/span[contains(text(),"Add To Chart:")]/ancestor::node()[3]', + itemByName: function (name) { + return selectors.catalog.addToChartMenu.container + '//span[@class="x-menu-item-text" and text() = "' + name + '"]'; + } + } + }, + buttonMenu: { + firstLevel: '.x-menu-floating:not(.x-hide-offsets)', + firstLevelChild: function () { + return selectors.buttonMenu.firstLevel + ' ul li:nth-child(3)'; + } + }, + filters: { + grid: '//div[@id="grid_filters_metric_explorer"]', + toolbar: { + byName: function (name) { + return '//div[@id="grid_filters_metric_explorer"]//div[contains(@class, "x-grid3-col-value_name") and contains(text(), "' + name + '")]/ancestor::node()[2]/td[contains(@class, "x-grid3-td-checked")]/div/div[contains(@class, "x-grid3-check-col-on")]'; + }, + apply: '//div[@id="grid_filters_metric_explorer"]//button[@class=" x-btn-text" and contains(text(), "Apply")]', + checkBox: '//div[@id="grid_filters_metric_explorer"]//div[contains(@class, "x-grid3-check-col-on")]', + firstCheckBox: '(//div[@id="grid_filters_metric_explorer"]//div[contains(@class, "x-grid3-check-col-on")])[1]', + cancel: '//div[@id="grid_filters_metric_explorer"]//button[@class=" x-btn-text" and contains(text(), "Cancel")]', + } + }, + undo: '(//div[@id="metric_explorer"]//button[contains(@class, "x-btn-text-icon")])[1]' +}; +export default selectors; diff --git a/tests/playwright/lib/mocha.selectors.ts b/tests/playwright/lib/mocha.selectors.ts new file mode 100644 index 0000000000..e382a98d58 --- /dev/null +++ b/tests/playwright/lib/mocha.selectors.ts @@ -0,0 +1,35 @@ +const selectors = { + index: `file:///xdmod/html/unit_tests/index.html`, + passes: '//div[@id="mocha"]//ul[@id="mocha-stats"]//li[@class="passes"]//em', + fails: '//div[@id="mocha"]//ul[@id="mocha-stats"]//li[@class="failures"]//em', + time: '//div[@id="mocha"]//ul[@id="mocha-stats"]//li[@class="duration"]//em', + taskNav: function(task) { + return `//div[@id="mocha"]//ul[@id="mocha-report"]//li[@class="suite"]//h2[text()="${task}"]//a`; + }, + tasksDisplayed: '//div[@id="mocha"]//ul[@id="mocha-report"]//li[@class="suite"]//ul//ul//li//h2', + messageBox:{ + window: '//div[contains(@class, "x-window x-window-plain")]', + button:{ + ok: function() { + return selectors.messageBox.window + '//button[text()="OK"]'; + }, + yes: function() { + return selectors.messageBox.window + '//button[text()="Yes"]'; + }, + no: function() { + return selectors.messageBox.window + '//button[text()="No"]'; + }, + cancel: function() { + return selectors.messageBox.window + '//button[text()="Cancel"]'; + } + } + }, + headers: { + navHeader: function(name) { + return `//ul[@id="mocha-report"]//li//h1//a[text()="${name}"]`; + }, + }, + codeBlocks: '//ul[@id="mocha-report"]//li//ul//ul//pre' +}; + +export default selectors; diff --git a/tests/playwright/lib/myProfile.page.ts b/tests/playwright/lib/myProfile.page.ts new file mode 100644 index 0000000000..213e6105b2 --- /dev/null +++ b/tests/playwright/lib/myProfile.page.ts @@ -0,0 +1,34 @@ +import {BasePage} from "./base.page"; +import selectors from './myProfile.selectors' + +class MyProfile extends BasePage{ + static readonly selectors = selectors; + + static readonly toolbarButton = this.selectors.buttons.toolbar; + static readonly container = this.selectors.container; + static readonly general = this.selectors.general; + static readonly tabs = this.selectors.tabs; + + /** + * Retrieve an XPath for a tab that contains the parameter text within the + * My Profile window. Values can be found within `this.names.tabs`. + * + * @param text {string} the text found within the tab to be returned. + * @returns {string} + */ + async tab(text) { + return MyProfile.tabs.byText(text); + } + + /** + * Retrieve an XPath for a button, identified by the name parameter, within + * the 'My Profile' window. Values for name provided by `this.names.buttons` + * + * @param name {string} + * @returns {string} + */ + async button(name) { + return MyProfile.general.generalName(name); + } +} +export default MyProfile; diff --git a/tests/playwright/lib/myProfile.selectors.ts b/tests/playwright/lib/myProfile.selectors.ts new file mode 100644 index 0000000000..4f93cffd37 --- /dev/null +++ b/tests/playwright/lib/myProfile.selectors.ts @@ -0,0 +1,65 @@ +const selectors ={ + container: '//div[@id="xdmod-profile-editor"]', + genSet : function () { + return selectors.container + '//div[@id="xdmod-profile-general-settings"]'; + }, + userInformation : function () { + return selectors.genSet() + '//div[contains(@class, "user_profile_section_general")]'; + }, + tabs: { + byText: function(text) { + return selectors.container + '//span[contains(@class, "x-tab-strip-text") and contains(text(),"'+ text + '")]'; + }, + general: 'General', + role_delegation: 'Role Delegation' + }, + general: { + generalName: function(name) { + return selectors.genSet() + '//button[contains(@class, "' + name + '")]'; + }, + user_information: { + top_role: function () { + return '//div[@id="user_profile_most_privileged_role"]'; + }, + first_name: function () { + return generalUserInformation('first_name'); + }, + last_name: function () { + return generalUserInformation('last_name'); + }, + email_address: function () { + return generalUserInformation('email_address'); + } + }, + update_password: { + update: 'user_profile_option_password_update', + password: 'new_password', + password_again: 'password_again' + } + }, + role_delegation: { + staff_member: 'staff_member' + }, + buttons: { + update: 'user_profile_btn_update', + close: 'general_btn_close', + toolbar: '#global-toolbar-profile' + } +} + +/** + * Retrieve an XPath for a control, identified by the name parameter, within + * the 'User Information' section of the 'General' tab. `names` are provided + * by `this.names.general.user_information`. + * + * @param name {string} + * @returns {string} + */ +function generalUserInformation(name) { + if (name == 'profile_editor_most_privileged_role') { + return selectors.userInformation() + '//span[@id="' + name + '"]'; + } + return selectors.userInformation() + '//input[@name="'+ name + '"]'; +} + +export default selectors; diff --git a/tests/playwright/lib/reportGenerator.page.ts b/tests/playwright/lib/reportGenerator.page.ts new file mode 100644 index 0000000000..cc2fb4e0e8 --- /dev/null +++ b/tests/playwright/lib/reportGenerator.page.ts @@ -0,0 +1,1577 @@ +/** + Report generator test classes. + */ +import artifacts from "../tests/helpers/artifacts"; +const expected = artifacts.getArtifact('reportGenerator'); +/** + * Helper function for creating XPath expression predicates to accurately + * determine if an element has a class. + * + * This prevents incorrect matching of class names where the desired class name + * is a substring of other class names. + * + * @param {String} className CSS class name. + * + * @return {String} XPath expression predicate. + */ +function classContains(className) { + return `contains(concat(" ",normalize-space(@class)," ")," ${className} ")`; +} + +import XDMoD from './xdmod.page'; +import {expect, Page} from '@playwright/test'; +import selectors from './reportGenerator.selectors' + +/** + * A single row in the list of reports. + */ +export class MyReportsRow { + readonly page: Page; + readonly name: string; + readonly derivedFrom: string; + readonly schedule: string; + readonly deliveryFormat: string; + readonly numberOfCharts: string; + readonly numberOfChartsPerPage: string; + readonly selector:string; + + /** + * @param {String} selector XPath selector for a "My Reports" row. + */ + constructor(selector: string, page: Page) { + this.page = page; + this.selector = selector; + this.name= selector + '//tr/td[position()=2]//div'; + this.derivedFrom= selector + '//tr/td[position()=3]//div'; + this.schedule= selector + '//tr/td[position()=4]//div'; + this.deliveryFormat= selector + '//tr/td[position()=5]//div[position()=2]'; + this.numberOfCharts = selector + '//tr/td[position()=6]//div'; + this.numberOfChartsPerPage = selector + '//tr/td[position()=6]//div/span' + } + + /** + * Get the name of the chart. + * + * @return {String} + */ + async getName() { + return this.page.textContent(this.name); + } + + /** + * Get the name of the template this report is derived from or "Manual" if + * the report was created manually.. + * + * @return {String} + */ + async getDerivedFrom() { + return this.page.textContent(this.derivedFrom); + } + + /** + * Get the schedule (frequency) of the report. + * + * @return {String} + */ + async getSchedule() { + return this.page.textContent(this.schedule); + } + + /** + * Get the delivery format (PDF or Word Document) of the report. + * + * @return {String} + */ + async getDeliveryFormat() { + return this.page.textContent(this.deliveryFormat); + } + + /** + * Get the number of charts in the report. + * + * @return {Number} + */ + async getNumberOfCharts() { + const text = await this.page.textContent(this.numberOfCharts); + return parseInt(text.trim(), 10); + } + + /** + * Get the number of charts per page in the report. + * + * @return {Number} + */ + async getNumberOfChartsPerPage() { + const chartsPerPage = await this.page.textContent(this.numberOfChartsPerPage); + const matches = chartsPerPage.match(/\((\d+) per page\)/); + if (matches === null) { + throw new Error(`Failed to determine number of charts from text "${chartsPerPage}"`); + } + return parseInt(matches[1], 10); + } + + /** + * Check if the row is selected. + * + * @return {Boolean} + */ + async isSelected() { + const att = await this.page.getAttribute(this.selector, 'class'); + return att.match(/(^| )x-grid3-row-selected($| )/) !== null; + } + + /** + * Click the row. + */ + async click() { + await this.page.click(this.selector); + } + + /** + * Double click the row. + */ + async doubleClick() { + await this.page.dblclick(this.selector); + } + + /** + * Toggle the row selection using Control+Click. + */ + async toggleSelection() { + await this.page.keyboard.down('Control'); + await this.page.click(this.selector); + await this.page.keyboard.up('Control'); + } +} + +/** + * A chart in the "Available Charts" list. + */ +export class AvailableChart { + readonly page:Page; + readonly selector:string; + readonly titleAndDrillDetails:string; + readonly dateDescription:string; + readonly timeframeType:string; + + /** + * @param {String} selector XPath selector for an "Available Chart". + */ + constructor(selector, page) { + this.page = page; + this.selector = selector; + const baseSelector = selector + '//tr/td[position()=2]/div/div'; + this.titleAndDrillDetails = baseSelector + '/div[position()=4]/span'; + this.dateDescription = baseSelector + '/div[position()=5]'; + this.timeframeType = baseSelector + '/div[position()=6]'; + } + + /** + * Get the combined title and drill-down details of the chart. + * + * Contains "
" between the title and drill details. + * + * @return {String} + */ + async getTitleAndDrillDetails() { + return this.page.innerHTML(this.titleAndDrillDetails); + } + + /** + * Get the title of the chart. + * + * @return {String} + */ + async getTitle() { + const first = await this.getTitleAndDrillDetails(); + return first.split('
')[0].trim(); + } + + /** + * Get the drill-down details of the chart. + * + * @return {String} + */ + async getDrillDetails() { + const first = await this.getTitleAndDrillDetails(); + const drillDetails = first.split('
')[1].trim(); + return drillDetails === ' ' ? '' : drillDetails; + } + + /** + * Get the date description of the chart. + * + * @return {String} + */ + async getDateDescription() { + return this.page.locator(this.dateDescription).textContent(); + } + + /** + * Get the timeframe of the chart. + * + * @return {String} + */ + async getTimeframeType() { + return this.page.locator(this.timeframeType).textContent(); + } + + /** + * Check if the chart is selected. + * + * @return {Boolean} + */ + async isSelected() { + const att = await this.page.getAttribute(this.selector, 'class'); + return att.match(/(^| )x-grid3-row-selected($| )/) !== null; + } + + /** + * Click the chart. + */ + async click() { + await this.page.click(this.selector); + } + + /** + * Toggle the chart selection using Control+Click. + */ + async toggleSelection() { + await this.page.keyboard.down('Control'); + await this.page.click(this.selector); + await this.page.keyboard.up('Control'); + } +} + +/** + * A chart in the "Included Charts" list. + */ +export class IncludedChart { + + readonly page:Page; + readonly selector: string; + readonly titleAndDrillDetails: string; + readonly dateDescription:string; + readonly timeframeEditIcon:string; + readonly timeframeType:string; + readonly timeframeResetIcon:string; + + /** + * @param {String} selector XPath selector for an "Included Chart". + */ + constructor(selector, page) { + this.page = page; + this.selector = selector; + const baseSelector = selector + '//tr/td[position()=2]/div/div'; + this.titleAndDrillDetails = baseSelector + '/div[position()=4]/span'; + this.dateDescription = baseSelector + '/div[position()=6]'; + this.timeframeEditIcon = baseSelector + '/div[position()=5]/a[position()=1]'; + this.timeframeType = baseSelector + '/div[position()=7]/span'; + this.timeframeResetIcon = baseSelector + '/div[position()=5]/a[position()=2]' + } + + /** + * Get the combined title and drill-down details of the chart. + * + * Contains "
" between the title and drill details. + * + * @return {String} + */ + async getTitleAndDrillDetails() { + return this.page.innerHTML(this.titleAndDrillDetails, false); + } + + /** + * Get the title of the chart. + * + * @return {String} + */ + async getTitle() { + const first = await this.getTitleAndDrillDetails(); + return first.split('
')[0].trim(); + } + + /** + * Get the drill-down details of the chart. + * + * @return {String} + */ + async getDrillDetails() { + const first = await this.getTitleAndDrillDetails(); + const drillDetails = first.split('
')[1].trim(); + return drillDetails === ' ' ? '' : drillDetails; + } + + /** + * Get the date description of the chart. + * + * @return {String} + */ + async getDateDescription() { + return this.page.locator(this.dateDescription).textContent(); + } + + /** + * Get the timeframe of the chart. + * + * @return {String} + */ + async getTimeframeType() { + return this.page.locator(this.timeframeType).textContent(); + } + + /** + * Check if the row is selected. + * + * @return {Boolean} + */ + async isSelected() { + const att = await this.page.getAttribute(this.selector, 'class'); + return att.match(/(^| )x-grid3-row-selected($| )/) !== null; + } + + /** + * Click the chart. + */ + async click() { + await this.page.click(this.selector); + } + + /** + * Click the date range to edit timeframe. + */ + async editTimeframe() { + await this.page.click(this.timeframeEditIcon); + } + + /** + * Click the timeframe reset icon. + */ + async resetTimeframe() { + await this.page.click(this.timeframeResetIcon); + } + + /** + * Toggle the chart selection using Control+Click. + */ + async toggleSelection() { + await this.page.keyboard.down('Control'); + await this.page.click(this.selector); + await this.page.keyboard.up('Control'); + } +} + +/** + * Report generator page. + */ +export class ReportGenerator { + readonly page:Page; + readonly xdmod:XDMoD; + readonly tabName:string; + readonly selectors = selectors; + + constructor(page:Page) { + const xdmod = new XDMoD(page, page.baseUrl); + this.page = page; + this.xdmod = xdmod; + this.tabName = 'Report Generator'; + } + + /** + * Determine if the report generator page is enabled. + * + * The report generator is considered to be enabled if the report + * generator tab exists. + * + * @return {Boolean} True if the report generator page is enabled. + */ + async isEnabled() { + return this.page.isVisible(this.selectors.tab()); + } + + /** + * Check if the "New Based On" button in the "My Reports" toolbar is + * enabled. + * + * There are two separate "New Based On" buttons. Only one should be + * visible at a time. This method returns true if the visible button is + * enabled. + * + * @return {Boolean} True if the button is enabled. + */ + async isNewBasedOnEnabled() { + const visibleButtons = await this.page.$$(this.selectors.myReports.toolbar.newBasedOnVisibleButton()); + await expect(visibleButtons.length, 'Two "New Based On" button are present').toEqual(2); + const firstButton = this.selectors.myReports.toolbar.numNewBasedOnVisibleButton(1); + const firstButtonClass = await this.page.getAttribute(firstButton, 'class'); + return firstButtonClass.match(/(^| )x-item-disabled($| )/) === null; + } + + /** + * Check if the "Edit" button in the "My Reports" toolbar is enabled. + * + * @return {Boolean} True if the button is enabled. + */ + async isEditSelectedReportsEnabled() { + const editButtonClass = await this.page.getAttribute(this.selectors.myReports.toolbar.editButtonInClass(), 'class'); + return editButtonClass.match(/(^| )x-item-disabled($| )/) === null; + } + + /** + * Check if the "Preview" button in the "My Reports" toolbar is enabled. + * + * @return {Boolean} True if the button is enabled. + */ + async isPreviewSelectedReportsEnabled() { + const previewButtonClass = await this.page.getAttribute(this.selectors.myReports.toolbar.previewButtonInClass(), 'class'); + return previewButtonClass.match(/(^| )x-item-disabled($| )/) === null; + } + + /** + * Check if the "Send Now" button in the "My Reports" toolbar is enabled. + * + * @return {Boolean} True if the button is enabled. + */ + async isSendSelectedReportsEnabled() { + // There are two separate "Send Now" buttons in the "My Reports" panel. + // Only one should be visible at a time. + const visibleButtons = await this.page.$$(this.selectors.myReports.toolbar.sendNowButtonInClass()); + await expect(visibleButtons.length, 'Two "Send Now" button are present').toEqual(2); + const firstSendButton = this.selectors.myReports.toolbar.firstSendNowButton(); + const firstSendButtonClass = await this.page.getAttribute(firstSendButton, 'class'); + return firstSendButtonClass.match(/(^| )x-item-disabled($| )/) === null; + } + + /** + * Check if the "Download" button in the "My Reports" toolbar is enabled. + * + * @return {Boolean} True if the button is enabled. + */ + async isDownloadSelectedReportsEnabled() { + const visibleButtons = await this.page.$$(this.selectors.myReports.toolbar.downloadButtonInClass()); + await expect(visibleButtons.length, 'Two "New Based On" button are present').toEqual(2); + const firstDownloadButton = this.selectors.myReports.toolbar.firstDownloadButton(); + const firstDownloadButtonClass = await this.page.getAttribute(firstDownloadButton, 'class'); + return firstDownloadButtonClass.match(/(^| )x-item-disabled($| )/) === null; + } + + /** + * Check if the "Delete" button in the "My Reports" toolbar is enabled. + * + * @return {Boolean} True if the button is enabled. + */ + async isDeleteSelectedReportsEnabled() { + const deleteButtonClass = await this.page.getAttribute(this.selectors.myReports.toolbar.deleteButtonInClass(), 'class'); + return deleteButtonClass.match(/(^| )x-item-disabled($| )/) === null; + } + + /** + * Wait for the "My Reports" panel to be visible. + */ + async waitForMyReportsPanelVisible() { + await this.page.isVisible(this.selectors.myReports.panel()); + await expect(this.page.locator(this.selectors.myReports.panel())).toBeVisible(); + } + + /** + * Wait for the "Report Preview" panel to be visible. + */ + async waitForReportPreviewPanelVisible() { + await this.page.isVisible(this.selectors.reportPreview.panel()); + } + + /** + * Wait for the "Report Editor" panel to be visible. + */ + async waitForReportEditorPanelVisible() { + await this.page.isVisible(this.selectors.reportEditor.panel()); + } + + /** + * Wait for the "Included Charts" panel to be visible. + */ + async waitForIncludedChartsPanelVisible() { + await this.waitForReportEditorPanelVisible(); + await this.page.isVisible(this.selectors.reportEditor.includedCharts.panel()); + } + + /** + * Wait for the "Available Charts" panel to be visible. + */ + async waitForAvailableChartsPanelVisible() { + await this.page.locator(this.selectors.availableCharts.panel()).waitFor({state:'visible'}); + } + + /** + * Wait for the "Message" window to be visible. + */ + async waitForMessageWindowVisible() { + await this.page.isVisible(this.selectors.message.window()); + } + + /** + * Wait for the "Delete Selected Report" window to be visible. + */ + async waitForDeleteSelectedReportsWindowVisible() { + await this.page.isVisible(this.selectors.deleteSelectedReports.window()); + } + + /** + * Wait for the "Delete Selected Report" window to not be visible. + */ + async waitForDeleteSelectedReportsWindowNotVisible() { + await this.page.isHidden(this.selectors.deleteSelectedReports.window()); + } + + /** + * Wait for the "Delete Selected Chart" window to be visible. + */ + async waitForDeleteSelectedChartsWindowVisible() { + await this.page.isVisible(this.selectors.deleteSelectedCharts.window()); + } + + /** + * Wait for the "Delete Selected Chart" window to not be visible. + */ + async waitForDeleteSelectedChartsWindowNotVisible() { + await this.page.isHidden(this.selectors.deleteSelectedCharts.window()); + } + + /** + * Wait for the "Remove Selected Chart" window to be visible. + */ + async waitForRemoveSelectedChartsWindowVisible() { + await this.page.isVisible(this.selectors.removeSelectedCharts.window()); + } + + /** + * Wait for the "Remove Selected Chart" window to not be visible. + */ + async waitForRemoveSelectedChartsWindowNotVisible() { + await this.page.isHidden(this.selectors.removeSelectedCharts.window()); + } + + /** + * Wait for the "Edit Chart Timeframe" window to be visible. + * + * Also waits for the error message to not be visible. + */ + async waitForEditChartTimeframeWindowVisible() { + await this.page.isVisible(this.selectors.editChartTimeframe.window()); + await this.page.isHidden(this.selectors.editChartTimeframe.errorMessage()); + } + + /** + * Wait for the "Save Report As" window to be visible. + */ + async waitForSaveReportAsWindowVisible() { + await this.page.isVisible(this.selectors.saveReportAs.window()); + } + + /** + * Wait for the "Report Built" window to be visible. + */ + async waitForReportBuiltWindowVisible() { + await this.page.isVisible(this.selectors.reportBuilt.window()); + } + + /** + * Wait for the "Report Built" window to not be visible. + */ + async waitForReportBuiltWindowNotVisible() { + await this.page.isHidden(this.selectors.reportBuilt.window()); + } + + /** + * Wait for at least one report to be visible on the page. + */ + async fullyLoaded() { + await this.page.locator(this.selectors.myReports.reportList.rowByIndex(1)).waitFor({state:'visible'}); + } + + /** + * Convenience method to convert the rows in the "My Reports" list into + * objects. + * + * The list of reports must be visible or this method will throw an + * exception. + * + * @return {MyReportsRow[]} + */ + async getMyReportsRows() { + await this.waitForMyReportsPanelVisible(); + const selector = this.selectors.myReports.reportList.rows(); + const computed = await this.page.$$(selector); + let result = await Promise.all(computed.map(async(element, i) => new MyReportsRow(`(${selector})[${i + 1}]`, this.page))); + return result; + } + + /** + * Convenience method to convert the charts in the "Included Charts" list + * into objects. + * + * The list of included charts must be visible or this method will throw an + * exception. + * + * @return {IncludedChart[]} + */ + async getIncludedCharts() { + await this.waitForIncludedChartsPanelVisible(); + const selector = this.selectors.reportEditor.includedCharts.chartList.rows(); + const computed = await this.page.$$(selector); + return await Promise.all(computed.map(async(element, i) => new IncludedChart(`(${selector})[${i + 1}]`, this.page))); + } + + /** + * Convenience method to convert the charts in the "Available Charts" list + * into objects. + * + * The list of available charts must be visible or this method will throw an + * exception. + * + * @return {IncludedChart[]} + */ + async getAvailableCharts() { + await this.waitForAvailableChartsPanelVisible(); + const selector = this.selectors.availableCharts.chartList.rows(); + let elemCount = this.page.$(selector).length; + let lastCount = -1; + while (elemCount !== lastCount) { + lastCount = elemCount; + await this.page.waitForTimeout(100); + elemCount = this.page.$(selector).length; + } + const computed = await this.page.$$(selector); + return await Promise.all(computed.map((element, i) => new AvailableChart(`(${selector})[${i + 1}]`, this.page))); + } + + /** + * Get the title of the "Message" window. + * + * @return {String} + */ + async getMessageWindowTitle() { + await this.waitForMessageWindowVisible(); + return this.page.locator(this.selectors.message.titleElement()).textContent(); + } + + /** + * Get the message text of the "Message" window. + * + * @return {String} + */ + async getMessage() { + await this.waitForMessageWindowVisible(); + return this.page.locator(this.selectors.message.textElement()).textContent(); + } + + /** + * Select the "Report Generator" tab by clicking it. + */ + async selectTab() { + await this.xdmod.selectTab('report_generator'); + await this.waitForMyReportsPanelVisible(); + } + + /** + * Select a report in the "My Reports" panel by clicking the row in the list + * of reports. + * + * @param {String} reportName The name of the report. + */ + async selectReportByName(reportName) { + await this.waitForMyReportsPanelVisible(); + + let foundReport = false; + + const rows = await this.getMyReportsRows(); + for (const row:MyReportsRow of rows){ + if (await row.getName() === reportName){ + await row.click(); + foundReport = true; + } + } + + if (!foundReport) { + throw new Error(`No report named "${reportName}" found`); + } + } + + /** + * Select all reports in the "My Reports" panel by clicking the "Select" + * button then clicking "All Reports". + */ + async selectAllReports() { + await this.waitForMyReportsPanelVisible(); + await this.page.click(this.selectors.myReports.toolbar.selectButton()); + await this.page.isVisible(this.selectors.myReports.toolbar.selectAllReportsButton()); + await this.page.click(this.selectors.myReports.toolbar.selectAllReportsButton()); + await this.page.isHidden(this.selectors.myReports.toolbar.selectMenu()); + // Ext.Button ignores clicks for 250ms + } + + /** + * Deselect all reports in the "My Reports" panel by clicking the "Select" + * button then clicking "No Reports". + */ + async deselectAllReports() { + await this.waitForMyReportsPanelVisible(); + const selectButLoc = await this.page.locator(this.selectors.myReports.toolbar.selectButton()); + await selectButLoc.click({delay:250}); + await expect(this.page.locator(this.selectors.myReports.toolbar.selectMenu())).toBeVisible(); + await this.page.click(this.selectors.myReports.toolbar.selectNoReportsButton()); + await this.page.locator(this.selectors.myReports.toolbar.selectMenu()).waitFor({state:'hidden'}); + // Ext.Button ignores clicks for 250ms + } + + /** + * Invert the report selection in the "My Reports" panel by clicking the + * "Select" button then clicking "Invert Selection". + */ + async invertReportSelection() { + await this.waitForMyReportsPanelVisible(); + const selectButLoc = await this.page.locator(this.selectors.myReports.toolbar.selectButton()); + await selectButLoc.click({delay:250}); + await expect(this.page.locator(this.selectors.myReports.toolbar.selectMenu())).toBeVisible(); + await this.page.isVisible(this.selectors.myReports.toolbar.invertSelectionButton()); + // Multiple buttons match the "Invert Selection" selector, but only one should be visible + // This is not the case because of the actions called before this method. + await this.page.click(this.selectors.myReports.toolbar.invertSelectionButton()); + await this.page.locator(this.selectors.myReports.toolbar.selectMenu()).waitFor({state:'hidden'}); + // Ext.Button ignores clicks for 250ms + } + + /** + * Create a new report from the "My Reports" panel by clicking the "New" + * button. + */ + async createNewReport() { + await this.waitForMyReportsPanelVisible(); + await this.page.click(this.selectors.myReports.toolbar.newButton()); + } + + /** + * Click the "New Based On" button. + * + * Must click the name of the report template separately. + */ + async clickNewBasedOn() { + await this.waitForMyReportsPanelVisible(); + // There are two separate "New Based On" buttons. Only one should be + // visible at a time. + const buttons = await this.page.$$(this.selectors.myReports.toolbar.newBasedOnVisibleButton()); + await expect(buttons.length, 'Two "New Based On" button are present').toEqual(2); + const button1 = await this.page.locator(this.selectors.myReports.toolbar.numNewBasedOnVisibleButton(1)); + const button2 = await this.page.locator(this.selectors.myReports.toolbar.numNewBasedOnVisibleButton(2)); + if (await button1.isVisible()) { + await button1.click(); + } else { + await button2.click(); + } + + // Ext.Button ignores clicks for 250ms + // Ext seems to also be "slow" to add events to the button + } + + /** + * Get the report template names. + * + * The list of report template names must be visible. + * + * @return {String[]} Report template names. + */ + async getReportTemplateNames() { + await this.page.isVisible(this.selectors.myReports.toolbar.newBasedOnTemplateRows()); + const computed = await this.page.locator(this.selectors.myReports.toolbar.newBasedOnTemplateRowsButton()); + await expect(computed).toBeVisible(); + return computed.allTextContents(); + } + + /** + * Select a template from the "New Based On" menu. + * + * Must click the "New Based On" button before selecting the template. + * + * @param {String} templateName The full name of the template. + * @param {String} center The name of the center to select [optional]. + */ + async selectNewBasedOnTemplate(templateName, center) { + const menuLoc = await this.page.locator(this.selectors.myReports.toolbar.newBasedOnMenu()); + await expect(menuLoc).toBeVisible(); + const reportRows = await this.getMyReportsRows(); + const reportCount = reportRows.length; + if (!center) { + await this.page.click(this.selectors.myReports.toolbar.newBasedOnTemplate(templateName)); + } else { + // move the mouse to the middle of the menu so that the center selection menu appears + await this.page.locator(this.selectors.myReports.toolbar.newBasedOnTemplate(templateName)).hover(); + + // wait for the new menu to be visible + await this.page.isVisible(this.selectors.myReports.toolbar.newBasedOnTemplateWithCenter(center)); + + // Select the option that corresponds with the center argument + await this.page.click(this.selectors.myReports.toolbar.newBasedOnTemplateWithCenter(center)); + } + // There is no visible indicator that the reports are being + // updated, so wait for the number of rows to increase + // specifically look for there to be at least one more item + // this seems to be do the the fact that elements and selectors get cached + await expect(this.page.locator(this.selectors.myReports.reportList.rowByIndex(reportCount + 1))).toBeVisible(); + } + + /** + * Select a report from the "New Based On" menu. + * + * Must select the same report in the "My Reports list (and no other + * reports) and click the "New Based On" button before selecting the report + * in the "New Based On" menu. + * + * @param {String} reportName The full name of the template. + */ + async selectNewBasedOnReport(reportName) { + await this.page.isVisible(this.selectors.myReports.toolbar.newBasedOnMenu()); + await this.page.click(this.selectors.myReports.toolbar.newBasedOnReport(reportName)); + } + + /** + * Edit the selected report in the "My Reports" panel by clicking the + * "Edit" button. + */ + async editSelectedReports() { + await this.waitForMyReportsPanelVisible(); + await this.page.click(this.selectors.myReports.toolbar.editButton()); + } + + /** + * Edit a report in the "My Reports" panel by double clicking the row for + * that report. + * + * @param {String} reportName Name of the report. + */ + async editReportByName(reportName) { + await this.waitForMyReportsPanelVisible(); + + let foundReport = false; + + const rows = await this.getMyReportsRows(); + for (const row of rows){ + if (await row.getName() === reportName){ + await row.dblclick(); + foundReport = true; + } + } + + if (!foundReport) { + throw new Error(`No report named "${reportName}" found`); + } + } + + /** + * Preview the selected report in the "My Reports" panel by clicking the + * "Preview" button. + */ + async previewSelectedReports() { + await this.waitForMyReportsPanelVisible(); + await this.page.click(this.selectors.myReports.toolbar.previewButton()); + } + + /** + * Return to the "My Reports" panel from the "Report Preview" by clicking + * the "Return To Reports Overview" button. + */ + async returnToReportsOverview() { + await this.waitForReportPreviewPanelVisible(); + await this.page.click(this.selectors.reportPreview.toolbar.returnToReportsOverviewButton()); + } + + /** + * Click the "Send Now" button in the "My Reports" panel. + * + * It may be necessary to click the "As PDF" or "As Word Document" option + * after clicking this button. + */ + async sendSelectedReportsNow() { + await this.waitForMyReportsPanelVisible(); + // There are two separate "Send Now" buttons in the "My Reports" panel. + // Only one should be visible at a time. + const visibleButtons = this.page.$$(this.selectors.myReports.toolbar.sendNowButtonInClass()).filter(button => button.isVisible()); + await expect(visibleButtons.length, 'One "Send Now" button is visible').toEqual(1); + await visibleButtons[0].click(); + } + + /** + * Select the "As PDF" option from the "Send Now" menu in the "My Reports" + * panel. + */ + async sendSelectedReportsAsPdfNow() { + await this.page.isVisible(this.selectors.myReports.toolbar.sendNowAsPdfButton()); + await this.page.click(this.selectors.myReports.toolbar.sendNowAsPdfButton()); + } + + /** + * Select the "As Word Document" option from the "Send Now" menu in the "My Reports" + * panel. + */ + async sendSelectedReportsAsWordDocumentNow() { + await this.page.isVisible(this.selectors.myReports.toolbar.sendNowAsWordDocumentButton()); + await this.page.click(this.selectors.myReports.toolbar.sendNowAsWordDocumentButton()); + } + + /** + * Click the "Download" button in the "My Reports" panel. + * + * It may be necessary to click the "As PDF" or "As Word Document" option + * after clicking this button. + */ + async downloadSelectedReports() { + await this.waitForMyReportsPanelVisible(); + await this.page.click(this.selectors.myReports.toolbar.downloadButton()); + } + + /** + * Select the "As PDF" option from the "Download" menu in the "My Reports" + * panel. + */ + async downloadSelectedReportsAsPdf() { + await this.page.isVisible(this.selectors.myReports.toolbar.downloadAsPdfButton()); + await this.page.click(this.selectors.myReports.toolbar.downloadAsPdfButton()); + // Wait for check mark image to appear and disappear. + await this.page.isVisible(this.selectors.checkmarkMask()); + await this.page.isHidden(this.selectors.checkmarkMask()); + } + + /** + * Select the "As Word Document" option from the "Download" menu in the + * "My Reports" panel. + */ + async downloadSelectedReportsAsWordDocument() { + await this.page.isVisible(this.selectors.myReports.toolbar.downloadAsWordDocumentButton()); + await this.page.click(this.selectors.myReports.toolbar.downloadAsWordDocumentButton()); + // Wait for check mark image to appear and disappear. + await this.page.isVisible(this.selectors.checkmarkMask()); + await this.page.isHidden(this.selectors.checkmarkMask()); + } + + /** + * Close the "Report Built" window. + */ + async closeReportBuiltWindow() { + await this.waitForReportBuiltWindowVisible(); + await this.page.click(this.selectors.reportBuilt.closeButton()); + await this.waitForReportBuiltWindowNotVisible(); + } + + /** + * Delete selected reports from "My Reports" by clicking the delete button. + * + * Does not confirm deletion of report, that button must be clicked + * separately. + */ + async deleteSelectedReports() { + await this.waitForMyReportsPanelVisible(); + await this.page.click(this.selectors.myReports.toolbar.deleteButton()); + } + + /** + * Click the "Yes" button in the "Delete Selected Report" window. + */ + async confirmDeleteSelectedReports() { + const rows = await this.getMyReportsRows(); + const reportCount = rows.length; + await this.waitForDeleteSelectedReportsWindowVisible(); + await this.page.click(this.selectors.deleteSelectedReports.yesButton()); + await this.waitForDeleteSelectedReportsWindowNotVisible(); + // There is no visible indicator that the reports are being + // updated, so wait for the number of rows to change. + for (let i = 0; i < 100; i++){ + try { + const second = await this.getMyReportsRows(); + const reportCount2 = second.length; + await expect(reportCount2 !== reportCount).toBeTruthy(); + break; + } catch (e) { + //in loading phase of deleting reports + } + } + } + + /** + * Click the "Yes" button in the "Delete Selected Report" window. + */ + async cancelDeleteSelectedReports() { + await this.waitForDeleteSelectedReportsWindowVisible(); + await this.page.click(this.selectors.deleteSelectedReports.noButton()); + await this.waitForDeleteSelectedReportsWindowNotVisible(); + } + + /** + * Save the report currently being edited. + */ + async saveReport() { + await this.waitForReportEditorPanelVisible(); + await this.page.click(this.selectors.reportEditor.toolbar.saveButton()); + await this.page.isVisible(this.selectors.message.window()); + await expect(await this.getMessageWindowTitle(), 'Message window title is correct').toEqual('Report Editor'); + await this.page.isHidden(this.selectors.message.window()); + } + + /** + * Save a report with a different name. + * + * Does not confirm saving the report. + * + * @param {String} reportName The new name to give the report (optional). + */ + async saveReportAs(reportName = undefined) { + await this.waitForReportEditorPanelVisible(); + await this.page.click(this.selectors.reportEditor.toolbar.saveAsButton()); + await this.waitForSaveReportAsWindowVisible(); + if (reportName !== undefined) { + /* + * There is a "timing" issue when setting the value of the input box + * and then clicking on save to quickly, this forces it to wait for + * the invalid input to not be present + */ + await this.page.locator(this.selectors.saveReportAs.reportNameInput()).fill(reportName); + await this.page.isHidden(this.selectors.saveReportAs.reportNameInputInvalid()); + } + } + + /** + * Confirm saving a report by clicking the "Save" button. + * + * @param {Boolean} expectError True, if an error is expected + * (defaults to false). If no error is expected then this method + * does not return until the confirmation message is no longer + * visible. + */ + async confirmSaveReportAs(expectError = false) { + await this.waitForSaveReportAsWindowVisible(); + await this.page.click(this.selectors.saveReportAs.saveButton()); + + if (!expectError) { + await expect(await this.getMessageWindowTitle(), 'Message window title is correct').toEqual('Report Editor'); + await expect(await this.getMessage(), 'Message is correct').toEqual('Report successfully saved as a copy'); + await this.page.isHidden(this.selectors.message.window()); + } + } + + /** + * Close "Save Report As" window by clicking the "Close" button. + */ + async closeSaveReportAs() { + await this.waitForSaveReportAsWindowVisible(); + await this.page.click(this.selectors.saveReportAs.closeButton()); + } + + /** + * Preview the report currently being edited. + */ + async previewCurrentReport() { + await this.waitForReportEditorPanelVisible(); + await this.page.click(this.selectors.reportEditor.toolbar.previewButton()); + } + + /** + * Click the "Send Now" button in the "Report Editor" panel. + * + * It may be necessary to click the "As PDF" or "As Word Document" option + * after clicking this button. + */ + async sendCurrentlyBeingEditedReportNow() { + await this.waitForReportEditorPanelVisible(); + await this.page.click(this.selectors.reportEditor.toolbar.sendNowButton()); + } + + /** + * Select the "As PDF" option from the "Send Now" menu in the + * "Report Editor" panel. + */ + async sendCurrentlyBeingEditedReportAsPdfNow() { + await this.waitForReportEditorPanelVisible(); + await this.page.isVisible(this.selectors.myReports.toolbar.sendNowAsPdfButton()); + await this.page.click(this.selectors.myReports.toolbar.sendNowAsPdfButton()); + } + + /** + * Select the "As PDF" option from the "Send Now" menu in the + * "Report Editor" panel. + */ + async sendCurrentlyBeingEditedReportAsWordDocumentNow() { + await this.waitForReportEditorPanelVisible(); + await this.page.isVisible(this.selectors.myReports.toolbar.sendNowAsWordDocumentButton()); + await this.page.click(this.selectors.myReports.toolbar.sendNowAsWordDocumentButton()); + } + + /** + * Click the "Download" button in the "Report Editor" panel. + * + * It may be necessary to click the "As PDF" or "As Word Document" option + * after clicking this button. + */ + async downloadCurrentlyBeingEditedReport() { + await this.waitForReportEditorPanelVisible(); + await this.page.click(this.selectors.reportEditor.toolbar.downloadButton()); + } + + /** + * Select the "As PDF" option from the "Download" menu in the + * "Report Editor" panel. + */ + async downloadCurrentlyBeingEditedReportAsPdf() { + await this.page.isVisible(this.selectors.myReports.toolbar.downloadAsPdfButton()); + await this.page.click(this.selectors.myReports.toolbar.downloadAsPdfButton()); + } + + /** + * Select the "As Word Document" option from the "Download" menu in the + * "Report Editor" panel. + */ + async downloadCurrentlyBeingEditedReportAsWordDocument() { + await this.page.isVisible(this.selectors.myReports.toolbar.downloadAsWordDocumentButton()); + await this.page.click(this.selectors.myReports.toolbar.downloadAsWordDocumentButton()); + } + + /** + * Return to "My Reports" by clicking the "Return to My Reports" button. + */ + async returnToMyReports() { + await this.waitForReportEditorPanelVisible(); + await this.page.click(this.selectors.reportEditor.toolbar.returnToMyReportsButton()); + } + + /** + * Get the name of the report currently being edited. + * + * @return {String} + */ + async getReportName() { + await this.waitForReportEditorPanelVisible(); + return this.page.locator(this.selectors.reportEditor.generalInformation.reportNameInput()).inputValue(); + } + + /** + * Set the name of the report currently being edited. + * + * @param {String} name + */ + async setReportName(name) { + await this.waitForReportEditorPanelVisible(); + await this.page.locator(this.selectors.reportEditor.generalInformation.reportNameInput()).fill(name); + } + + /** + * Get the title of the report currently being edited. + * + * @return {String} + */ + async getReportTitle() { + await this.waitForReportEditorPanelVisible(); + return this.page.locator(this.selectors.reportEditor.generalInformation.reportTitleInput()).inputValue(); + } + + /** + * Set the title of the report currently being edited. + * + * @param {String} reportTitle + */ + async setReportTitle(reportTitle) { + await this.waitForReportEditorPanelVisible(); + await this.page.locator(this.selectors.reportEditor.generalInformation.reportTitleInput()).fill(reportTitle); + } + + /** + * Get the header text of the report currently being edited. + * + * @return {String} + */ + async getHeaderText() { + await this.waitForReportEditorPanelVisible(); + return this.page.locator(this.selectors.reportEditor.generalInformation.headerTextInput()).inputValue(); + } + + /** + * Set the header text of the report currently being edited. + * + * @param {String} headerText + */ + async setHeaderText(headerText) { + await this.waitForReportEditorPanelVisible(); + await this.page.locator(this.selectors.reportEditor.generalInformation.headerTextInput()).fill(headerText); + } + + /** + * Get the footer text of the report currently being edited. + * + * @return {String} + */ + async getFooterText() { + await this.waitForReportEditorPanelVisible(); + return this.page.locator(this.selectors.reportEditor.generalInformation.footerTextInput()).inputValue(); + } + + /** + * Set the footer text of the report currently being edited. + * + * @param {String} footerText + */ + async setFooterText(footerText) { + await this.waitForReportEditorPanelVisible(); + await this.page.locator(this.selectors.reportEditor.generalInformation.footerTextInput()).fill(footerText); + } + + /** + * Get the number of charts per page for the report currently being edited. + * + * @return {Number} + */ + async getNumberOfChartsPerPage() { + await this.waitForReportEditorPanelVisible(); + if (this.page.isChecked(this.selectors.reportEditor.chartLayout.twoChartsPerPageRadioButton())) { + return 1; + } else if (this.page.isChecked(this.selectors.reportEditor.chartLayout.oneChartPerPageRadioButton())) { + return 2; + } + + throw new Error('No charts per page option selected'); + } + + /** + * Set the number of charts per page for the report currently being edited. + * + * @param {Number} chartsPerPage Must be 1 or 2. + */ + async setNumberOfChartsPerPage(chartsPerPage) { + await this.waitForReportEditorPanelVisible(); + if (chartsPerPage === 1) { + await this.page.click(this.selectors.reportEditor.chartLayout.oneChartPerPageRadioButton()); + } else if (chartsPerPage === 2) { + await this.page.click(this.selectors.reportEditor.chartLayout.twoChartsPerPageRadioButton()); + } else { + throw new Error(`Invalid number of charts per page: "${chartsPerPage}"`); + } + } + + /** + * Get the schedule (delivery frequency) of the report currently being + * edited. + * + * @return {String} + */ + async getSchedule() { + await this.waitForReportEditorPanelVisible(); + return this.page.locator(this.selectors.reportEditor.scheduling.scheduleInput()).inputValue(); + } + + /** + * Set the schedule (delivery frequency) of the report currently being + * edited. + * + * @param {String} frequency + */ + async setSchedule(frequency) { + await this.waitForReportEditorPanelVisible(); + await this.page.click(this.selectors.reportEditor.scheduling.scheduleInput()); + await this.page.isVisible(this.selectors.reportEditor.scheduling.scheduleOption(frequency)); + await this.page.click(this.selectors.reportEditor.scheduling.scheduleOption(frequency)); + } + + /** + * Get the delivery format ('PDF" or "Word Document') of the report + * currently being edited. + * + * @return {String} + */ + async getDeliveryFormat() { + await this.waitForReportEditorPanelVisible(); + return this.page.locator(this.selectors.reportEditor.scheduling.deliveryFormatInput()).inputValue(); + } + + /** + * Set the delivery format ('PDF" or "Word Document') of the report + * currently being edited. + * + * @param {String} format + */ + async setDeliveryFormat(format) { + await this.waitForReportEditorPanelVisible(); + await this.page.click(this.selectors.reportEditor.scheduling.deliveryFormatInput()); + await this.page.isVisible(this.selectors.reportEditor.scheduling.deliveryFormatOption(format)); + await this.page.click(this.selectors.reportEditor.scheduling.deliveryFormatOption(format)); + } + + /** + * Select all charts in the "Included Charts" panel by clicking the "Select" + * button then clicking "All Charts". + */ + async selectAllIncludedCharts() { + await this.waitForIncludedChartsPanelVisible(); + await this.page.click(this.selectors.reportEditor.includedCharts.toolbar.selectButton()); + await this.page.isVisible(this.selectors.reportEditor.includedCharts.toolbar.selectAllChartsButton()); + await this.page.click(this.selectors.reportEditor.includedCharts.toolbar.selectAllChartsButton()); + await this.page.isHidden(this.selectors.reportEditor.includedCharts.toolbar.selectAllChartsButton()); + // Ext.Button ignores clicks for 250ms + } + + /** + * Deselect all charts in the "Included Charts" panel by clicking the + * "Select" button then clicking "No Charts". + */ + async deselectAllIncludedCharts() { + await this.waitForIncludedChartsPanelVisible(); + const selectButLoc = await this.page.locator(this.selectors.reportEditor.includedCharts.toolbar.selectButton()); + await selectButLoc.click({delay:250}); + await this.page.isVisible(this.selectors.myReports.toolbar.selectMenu()); + await expect (this.page.locator(this.selectors.myReports.toolbar.selectMenu())).toBeVisible(); + await this.page.click(this.selectors.reportEditor.includedCharts.toolbar.selectNoChartsButton()); + await this.page.locator(this.selectors.reportEditor.includedCharts.toolbar.selectNoChartsButton()).waitFor({state:'hidden'}); + // Ext.Button ignores clicks for 250ms + } + + /** + * Invert the chart selection in the "Included Charts" panel by clicking the + * "Select" button then clicking "Invert Selection". + */ + async invertIncludedChartsSelection() { + await this.waitForIncludedChartsPanelVisible(); + await this.page.click(this.selectors.reportEditor.includedCharts.toolbar.selectButton()); + await this.page.isVisible(this.selectors.reportEditor.includedCharts.toolbar.invertSelectionButton()); + // Multiple buttons match the "Invert Selection" selector, but only one should be visible. + const visibleButtons = this.page.$$(this.selectors.myReports.toolbar.invertSelectionButton()).filter(button => button.isVisible()); + await expect(visibleButtons.length, 'One "Invert Selection" button is visible').toEqual(1); + await visibleButtons[0].click(); + await this.page.isHidden(this.selectors.reportEditor.includedCharts.toolbar.invertSelectionButton()); + // Ext.Button ignores clicks for 250ms + } + + /** + * Click the "Edit Timeframe of Selected Charts" button in the "Included + * Charts" panel. + */ + async editTimeframeOfSelectedCharts() { + await this.page.click(this.selectors.reportEditor.includedCharts.toolbar.editTimeframeButton()); + } + + /** + * Click the "Specific" radio button in the "Edit Chart Timeframe" window. + */ + async selectSpecificChartTimeframe() { + await this.waitForEditChartTimeframeWindowVisible(); + await this.page.click(this.selectors.editChartTimeframe.specificRadioButton()); + } + + /** + * Set the start date in the "Edit Chart Timeframe" window. + * + * @param {String} startDate + */ + async setSpecificChartTimeframeStartDate(startDate) { + await this.waitForEditChartTimeframeWindowVisible(); + await this.page.locator(this.selectors.editChartTimeframe.startDateInput()).fill(startDate); + } + + /** + * Set the end date in the "Edit Chart Timeframe" window. + * + * @param {String} endDate + */ + async setSpecificChartTimeframeEndDate(endDate) { + await this.waitForEditChartTimeframeWindowVisible(); + await this.page.locator(this.selectors.editChartTimeframe.endDateInput()).fill(endDate); + } + + /** + * Click the "Periodic" radio button in the "Edit Chart Timeframe" window + * and pick a duration. + * + * @param {String} duration Name of the periodic duration. + */ + async selectPeriodicChartTimeframe(duration) { + await this.waitForEditChartTimeframeWindowVisible(); + await this.page.click(this.selectors.editChartTimeframe.periodicRadioButton()); + await this.page.isVisible(this.selectors.editChartTimeframe.periodicInput()); + await this.page.click(this.selectors.editChartTimeframe.periodicInput()); + await this.page.isVisible(this.selectors.editChartTimeframe.periodicOption(duration)); + await this.page.click(this.selectors.editChartTimeframe.periodicOption(duration)); + await this.page.isHidden(this.selectors.editChartTimeframe.periodicOption(duration)); + } + + /** + * Click the "Cancel" button in the "Edit Chart Timeframe" window. + */ + async cancelEditTimeframeOfSelectedCharts() { + await this.waitForEditChartTimeframeWindowVisible(); + await this.page.click(this.selectors.editChartTimeframe.cancelButton()); + } + + /** + * Click the "Update" button in the "Edit Chart Timeframe" window. + */ + async confirmEditTimeframeOfSelectedCharts() { + await this.waitForEditChartTimeframeWindowVisible(); + await this.page.click(this.selectors.editChartTimeframe.updateButton()); + } + + /** + * Get the error message displayed in the "Edit Chart Timeframe" window. + * + * This method must be called while the error message is still visible (or + * directly before the message appears) or it will fail. + * + * @return {String} The text of the error message. + */ + async getEditChartTimeframeErrorMessage() { + await this.page.isVisible(this.selectors.editChartTimeframe.errorMessage()); + return this.page.textContent(this.selectors.editChartTimeframe.errorMessage()); + } + + /** + * Remove selected charts from the "Included Charts" panel by clicking the + * "Remove" button. + */ + async removeSelectedIncludedCharts() { + await this.waitForIncludedChartsPanelVisible(); + await this.page.click(this.selectors.myReports.toolbar.deleteButton()); + } + + /** + * Click the "No" button in the "Remove Selected Chart" window. + */ + async cancelRemoveSelectedIncludedCharts() { + await this.waitForRemoveSelectedChartsWindowVisible(); + await this.page.click(this.selectors.removeSelectedCharts.noButton()); + await this.waitForRemoveSelectedChartsWindowNotVisible(); + } + + /** + * Click the "Yes" button in the "Remove Selected Chart" window. + */ + async confirmRemoveSelectedIncludedCharts() { + await this.waitForRemoveSelectedChartsWindowVisible(); + const first = await this.getIncludedCharts(); + const chartCount = first.length; + await this.page.click(this.selectors.removeSelectedCharts.yesButton()); + await this.waitForRemoveSelectedChartsWindowNotVisible(); + // There is no visible indicator that the charts are being + // updated, so wait for the number of rows to change. + await this.page.waitForFunction(async () => chartCount !== (await this.getIncludedCharts()).length); + } + + /** + * Select all charts in the "Available Charts" panel by clicking the + * "Select" button then clicking "All Charts". + */ + async selectAllAvailableCharts() { + await this.waitForAvailableChartsPanelVisible(); + await this.page.click(this.selectors.availableCharts.toolbar.selectButton()); + await this.page.isVisible(this.selectors.availableCharts.toolbar.selectAllChartsButton()); + await this.page.click(this.selectors.availableCharts.toolbar.selectAllChartsButton()); + await this.page.isHidden(this.selectors.availableCharts.toolbar.selectAllChartsButton()); + // Ext.Button ignores clicks for 250ms + } + + /** + * Deselect all charts in the "Available Charts" panel by clicking the + * "Select" button then clicking "No Charts". + */ + async deselectAllAvailableCharts() { + await this.waitForAvailableChartsPanelVisible(); + const selectButLoc = await this.page.locator(this.selectors.availableCharts.toolbar.selectButton()); + await selectButLoc.click({delay:250}); + await expect(this.page.locator(this.selectors.myReports.toolbar.selectMenu())).toBeVisible(); + await this.page.click(this.selectors.availableCharts.toolbar.selectNoChartsButton()); + await this.page.locator(this.selectors.availableCharts.toolbar.selectNoChartsButton()).waitFor({state:'hidden'}); + // Ext.Button ignores clicks for 250ms + } + + /** + * Invert the chart selection in the "Available Charts" panel by clicking + * the "Select" button then clicking "Invert Selection". + */ + async invertAvailableChartsSelection() { + await this.waitForAvailableChartsPanelVisible(); + const selectButLoc = await this.page.locator(this.selectors.availableCharts.toolbar.selectButton()); + await selectButLoc.click({delay:250}); + await expect(this.page.locator(this.selectors.myReports.toolbar.selectMenu())).toBeVisible(); + await this.page.isVisible(this.selectors.availableCharts.toolbar.invertSelectionButton()); + // Multiple buttons match the "Invert Selection" selector, but only one should be visible. + // This is not the case anymore because of the actions done before this method. + await this.page.click(this.selectors.availableCharts.toolbar.invertSelectionButton()); + await this.page.locator(this.selectors.availableCharts.toolbar.invertSelectionButton()).waitFor({state:'hidden'}); + // Ext.Button ignores clicks for 250ms + } + + /** + * Delete selected charts from "Available Charts" by clicking the + * delete button. + * + * Does not confirm deletion of carts, that button must be clicked + * separately. + * + */ + async deleteSelectedAvailableCharts() { + await this.waitForAvailableChartsPanelVisible(); + await this.page.click(this.selectors.availableCharts.toolbar.deleteButton()); + } + + /** + * Click the "Yes" button in the "Delete Selected Charts" window. + */ + async confirmDeleteSelectedAvailableCharts() { + await this.waitForDeleteSelectedChartsWindowVisible(); + const availableCharts = await this.getAvailableCharts(); + const chartCount = availableCharts.length; + await this.page.click(this.selectors.deleteSelectedCharts.yesButton()); + // There is no visible indicator that the charts are being + // updated, so wait for the number of rows to change. + for (let i = 0; i < 100; i++) { + try { + const second = await this.getAvailableCharts(); + const chartCount2 = second.length; + await expect(chartCount2 !== chartCount).toBeTruthy(); + break; + } catch (e) { + //in loading phase of deleting reports + } + } + } + + /** + * Click the "No" button in the "Delete Selected Charts" window. + */ + async cancelDeleteSelectedAvailableCharts() { + await this.waitForDeleteSelectedChartsWindowVisible(); + await this.page.click(this.selectors.deleteSelectedCharts.noButton()); + } + + /** + * Add a chart to the report that is currently being edited. + * + * @param {Number} index The 0-based index of the chart in the list of + * available charts. + */ + async addChartToReport(index) { + await this.waitForAvailableChartsPanelVisible(); + await this.waitForIncludedChartsPanelVisible(); + const charts = await this.getAvailableCharts(); + const first = await this.getIncludedCharts(); + const includedChartCountBefore = first.length; + await expect(index, 'Index is valid').toBeLessThanOrEqual(charts.length); + await this.page.dragAndDrop(this.selectors.availableCharts.chartList.rows() + `[${index + 1}]`, this.selectors.reportEditor.includedCharts.chartList.panel()); + await this.page.isVisible(this.selectors.reportEditor.includedCharts.chartList.rows() + `[${includedChartCountBefore + 1}]`); + } + + async getCharts(user, report_template_index, options) { + let charts = expected[user].report_templates[report_template_index].charts; + await charts.forEach(function (chart, i) { + if (chart.startDate in options) { + charts[i].startDate = options[chart.startDate]; + } + if (chart.endDate in options) { + charts[i].endDate = options[chart.endDate]; + } + }); + return charts; + } +} +export default {MyReportsRow, AvailableChart, IncludedChart, ReportGenerator}; diff --git a/tests/playwright/lib/reportGenerator.selectors.ts b/tests/playwright/lib/reportGenerator.selectors.ts new file mode 100644 index 0000000000..d1d0f79226 --- /dev/null +++ b/tests/playwright/lib/reportGenerator.selectors.ts @@ -0,0 +1,196 @@ +const selectors = { + tab: () => `//div[${classContains('x-tab-panel-header')}]//span[${classContains('x-tab-strip-text')} and text()='Report Generator']`, + panel: () => '//div[@id="report_generator"]', + mask: () => `//div[${classContains('ext-el-mask')}]`, + configureTime: { + frameButton: "(//div[@id='main_tab_panel']//div[@id='tg_usage']//table[@class='x-toolbar-ct']/tbody/tr/td[@class='x-toolbar-left']/table/tbody/tr[@class='x-toolbar-left-row']//tbody[@class='x-btn-small x-btn-icon-small-left'])[1]", + byTimeFrameName: function(name) { + return '//div[@class="x-menu x-menu-floating x-layer x-menu-nosep"]//ul//li//a//span[text()="' + name + '"]'; + } + }, + myReports: { + panel: () => selectors.panel() + `//div[${classContains('report_overview')}]`, + toolbar: { + panel: () => selectors.myReports.panel() + `//div[${classContains('x-panel-tbar')}]`, + selectButton: () => selectors.myReports.toolbar.panel() + '//button[text()="Select"]', + selectMenu: () => `//div[${classContains('x-menu-floating')} and contains(@style, "visibility: visible")]`, + selectAllReportsButton: () => selectors.myReports.toolbar.selectMenu() + '//span[text()="All Reports"]/ancestor::a', + selectNoReportsButton: () => selectors.myReports.toolbar.selectMenu() + '//span[text()="No Reports"]/ancestor::a', + invertSelectionButton: () => selectors.myReports.toolbar.selectMenu() + '//span[text()="Invert Selection"]/ancestor::a', + newButton: () => selectors.myReports.toolbar.panel() + '//button[text()="New"]', + newBasedOnButton: () => selectors.myReports.toolbar.panel() + '//button[text()="New Based On"]', + newBasedOnVisibleButton: () => selectors.myReports.toolbar.newBasedOnButton() + `/ancestor::table[${classContains('x-btn')}]`, + numNewBasedOnVisibleButton: (num) => '(' + selectors.myReports.toolbar.newBasedOnVisibleButton() + ')[' + num + ']', + newBasedOnMenu: () => `//div[${classContains('x-menu-floating')} and .//img[${classContains('btn_selected_report')} or ${classContains('btn_report_template')}]]`, + newBasedOnRows: () => selectors.myReports.toolbar.newBasedOnMenu() + `//li[not(${classContains('x-menu-sep-li')})]`, + newBasedOnTemplateRows: () => selectors.myReports.toolbar.newBasedOnMenu() + `//li[.//img[${classContains('btn_report_template')}]]`, + newBasedOnTemplateRowsButton: () => selectors.myReports.toolbar.newBasedOnTemplateRows() + `//a[./img[${classContains('btn_report_template')}]]//b`, + newBasedOnReportRows: () => selectors.myReports.toolbar.newBasedOnMenu() + `//li[.//img[${classContains('btn_selected_report')}]]`, + newBasedOnTemplate: name => selectors.myReports.toolbar.newBasedOnTemplateRows() + `//a[.//b[text()="${name}"]]`, + newBasedOnTemplateWithCenter: center => `//div[${classContains('x-menu-floating')}]//a[.//img[${classContains('btn_resource_provider')}] and .//span[contains(text(), "${center}")]]`, + newBasedOnReport: name => selectors.myReports.toolbar.newBasedOnReportRows() + `//a[./img[.//b[text()="${name}"]]`, + editButton: () => selectors.myReports.toolbar.panel() + '//button[text()="Edit"]', + editButtonInClass: () => selectors.myReports.toolbar.editButton() + `/ancestor::table[${classContains('x-btn')}]`, + previewButton: () => selectors.myReports.toolbar.panel() + '//button[text()="Preview"]', + previewButtonInClass: () => selectors.myReports.toolbar.previewButton() + `/ancestor::table[${classContains('x-btn')}]`, + sendNowButton: () => selectors.myReports.toolbar.panel() + '//button[text()="Send Now"]', + sendNowButtonInClass: () => selectors.myReports.toolbar.sendNowButton() + `/ancestor::table[${classContains('x-btn')}]`, + firstSendNowButton: () => '(' + selectors.myReports.toolbar.sendNowButton() + `/ancestor::table[${classContains('x-btn')}]` + ')[1]', + sendNowAsPdfButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="As PDF"]/ancestor::a`, + sendNowAsWordDocumentButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="As Word Document"]/ancestor::a`, + downloadButton: () => selectors.myReports.toolbar.panel() + '//button[text()="Download"]', + downloadButtonInClass: () => selectors.myReports.toolbar.downloadButton() + `/ancestor::table[${classContains('x-btn')}]`, + firstDownloadButton: () => '(' + selectors.myReports.toolbar.downloadButton() + `/ancestor::table[${classContains('x-btn')}]` + ')[1]', + downloadAsPdfButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="As PDF"]/ancestor::a`, + downloadAsWordDocumentButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="As Word Document"]/ancestor::a`, + deleteButton: () => selectors.myReports.toolbar.panel() + '//button[text()="Delete"]', + deleteButtonInClass: () => selectors.myReports.toolbar.deleteButton() + `/ancestor::table[${classContains('x-btn')}]` + }, + reportList: { + panel: () => selectors.myReports.panel() + `//div[${classContains('x-panel-body-noheader')}]`, + rows: () => selectors.myReports.reportList.panel() + `//div[${classContains('x-grid3-row')}]`, + rowByIndex: index => selectors.myReports.reportList.panel() + `//div[${classContains('x-grid3-row')} and position()=${index}]` + } + }, + reportPreview: { + panel: () => selectors.panel() + `//div[${classContains('report_preview')}]`, + toolbar: { + panel: () => selectors.reportPreview.panel() + `//div[${classContains('x-panel-tbar')}]`, + sendNowButton: () => selectors.reportPreview.toolbar.panel() + '//button[text()="Send Now"]', + downloadButton: () => selectors.reportPreview.toolbar.panel() + '//button[text()="Download"]', + returnToReportsOverviewButton: () => selectors.reportPreview.toolbar.panel() + `//button[${classContains('btn_return_to_previous')}]` + } + }, + reportEditor: { + panel: () => selectors.panel() + `//div[${classContains('report_edit')}]`, + toolbar: { + panel: () => selectors.reportEditor.panel() + `//div[${classContains('x-panel-tbar')} and .//button[text()="Save"]]`, + saveButton: () => selectors.reportEditor.toolbar.panel() + '//button[text()="Save"]', + saveAsButton: () => selectors.reportEditor.toolbar.panel() + '//button[text()="Save As"]', + previewButton: () => selectors.reportEditor.toolbar.panel() + '//button[text()="Preview"]', + sendNowButton: () => selectors.reportEditor.toolbar.panel() + '//button[text()="Send Now"]', + downloadButton: () => selectors.reportEditor.toolbar.panel() + '//button[text()="Download"]', + returnToMyReportsButton: () => selectors.reportEditor.toolbar.panel() + `//button[${classContains('btn_return_to_overview')}]` + }, + generalInformation: { + panel: () => selectors.reportEditor.panel() + `//div[${classContains('x-panel')} and .//span[text()="General Information"]]`, + reportNameInput: () => selectors.reportEditor.generalInformation.panel() + '//input[@name="report_name"]', + reportTitleInput: () => selectors.reportEditor.generalInformation.panel() + '//input[@name="report_title"]', + headerTextInput: () => selectors.reportEditor.generalInformation.panel() + '//input[@name="report_header"]', + footerTextInput: () => selectors.reportEditor.generalInformation.panel() + '//input[@name="report_footer"]' + }, + chartLayout: { + panel: () => selectors.reportEditor.panel() + `//div[${classContains('x-panel')} and .//span[text()="Chart Layout"]]`, + oneChartPerPageRadioButton: () => selectors.reportEditor.chartLayout.panel() + '//input[@value="1_up"]', + twoChartsPerPageRadioButton: () => selectors.reportEditor.chartLayout.panel() + '//input[@value="2_up"]' + }, + scheduling: { + panel: () => selectors.reportEditor.panel() + `//div[${classContains('x-panel')} and .//span[text()="Scheduling"]]`, + scheduleInput: () => selectors.reportEditor.scheduling.panel() + '//input[@name="report_generator_report_schedule"]', + scheduleOption: name => `//div[${classContains('x-combo-list-item')} and text()="${name}"]`, + deliveryFormatInput: () => selectors.reportEditor.scheduling.panel() + '//div[./label[text()="Delivery Format:"]]//input', + deliveryFormatOption: name => `//div[${classContains('x-combo-list-item')} and text()="${name}"]` + }, + includedCharts: { + panel: () => selectors.reportEditor.panel() + '//div[@id="ReportCreatorGrid"]', + toolbar: { + panel: () => selectors.reportEditor.includedCharts.panel() + `//div[${classContains('x-panel-tbar')}]`, + selectButton: () => selectors.reportEditor.includedCharts.toolbar.panel() + '//button[text()="Select"]', + selectAllChartsButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="All Charts"]/ancestor::a`, + selectNoChartsButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="No Charts"]/ancestor::a`, + invertSelectionButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="Invert Selection"]/ancestor::a`, + editTimeframeButton: () => selectors.reportEditor.includedCharts.toolbar.panel() + '//button[text()="Edit Timeframe of Selected Charts"]', + removeButton: () => selectors.reportEditor.includedCharts.toolbar.panel() + '//button[text()="Remove"]' + }, + chartList: { + panel: () => selectors.reportEditor.includedCharts.panel() + '//div[@class="x-panel-body" and .//div[text()="Chart"]]', + rows: () => selectors.reportEditor.includedCharts.chartList.panel() + `//div[${classContains('x-grid3-row')}]` + } + } + }, + availableCharts: { + panel: () => selectors.panel() + '//div[@id="chart_pool_panel"]', + toolbar: { + panel: () => selectors.availableCharts.panel() + '//div[@class="x-panel-tbar"]', + selectButton: () => selectors.availableCharts.toolbar.panel() + '//button[text()="Select"]', + selectAllChartsButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="All Charts"]/ancestor::a`, + selectNoChartsButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="No Charts"]/ancestor::a`, + invertSelectionButton: () => `//div[${classContains('x-menu-floating')}]//a[.//span[text()="Invert Selection"]]`, + deleteButton: () => selectors.availableCharts.toolbar.panel() + '//button[text()="Delete"]' + }, + chartList: { + panel: () => selectors.availableCharts.panel() + '//div[@class="x-panel-body" and .//div[text()="Chart"]]', + rows: () => selectors.availableCharts.chartList.panel() + `//div[${classContains('x-grid3-row')}]` + } + }, + message: { + window: () => '//div[@id="report_generator_message"]', + titleElement: () => selectors.message.window() + `//span[${classContains('x-window-header-text')}]`, + textElement: () => selectors.message.window() + '//b' + }, + deleteSelectedReports: { + window: () => `//div[${classContains('x-window')} and .//span[text()="Delete Selected Report" or text()="Delete Selected Reports"]]`, + yesButton: () => selectors.deleteSelectedReports.window() + '//button[text()="Yes"]', + noButton: () => selectors.deleteSelectedReports.window() + '//button[text()="No"]' + }, + unsavedChanges: { + window: () => `//div[${classContains('x-window')} and .//span[text()="Unsaved Changes"]]`, + yesButton: () => selectors.unsavedChanges.window() + '//button[text()="Yes"]', + noButton: () => selectors.unsavedChanges.window() + '//button[text()="No"]', + cancelButton: () => selectors.unsavedChanges.window() + '//button[text()="Cancel"]' + }, + deleteSelectedCharts: { + window: () => `//div[${classContains('x-window')} and .//span[text()="Delete Selected Chart" or text()="Delete Selected Charts"]]`, + yesButton: () => selectors.deleteSelectedCharts.window() + '//button[text()="Yes"]', + noButton: () => selectors.deleteSelectedCharts.window() + '//button[text()="No"]' + }, + removeSelectedCharts: { + window: () => `//div[${classContains('x-window')} and .//span[text()="Remove Selected Chart" or text()="Remove Selected Charts"]]`, + yesButton: () => selectors.removeSelectedCharts.window() + '//button[text()="Yes"]', + noButton: () => selectors.removeSelectedCharts.window() + '//button[text()="No"]' + }, + saveReportAs: { + window: () => `//div[${classContains('x-window')} and .//span[text()="Save Report As"]]`, + reportNameInput: () => selectors.saveReportAs.window() + '//input[@name="report_name"]', + saveButton: () => selectors.saveReportAs.window() + '//button[text()="Save"]', + closeButton: () => selectors.saveReportAs.window() + '//button[text()="Close"]', + reportNameInputInvalid: () => selectors.saveReportAs.window() + `//input[@name="report_name" and ${classContains('x-form-invalid')}]` + }, + reportBuilt: { + window: () => `//div[${classContains('x-window')} and .//span[text()="Report Built"]]`, + viewReportButton: () => selectors.reportBuilt.window() + '//button[text()="View Report"]', + closeButton: () => selectors.reportBuilt.window() + `//div[${classContains('x-tool-close')}]` + }, + editChartTimeframe: { + window: () => `//div[${classContains('chart_date_editor')}]`, + specificRadioButton: () => selectors.editChartTimeframe.window() + '//input[@name="report_creator_chart_entry" and @value="Specific"]', + periodicRadioButton: () => selectors.editChartTimeframe.window() + '//input[@name="report_creator_chart_entry" and @value="Periodic"]', + periodicInput: () => selectors.editChartTimeframe.window() + '//table[contains(@class,"menu")]//button', + periodicOption: name => `//div[${classContains('x-menu-floating')}]//a[starts-with(text(),"${name}')]`, + startDateInput: () => selectors.editChartTimeframe.window() + '//input[@id="report_generator_edit_date_start_date_field"]', + endDateInput: () => selectors.editChartTimeframe.window() + '//input[@id="report_generator_edit_date_end_date_field"]', + updateButton: () => selectors.editChartTimeframe.window() + `//button[${classContains('chart_date_editor_update_button')}]`, + cancelButton: () => selectors.editChartTimeframe.window() + `//button[${classContains('chart_date_editor_cancel_button')}]`, + errorMessage: () => selectors.editChartTimeframe.window() + `//div[${classContains('overlay_message')}]` + }, + // The mask with the check mark image that is displayed after a report is ready for download or has been emailed. + checkmarkMask: () => `//div[${classContains('ext-el-mask-msg')} and .//img[@src="gui/images/checkmark.png"]]`, + reportDisplay: '//div[@class="x-panel report_overview x-hide-display"]' +}; + +/** + * Helper function for creating XPath expression predicates to accurately + * determine if an element has a class. + * + * This prevents incorrect matching of class names where the desired class name + * is a substring of other class names. + * + * @param {String} className CSS class name. + * + * @return {String} XPath expression predicate. + */ +function classContains(className) { + return `contains(concat(" ",normalize-space(@class)," ")," ${className} ")`; +} + +export default selectors; diff --git a/tests/playwright/lib/usageTab.page.ts b/tests/playwright/lib/usageTab.page.ts new file mode 100644 index 0000000000..e3b5ba1569 --- /dev/null +++ b/tests/playwright/lib/usageTab.page.ts @@ -0,0 +1,188 @@ +import {expect} from '@playwright/test'; +import XDMoD from './xdmod.page'; +import {BasePage} from "./base.page"; +import selectors from './usageTab.selectors' + +class Usage extends BasePage{ + readonly selectors = selectors; + + readonly legendTextLocator = this.page.locator(selectors.legendText()); + readonly chartLocator = this.page.locator(selectors.chart); + readonly maskLocator = this.page.locator(selectors.mask); + readonly durationButtonLocator = this.page.locator(selectors.durationButton()); + readonly durationMenuLocator = this.page.locator(selectors.durationMenu); + readonly startFieldLocator = this.page.locator(selectors.startField); + readonly endFieldLocator = this.page.locator(selectors.endField); + readonly refreshButtonLocator = this.page.locator(selectors.refreshButton); + readonly availableForReportCheckboxLocator = this.page.locator(selectors.availableForReportCheckbox); + + async checkLegendText(text){ + await expect(this.legendTextLocator).toBeVisible(); + await expect(this.legendTextLocator).toContainText(text); + } + + /** + * Select the "Usage" tab by clicking it. + */ + async selectTab(){ + const xdmod = new XDMoD(this.page, this.page.baseUrl); + await xdmod.selectTab('tg_usage'); + await expect(this.chartLocator).toBeVisible(); + await expect(this.maskLocator).toBeHidden(); + } + + /** + * Select a duration from the list of preset options. + * + * @param {String} name The name of the duration preset. + */ + async selectDuration(name){ + await this.durationButtonLocator.click(); + await expect(this.durationMenuLocator).toBeVisible(); + await this.page.locator(selectors.durationMenuItem(name)).click(); + await expect(this.maskLocator).toBeHidden(); + + // The chart automatically refreshes after a new duration is + // selected, but the menu remains open. Clicking the refresh + // button will close the menu. + await this.refresh(); + } + + /** + * Set the start date. + * + * @param {String} date Start date. + */ + async setStartDate(date:string){ + await this.startFieldLocator.fill(date); + } + + /** + * Set the end date. + * + * @param {String} date End date. + */ + async setEndDate(date:string){ + await this.endFieldLocator.fill(date); + } + + /** + * Refresh current chart by clicking the "Refresh" button. + */ + async refresh(){ + await this.refreshButtonLocator.click(); + await this.maskLocator.waitFor({state:"detached"}); + } + + /** + * Make the current chart available for use in the report generator by + * clicking the "Available for Report" checkbox. + * + * Preconditions: + * - The "Available for Report" checkbox is visible, enabled and not checked. + * + * Postconditions: + * - The "Available for Report" checkbox is checked. + */ + async makeCurrentChartAvailableForReport(){ + await expect(this.availableForReportCheckboxLocator.isVisible(), '"Available for Report" checkbox is visible').toBeTruthy(); + await expect(this.availableForReportCheckboxLocator.isEnabled(), '"Available for Report" checkbox is enabled').toBeTruthy(); + const checkbox = await this.page.$eval(selectors.availableForReportCheckbox, node => node.checked); + await expect(checkbox, '"Available for Report" checkbox is not checked').toBeFalsy(); + await this.availableForReportCheckboxLocator.click(); + await expect(this.availableForReportCheckboxLocator.isChecked(), '"Available for Report" checkbox is checked').toBeTruthy(); + } + + /** + * Remove the current chart from the report generator by clicking the + * "Available for Report" checkbox. + * + * Preconditions: + * - The "Available for Report" checkbox is visible, enabled and checked. + * + * Postconditions: + * - The "Available for Report" checkbox is not checked. + */ + async makeCurrentChartUnavailableForReport(){ + await expect(this.availableForReportCheckboxLocator.isVisible(), '"Available for Report" checkbox is visible').toBeTruthy(); + await expect(this.availableForReportCheckboxLocator.isEnabled(), '"Available for Report" checkbox is enabled').toBeTruthy(); + await expect(this.availableForReportCheckboxLocator.isChecked(), '"Available for Report" checkbox is checked').toBeTruthy(); + await this.availableForReportCheckboxLocator.click(); + const checkbox = await this.page.$eval(selectors.availableForReportCheckbox, node => node.checked); + await expect(checkbox, '"Available for Report" checkbox is not checked').toBeFalsy(); + } + + /** + * Check if a top-level tree node is expanded. + * + * @param {String} name The name of the tree node. + * + * @return {Boolean} True if the node is expanded. + */ + async isTreeNodeExpanded(name){ + const unfoldTreeSelector = selectors.unfoldTreeNodeByName(name); + const unfoldTreeClass = await this.page.getAttribute(unfoldTreeSelector, 'class'); + return unfoldTreeClass.match(/[$ ]x-tree-node-plus[^ ]/) === null; + } + + /** + * Expand a top-level node in the metrics tree by clicking the + * plus/minus icon. + * + * @param {String} name The name of the tree node. + */ + async expandTreeNode(name){ + await expect(this.isTreeNodeExpanded(name), 'Tree node is collpased').toBeFalsy(); + await this.page.locator(selectors.unfoldTreeNodeByName(name)).click(); + } + + /** + * Collapse a top-level node in the metrics tree by clicking the + * plus/minus icon. + * + * @param {String} name The name of the tree node. + */ + async collapseTreeNode(name){ + await expect(this.isTreeNodeExpanded(name), 'Tree node is expanded').toBeTruthy(); + await this.page.locator(selectors.unfoldTreeNodeByName(name)).click(); + } + + /** + * Select a top-level tree node. + * + * @param {String} name The name of the tree node. + */ + async selectTreeNode(name){ + await this.page.locator(selectors.topTreeNodeByName(name)).click(); + await expect(this.maskLocator).toBeHidden(); + } + + /** + * Select a child node in the metrics tree by clicking. + * + * @param {String} topName The name of the top-level tree node. + * @param {String} childName The name of the child tree node. + */ + async selectChildTreeNode(topName, childName){ + const check = await this.isTreeNodeExpanded(topName); + if (!check){ + await this.expandTreeNode(topName); + } + await this.page.locator(selectors.treeNodeByPath(topName, childName)).click(); + } + + /** + * Check if the menu item element that contains the text in `display` is enabled. + * + * @param display + * @returns {boolean} + */ + async toolbarMenuItemIsEnabled(display){ + const item = selectors.displayMenuItemByText(display); + await this.page.locator(item).isVisible(); + const itemClass = await this.page.getAttribute(item, 'class'); + const itemIsDisabled = itemClass.includes('x-item-disabled'); + return !(itemIsDisabled); + } +} +export default Usage; diff --git a/tests/playwright/lib/usageTab.selectors.ts b/tests/playwright/lib/usageTab.selectors.ts new file mode 100644 index 0000000000..b0dc050ab8 --- /dev/null +++ b/tests/playwright/lib/usageTab.selectors.ts @@ -0,0 +1,48 @@ +const selectors ={ + tab : '#main_tab_panel__tg_usage', + startField : '//div[@id="tg_usage"]//table[@class="x-toolbar-ct"]//input[contains(@id,"start_field_ext")]', + endField : '//div[@id="tg_usage"]//table[@class="x-toolbar-ct"]//input[contains(@id,"end_field_ext")]', + legendText : function() { + return selectors.chart0 + '/*[name()="g" and contains(@class, "infolayer")]/*[name()="g" and @class="legend"]' + }, + refreshButton : '//div[@id="tg_usage"]//table[contains(@id, "refresh_button")]', + toolbar : { + exportButton: '//div[@id="tg_usage"]//button[text()="Export"]/ancestor::node()[5]' + }, + panel : '//div[@id="tg_usage"]', + availableForReportCheckbox : '//div[@id="tg_usage"]//label[text()="Available For Report"]/parent::node()/input[@type="checkbox"]', + mask : '.ext-el-mask', + topTreeNodeByName : function (name) { + return '//div[@id="tg_usage"]//div[@class="x-tree-root-node"]/li/div[contains(@class,"x-tree-node-el")]//span[text() = "' + name + '"]'; + }, + treeNodeByPath : function (topname, childname) { + return selectors.topTreeNodeByName(topname) + '/ancestor::node()[3]//span[text() = "' + childname + '"]'; + }, + unfoldTreeNodeByName : function (name) { + return selectors.topTreeNodeByName(name) + '/ancestor::node()[2]/img[contains(@class,"x-tree-ec-icon")]'; + }, + chart : '//div[@id="tg_usage"]//div[contains(@class, "plot-container")]//*[local-name() = "svg"][1]', + chart0: '//div[@id="tg_usage"]//div[contains(@class, "plot-container")]//*[local-name() = "svg"]', + chartByTitle : function (title, zero=false) { + let chart = zero ? selectors.chart0 : selectors.chart; + return chart + '/*[name()="g" and contains(@class, "infolayer")]//*[name()="g" and contains(@class, "annotation") and @data-index="0"]//*[local-name() = "text" and contains(text(),"' + title +'")]'; + }, + chartXAxisLabelByName : function (name) { + return '(' + selectors.chart + '//*[name()="g" and @class="xaxislayer-below"]/*[name()="g" and @class="xtick"])[1]'; + }, + durationButton : function(){ + return selectors.panel + '//button[contains(@class,"custom_date")]'; + }, + durationMenu : '//div[contains(@class,"x-menu-floating")]', + durationMenuItem : function(name){ + return `${selectors.durationMenu}//li/a[./span[text()="${name}"]]`; + }, + toolbarButtonByText : function (text) { + return `//div[contains(@class, "x-toolbar")]//button[contains(text(), "${text}")]`; + }, + displayMenuItemByText : function (text) { + return `//div[@id='chart_config_menu_chart_toolbar_tg_usage']//span[contains(text(), '${text}')]//ancestor::li[contains(@class, 'x-menu-list-item')]`; + }, + signInLink: '#sign_in_link', +} +export default selectors; diff --git a/tests/playwright/lib/xdmod.page.ts b/tests/playwright/lib/xdmod.page.ts new file mode 100644 index 0000000000..4a4247924a --- /dev/null +++ b/tests/playwright/lib/xdmod.page.ts @@ -0,0 +1,18 @@ +import {expect} from '@playwright/test'; +import {BasePage} from "./base.page"; +import selectors from "./xdmod.selectors"; + +class XDMoD extends BasePage{ + readonly selectors = selectors; + readonly maskLocator = this.page.locator(selectors.mask); + + async selectTab(tabId:string){ + const tabLocator = this.page.locator(this.selectors.tab(tabId)); + const panel = this.page.locator(this.selectors.panel(tabId)); + + await tabLocator.click(); + await panel.waitFor({state:'visible'}); + await expect(this.maskLocator).toBeHidden(); + } +} +export default XDMoD; diff --git a/tests/playwright/lib/xdmod.selectors.ts b/tests/playwright/lib/xdmod.selectors.ts new file mode 100644 index 0000000000..048632913a --- /dev/null +++ b/tests/playwright/lib/xdmod.selectors.ts @@ -0,0 +1,48 @@ +const selectors ={ + mask: '.ext-el-mask', + tab: function(tabId) { + return '//div[contains(@class, "x-tab-strip-wrap")]//li[@id="main_tab_panel__' + tabId + '"]'; + }, + panel: function(tabId) { + return '//div[@id="' + tabId + '"]'; + }, + exportDialog: { + window: '//body/div[contains(@class,"x-window")]//span[contains(@class, "x-window-header-text") and text() = "Export"]/ancestor::node()[5]', + cancelButton: function () { + return selectors.exportDialog.window + '//button[contains(text(), "Cancel")]'; + }, + formElement: function (formElementName) { + return selectors.exportDialog.window + '//input[@name="' + formElementName + '"]'; + }, + dropDown: function (formElementName) { + return selectors.exportDialog.formElement(formElementName) + '/../img[contains(@class,"x-form-arrow-trigger")]'; + }, + formatDropdown: function () { + return selectors.exportDialog.dropDown('format_type'); + }, + showTitleCheckbox: function () { + return selectors.exportDialog.formElement('show_title'); + }, + imageSizeDropdown: function () { + return selectors.exportDialog.dropDown('image_size'); + }, + widthInput: function () { + return selectors.exportDialog.formElement('width_inches'); + }, + heightInput: function () { + return selectors.exportDialog.formElement('height_inches'); + }, + fontInput: function () { + return selectors.exportDialog.formElement('font_pt'); + }, + comboList: '//div[contains(@class, "x-combo-list")]', + comboListItemByNum: function(num) { + return '(' + selectors.exportDialog.comboList + ')[' + num + ']'; + }, + comboListItems: '//div[contains(@style, "visibility: visible")]//div[contains(@class, "x-combo-list-item")]', + comboListItemByName: function (name) { + return '//div[contains(@style, "visibility: visible")]//div[contains(@class, "x-combo-list-item") and contains(text(), "' + name + '")]'; + } + } +} +export default selectors; diff --git a/tests/playwright/package-lock.json b/tests/playwright/package-lock.json new file mode 100644 index 0000000000..9f0b0b8ea7 --- /dev/null +++ b/tests/playwright/package-lock.json @@ -0,0 +1,30 @@ +{ + "name": "playwright", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@playwright/test": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.22.2.tgz", + "integrity": "sha512-cCl96BEBGPtptFz7C2FOSN3PrTnJ3rPpENe+gYCMx4GNNDlN4tmo2D89y13feGKTMMAIVrXfSQ/UmaQKLy1XLA==", + "dev": true, + "requires": { + "@types/node": "*", + "playwright-core": "1.22.2" + } + }, + "@types/node": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.35.tgz", + "integrity": "sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg==", + "dev": true + }, + "playwright-core": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.22.2.tgz", + "integrity": "sha512-w/hc/Ld0RM4pmsNeE6aL/fPNWw8BWit2tg+TfqJ3+p59c6s3B6C8mXvXrIPmfQEobkcFDc+4KirNzOQ+uBSP1Q==", + "dev": true + } + } +} diff --git a/tests/playwright/package.json b/tests/playwright/package.json new file mode 100644 index 0000000000..a78e205d28 --- /dev/null +++ b/tests/playwright/package.json @@ -0,0 +1,12 @@ +{ + "private": true, + "name": "playwright_ui_tests", + "version": "1.0.0", + "description": "UI tests for Open XDMoD Using Playwright", + "author": "Lorraine A. Gao", + "license": "GPL-3.0", + "repository": "https://github.com/ubccr/xdmod", + "devDependencies": { + "@playwright/test": "^1.22.2" + } +} diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts new file mode 100644 index 0000000000..2a25c462ea --- /dev/null +++ b/tests/playwright/playwright.config.ts @@ -0,0 +1,26 @@ +import {PlaywrightTestConfig, devices} from '@playwright/test'; +// Comment to trigger CI man do I hate trello boards... +const config: PlaywrightTestConfig = { + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + timeout: 60000, + workers: 5, + use: { + trace: 'on-first-retry', + video: 'on-first-retry', + screenshot: 'only-on-failure', + ignoreHTTPSErrors: true, + viewport: {width: 2560, height: 1600}, + baseURL: process.env.BASE_URL, + sso: process.env.SSO ? true : false, + timeout: 15000 + }, + projects: [ + { + name: 'chromium', + use: {...devices['Desktop Chrome']} + } + ] +}; + +export default config; diff --git a/tests/playwright/runtests.sh b/tests/playwright/runtests.sh new file mode 100755 index 0000000000..db256edc2b --- /dev/null +++ b/tests/playwright/runtests.sh @@ -0,0 +1,33 @@ +#!/bin/bash +BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +source $BASEDIR/../ci/runtest-include.sh + +SHMSIZEK=`df -k /dev/shm | grep shm | awk '{print $2}'` +if (( $SHMSIZEK < 2000000 )); then + echo "***************************************************************" + echo "Shared memory is less than 2G, tests may fail randomly" + echo "If you are using Docker use the option of --shm-size 2g" + echo "***************************************************************" +fi + +set -e +#ensure that playwright installed +npm update +npm install -g @playwright/test + +echo "UI tests beginning:" `date +"%a %b %d %H:%M:%S.%3N %Y"` + +#playwright automatically runs in headless + +while getopts ":j:" opt; do + case ${opt} in + j) log_junit=${OPTARG};; + esac +done + +if [ -n ${log_junit} ]; +then + PLAYWRIGHT_JUNIT_OUTPUT_NAME=test_results-${log_junit}.xml npx playwright test --reporter=junit tests/* +else + npx playwright test tests/* +fi diff --git a/tests/playwright/tests/SSOLogin/SSOLogin.spec.ts b/tests/playwright/tests/SSOLogin/SSOLogin.spec.ts new file mode 100644 index 0000000000..d99b6af03f --- /dev/null +++ b/tests/playwright/tests/SSOLogin/SSOLogin.spec.ts @@ -0,0 +1,52 @@ +import {test, expect} from '@playwright/test'; + +test('Single Sign On Login', async ({page}) =>{ + await test.step('Should have the Single Sign On option', async () => { + await page.goto('/'); + await expect(page.locator("//a[@id='sign_in_link']")).toBeVisible(); + await page.locator("//a[@id='sign_in_link']").click(); + }); + await test.step('Should let us select the SSO Login button', async () => { + await expect(page.locator('#SSOLoginLink')).toBeVisible(); + await page.locator('#SSOLoginLink').click(); + }); + await test.step('Should goto the Single Sign On login page and login', async () => { + await expect(page.locator('//button[@id="btn-sign-in"]')).toBeVisible(); + await page.click('//button[@id="btn-sign-in"]'); + }); + await test.step('Display Logged in Users Name', async () => { + await expect(page.locator('#welcome_message')).toBeVisible({timeout:10000}); + const msg = await page.locator('#welcome_message').textContent(); + await expect(msg).toEqual('Saml Jackson'); + await expect(page.locator('#main_tab_panel__about_xdmod')).toBeVisible(); + }); + await test.step('Might prompt with My Profile', async () => { + await expect(page.locator('#xdmod-profile-editor')).toBeVisible(); + await expect(page.locator('#xdmod-profile-editor button.general_btn_close')).toBeVisible(); + await page.locator('#xdmod-profile-editor button.general_btn_close').click(); + await expect(page.locator('#xdmod-profile-editor')).toBeHidden(); + }); + await test.step('Logout', async () => { + await expect(page.locator('#logout_link')).toBeVisible(); + await page.locator('#logout_link').click(); + await expect(page.locator('a[href*=actionLogin]')).toBeVisible(); + await expect(page.locator('#main_tab_panel__about_xdmod')).toBeVisible(); + }); +}); + +test('Single Sign On Login w/ deep link', async ({page}) => { + await test.step('Should have the Single Sign On option', async () => { + await page.goto('/#main_tab_panel:metric_explorer'); + await expect(page.locator('#SSOLoginLink')).toBeVisible(); + await page.locator('#SSOLoginLink').click(); + }); + await test.step('Should goto the Single Sign On login page and login', async () => { + await expect(page.locator('//button[@id="btn-sign-in"]')).toBeVisible(); + await page.click('//button[@id="btn-sign-in"]'); + }); + await test.step('Load Metric Explorer tab', async () => { + await expect(page.locator('#welcome_message')).toBeVisible({timeout:10000}); + await expect(page.locator('#welcome_message')).toContainText('Saml Jackson'); + await expect(page.locator('#metric_explorer')).toBeVisible(); + }); +}); diff --git a/tests/playwright/tests/about/about.spec.ts b/tests/playwright/tests/about/about.spec.ts new file mode 100644 index 0000000000..d71f51eacb --- /dev/null +++ b/tests/playwright/tests/about/about.spec.ts @@ -0,0 +1,109 @@ +import {test, expect} from '@playwright/test'; +import About from "../../lib/about.page"; +import {LoginPage} from "../../lib/login.page"; +import globalConfig from '../../playwright.config'; +import testing from '../../../ci/testing.json'; +let roles = testing.role; + +test.describe('About', async () => { + let baseUrl = globalConfig.use.baseURL; + test('Log In and Log Out Test', async ({page}) => { + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login(roles['cd'].username, roles['cd'].password, (roles['cd'].givenname + " " + roles['cd'].surname)); + const about = new About(page, page.baseUrl); + await page.locator(about.selectors.myProfile).click(); + await expect(page.locator(about.selectors.role)).toContainText('Center Director'); + await page.reload(); + + await test.step('Verify About is the Last Tab', async () => { + await expect(page.locator(about.selectors.tab)).toBeVisible(); + await expect(page.locator(about.selectors.last_tab)).toContainText('About'); + }); + + await test.step('Select About Tab', async () => { + await expect(page.locator(about.selectors.last_tab)).toBeVisible(); + await page.locator(about.selectors.last_tab).click(); + await expect(page.locator(about.selectors.container)).toBeVisible(); + }); + await test.step('Check Nav Entries', async () => { + await test.step('XDMoD', async () => { + await about.checkTab('XDMoD'); + }); + await test.step('Open XDMoD', async () => { + await about.checkTab('Open XDMoD') + }); + await test.step('SUPReMM', async () => { + await about.checkTab('SUPReMM'); + }); + await test.step('Roadmap', async () => { + await about.checkRoadMap(); + }); + await test.step('Team', async () => { + await about.checkTab('Team'); + }); + await test.step('Publications', async () => { + await about.checkTab('Publications'); + }); + await test.step('Presentations', async () => { + await about.checkTab('Presentations'); + }); + await test.step('Links', async () => { + await about.checkTab('Links'); + }); + await test.step('Release Notes', async () => { + await about.checkTab('Release Notes'); + }); + }); + await test.step('Click the logout link', async () => { + await expect(page.locator(about.selectors.logoutLink)).toBeVisible(); + const expired = await page.isVisible(about.selectors.expiredMessageBox); + if (expired) { + await page.click(about.selectors.continueLogoutButton); + } else { + await page.locator(about.selectors.logoutLink).click(); + } + }); + await test.step('Display Logged out State', async () => { + await page.locator(about.selectors.signInLink).waitFor({state:'visible'}); + }); + await test.step('Verify About is the Last Tab', async () => { + await expect(page.locator(about.selectors.tab)).toBeVisible(); + await expect(page.locator(about.selectors.last_tab)).toContainText('About'); + }); + + await test.step('Select About Tab', async () => { + await expect(page.locator(about.selectors.tab)).toBeVisible(); + await page.locator(about.selectors.last_tab).click(); + await expect(page.locator(about.selectors.container)).toBeVisible(); + }); + await test.step('Check Nav Entries', async () => { + await test.step('XDMoD', async () => { + await about.checkTab('XDMoD'); + }); + await test.step('Open XDMoD', async () => { + await about.checkTab('Open XDMoD') + }); + await test.step('SUPReMM', async () => { + await about.checkTab('SUPReMM'); + }); + await test.step('Roadmap', async () => { + await about.checkRoadMap(); + }); + await test.step('Team', async () => { + await about.checkTab('Team'); + }); + await test.step('Publications', async () => { + await about.checkTab('Publications'); + }); + await test.step('Presentations', async () => { + await about.checkTab('Presentations'); + }); + await test.step('Links', async () => { + await about.checkTab('Links'); + }); + await test.step('Release Notes', async () => { + await about.checkTab('Release Notes'); + }); + }); + }); +}); diff --git a/tests/playwright/tests/exportDialog/exportDialog.spec.ts b/tests/playwright/tests/exportDialog/exportDialog.spec.ts new file mode 100644 index 0000000000..c7487627d2 --- /dev/null +++ b/tests/playwright/tests/exportDialog/exportDialog.spec.ts @@ -0,0 +1,92 @@ +import {test, expect} from '@playwright/test'; +import Usage from '../../lib/usageTab.page'; +import XDMoD from '../../lib/xdmod.page'; + +test('Export Dialog', async ({page}) => { + await page.goto('/'); + await page.waitForLoadState(); + const usage = new Usage(page, page.baseUrl); + const xdmod = new XDMoD(page, page.baseUrl); + await test.step('Select "Usage" tab', async () => { + await usage.selectTab(); + }); + await test.step('Bring up export dialog', async () => { + await expect(page.locator(usage.selectors.toolbar.exportButton)).toBeVisible(); + await page.click(usage.selectors.toolbar.exportButton); + await expect(page.locator(xdmod.selectors.exportDialog.window)).toBeVisible(); + }); + await test.step('Check format list', async () => { + await expect(page.locator(xdmod.selectors.exportDialog.formatDropdown())).toBeVisible(); + await page.click(xdmod.selectors.exportDialog.formatDropdown()); + await expect(page.locator(xdmod.selectors.exportDialog.comboListItemByNum(1))).toBeVisible(); + const expected = [ + 'PNG - Portable Network Graphics', + 'SVG - Scalable Vector Graphics', + 'CSV - Comma Separated Values', + 'XML - Extensible Markup Language', + 'PDF - Portable Document Format' + ]; + const computed = await page.$$(xdmod.selectors.exportDialog.comboListItems); + const innerTexts = await Promise.all(computed.map(async (ele, i) => { + return await ele.innerText(); + })); + await expect(innerTexts).toEqual(expected); + await expect(page.locator(xdmod.selectors.exportDialog.formatDropdown())).toBeVisible(); + await page.click(xdmod.selectors.exportDialog.formatDropdown()); + await expect(page.locator(xdmod.selectors.exportDialog.comboListItemByNum(1))).toBeHidden(); + }); + await test.step('Check Image Sizes', async () => { + await expect(page.locator(xdmod.selectors.exportDialog.imageSizeDropdown())).toBeVisible(); + await page.click(xdmod.selectors.exportDialog.imageSizeDropdown()); + await expect(page.locator(xdmod.selectors.exportDialog.comboListItemByNum(8))).toBeVisible(); + const expected = [ + 'Small', + 'Medium', + 'Large', + 'Poster', + 'Custom' + ]; + const computed = await page.$$(xdmod.selectors.exportDialog.comboListItems); + const innerTexts = await Promise.all(computed.map(async (ele, i) => { + return await ele.innerText(); + })); + await expect(innerTexts).toEqual(expected); + await expect(page.locator(xdmod.selectors.exportDialog.imageSizeDropdown())).toBeVisible(); + await page.click(xdmod.selectors.exportDialog.imageSizeDropdown()); + await expect(page.locator(xdmod.selectors.exportDialog.comboListItemByNum(8))).toBeHidden(); + }); + await test.step('Check show chart title exists', async () => { + await expect(page.locator(xdmod.selectors.exportDialog.showTitleCheckbox())).toBeVisible(); + }); + await test.step('Switch to CSV output', async () => { + await expect(page.locator(xdmod.selectors.exportDialog.formatDropdown())).toBeVisible(); + await page.click(xdmod.selectors.exportDialog.formatDropdown()); + await expect(page.locator(xdmod.selectors.exportDialog.comboListItemByNum(1))).toBeVisible(); + await expect(page.locator(xdmod.selectors.exportDialog.comboListItemByName('CSV'))).toBeVisible(); + await page.click(xdmod.selectors.exportDialog.comboListItemByName('CSV')); + await expect(page.locator(xdmod.selectors.exportDialog.comboListItemByNum(1))).toBeHidden(); + }); + await test.step('Make sure title and image options are not visible', async () => { + await expect(page.locator(xdmod.selectors.exportDialog.showTitleCheckbox())).toBeHidden(); + await expect(page.locator(xdmod.selectors.exportDialog.imageSizeDropdown())).toBeHidden(); + }); + await test.step('Switch to PDF output', async () => { + await expect(page.locator(xdmod.selectors.exportDialog.formatDropdown())).toBeVisible(); + await page.click(xdmod.selectors.exportDialog.formatDropdown()); + await expect(page.locator(xdmod.selectors.exportDialog.comboListItemByNum(1))).toBeVisible(); + await expect(page.locator(xdmod.selectors.exportDialog.comboListItemByName('PDF'))).toBeVisible(); + await page.click(xdmod.selectors.exportDialog.comboListItemByName('PDF')); + await expect(page.locator(xdmod.selectors.exportDialog.comboListItemByNum(1))).toBeHidden(); + }); + await test.step('Make sure title and size options are visible', async () => { + await expect(page.locator(xdmod.selectors.exportDialog.showTitleCheckbox())).toBeVisible(); + await expect(page.locator(xdmod.selectors.exportDialog.widthInput())).toBeVisible(); + await expect(page.locator(xdmod.selectors.exportDialog.heightInput())).toBeVisible(); + await expect(page.locator(xdmod.selectors.exportDialog.fontInput())).toBeVisible(); + }); + await test.step('Close', async () => { + await expect(page.locator(xdmod.selectors.exportDialog.cancelButton())).toBeVisible(); + await page.click(xdmod.selectors.exportDialog.cancelButton()); + await expect(page.locator(xdmod.selectors.exportDialog.window)).toBeHidden(); + }); +}); diff --git a/tests/playwright/tests/helpers/artifacts.ts b/tests/playwright/tests/helpers/artifacts.ts new file mode 100644 index 0000000000..1aab67c016 --- /dev/null +++ b/tests/playwright/tests/helpers/artifacts.ts @@ -0,0 +1,13 @@ +import path from 'path'; +import fs from 'fs'; + +const artifacts = { + testEnv: process.env.TEST_ENV !== undefined ? process.env.TEST_ENV : 'xdmod', + artifactPath: path.join(__dirname, './../../../artifacts/'), + getArtifact: function (name, type = 'output'){ + const filePath = path.join(this.artifactPath, this.testEnv, 'ui', type, name + '.json'); + return JSON.parse(fs.readFileSync(filePath)); + } +} + +export default artifacts; diff --git a/tests/playwright/tests/helpers/index.ts b/tests/playwright/tests/helpers/index.ts new file mode 100644 index 0000000000..3c38e1d2ce --- /dev/null +++ b/tests/playwright/tests/helpers/index.ts @@ -0,0 +1 @@ +export default 'require-dir'; diff --git a/tests/ui/test/helpers/instructions.js b/tests/playwright/tests/helpers/instructions.ts similarity index 69% rename from tests/ui/test/helpers/instructions.js rename to tests/playwright/tests/helpers/instructions.ts index abf42397aa..938fd8a6bf 100644 --- a/tests/ui/test/helpers/instructions.js +++ b/tests/playwright/tests/helpers/instructions.ts @@ -1,10 +1,9 @@ -// TODO: Look into moving these to files... -var sections = { +const sections = { metricExplorer: { instructions: '
No data is available for viewing

Please refer to the instructions below:



For more information, please refer to the User Manual
' } }; - -module.exports = function instructions(browser, section, selector) { - expect(browser.getHTML(selector + ' .x-grid-empty')).to.equal(sections[section].instructions); +export async function instructions(page, section, selector){ + const computed = await page.innerHTML(selector + ' .x-grid-empty'); + return ('
' + computed + '
') === (sections[section].instructions); }; diff --git a/tests/playwright/tests/internal_dashboard/internal_dashboard.spec.ts b/tests/playwright/tests/internal_dashboard/internal_dashboard.spec.ts new file mode 100644 index 0000000000..051436a50b --- /dev/null +++ b/tests/playwright/tests/internal_dashboard/internal_dashboard.spec.ts @@ -0,0 +1,358 @@ +import {test, expect} from '@playwright/test'; +import InternalDashboard from "../../lib/internal_dashboard.page"; +import settings from '../../config/internal_dashboard/settings.json'; +import globalConfig from '../../playwright.config'; +import testing from '../../../ci/testing.json'; +let roles = testing.role; + +test.describe('Internal Dashboard Tests', async () => { + let baseUrl = globalConfig.use.baseURL; + test('Create a new user', async ({page}) => { + await page.goto('/internal_dashboard'); + const internalDash = new InternalDashboard(page, baseUrl, page.sso); + await internalDash.login(roles['mgr'].username, roles['mgr'].password, (roles['mgr'].givenname + " " + roles['mgr'].surname)); + + await test.step('Select User Management tab', async () => { + await expect(page.locator(InternalDashboard.selectors.header.tabs.user_management())).toBeVisible(); + await page.click(InternalDashboard.selectors.header.tabs.user_management()); + await expect(page.locator(InternalDashboard.selectors.user_management.tabs.account_requests())).toBeVisible(); + }); + + await test.step('Click "Create and Manage Users"', async () => { + await expect(page.locator(InternalDashboard.selectors.account_requests.toolbar.create_manage_users)).toBeVisible(); + await page.click(InternalDashboard.selectors.account_requests.toolbar.create_manage_users); + + await expect(page.locator(InternalDashboard.selectors.create_manage_users.window)).toBeVisible(); + }); + + await test.step('Select the "New User" tab', async () => { + await expect(page.locator(InternalDashboard.selectors.create_manage_users.tabs.new_user())).toBeVisible(); + await page.click(InternalDashboard.selectors.create_manage_users.tabs.new_user()); + + await expect(page.locator(InternalDashboard.selectors.create_manage_users.new_user.container())).toBeVisible(); + }); + + await test.step('Populate User Information', async () => { + await expect(page.locator(InternalDashboard.selectors.create_manage_users.new_user.first_name())).toBeVisible(); + await expect(page.locator(InternalDashboard.selectors.create_manage_users.new_user.lastName())).toBeVisible(); + await expect(page.locator(InternalDashboard.selectors.create_manage_users.new_user.emailAddress())).toBeVisible(); + + await page.fill(InternalDashboard.selectors.create_manage_users.new_user.first_name(), 'Bob'); + await page.fill(InternalDashboard.selectors.create_manage_users.new_user.lastName(), 'Test'); + await page.fill(InternalDashboard.selectors.create_manage_users.new_user.emailAddress(), 'btest@example.com'); + }); + + await test.step('Populate User Details', async () => { + await expect(page.locator(InternalDashboard.selectors.create_manage_users.new_user.username())).toBeVisible(); + await expect(page.locator(InternalDashboard.selectors.create_manage_users.new_user.mapTo())).toBeVisible(); + await expect(page.locator(InternalDashboard.selectors.create_manage_users.new_user.institution())).toBeVisible(); + + // the institution drop down should be disabled by default. + await expect(page.locator(InternalDashboard.selectors.create_manage_users.new_user.institution())).toBeDisabled(); + + await page.type(InternalDashboard.selectors.create_manage_users.new_user.username(), 'btest', {delay: 100}); + await page.type(InternalDashboard.selectors.create_manage_users.new_user.mapTo(), 'Unknown', {delay: 100}); + + await expect(page.locator(InternalDashboard.selectors.combo.container)).toBeVisible(); + await expect(page.locator(InternalDashboard.selectors.combo.itemByText('Unknown'))).toBeVisible(); + + await page.click(InternalDashboard.selectors.combo.itemByText('Unknown')); + await expect(page.locator(InternalDashboard.selectors.combo.container)).toBeHidden(); + + const mapTo = await page.inputValue(InternalDashboard.selectors.create_manage_users.new_user.mapTo()); + expect(mapTo).toEqual('Unknown'); + + // Wait for the institution combo to be enabled / have a value. + await expect(page.locator(InternalDashboard.selectors.create_manage_users.new_user.institution())).toBeEnabled(); + + // By selecting a person to map our user to the institution should be populated automatically. + // + const institution = await page.inputValue(InternalDashboard.selectors.create_manage_users.new_user.institution()); + expect(institution).toEqual('Unknown Organization'); + + // Institution should also be enabled because we're mapping the 'Unknown' person. + await expect(page.locator(InternalDashboard.selectors.create_manage_users.new_user.institution())).toBeEnabled(); + }); + + await test.step('Change Institution', async () => { + await page.click(InternalDashboard.selectors.create_manage_users.new_user.institution_trigger()); + await expect(page.locator(InternalDashboard.selectors.combo.container)).toBeVisible(); + await expect(page.locator(InternalDashboard.selectors.combo.itemByText('Screwdriver'))).toBeVisible(); + + await page.click(InternalDashboard.selectors.combo.itemByText('Screwdriver')); + await expect(page.locator(InternalDashboard.selectors.combo.container)).toBeHidden(); + + const newInstitution = await page.inputValue(InternalDashboard.selectors.create_manage_users.new_user.institution()); + expect(newInstitution).toEqual('Screwdriver'); + }); + + await test.step('Select Acls', async () => { + await page.click(InternalDashboard.selectors.create_manage_users.new_user.aclByName('User')); + await expect(page.locator(InternalDashboard.selectors.create_manage_users.new_user.aclByName('User'))).toHaveClass(/x-grid3-check-col-on/); + }); + + await test.step('Save User', async () => { + await page.click(InternalDashboard.selectors.create_manage_users.buttons.create_user()); + + await expect(page.locator(InternalDashboard.selectors.createSuccessNotification('btest'))).toBeVisible(); + await expect(page.locator(InternalDashboard.selectors.createSuccessNotification('btest'))).toBeHidden(); + }); + + await test.step('Close "Create and Manage Users"', async () => { + await page.click(InternalDashboard.selectors.create_manage_users.buttons.close()); + await expect(page.locator(InternalDashboard.selectors.create_manage_users.window)).toBeHidden(); + }) + + await test.step('Select the "Existing Users" tab', async () => { + await expect(page.locator(InternalDashboard.selectors.user_management.tabs.existing_users())).toBeVisible(); + await page.click(InternalDashboard.selectors.user_management.tabs.existing_users()); + await expect(page.locator(InternalDashboard.selectors.existing_users.table.container)).toBeVisible(); + }); + + await test.step('Check that the username is displayed correctly', async () => { + let selector = 'xpath=' + InternalDashboard.selectors.existing_users.table.col_for_user('btest', 'Username'); + await expect(page.locator(selector)).toBeVisible({timeout: 30000}); + await expect(page.locator(selector)).toContainText('btest'); + }); + + await test.step('Check that the first name is displayed correctly', async () => { + let column = page.locator('xpath=' + InternalDashboard.selectors.existing_users.table.col_for_user('btest', 'First Name')); + await expect(column).toBeVisible(); + await expect(column).toContainText('Bob'); + }); + + await test.step('Check that the last name is displayed correctly', async () => { + let column = page.locator('xpath=' + InternalDashboard.selectors.existing_users.table.col_for_user('btest', 'Last Name')); + await expect(column).toBeVisible(); + await expect(column).toContainText('Test'); + }); + + await test.step('Check that the E-Mail Address is displayed correctly', async () => { + let column = page.locator('xpath=' + InternalDashboard.selectors.existing_users.table.col_for_user('btest', 'E-Mail Address')); + await expect(column).toBeVisible(); + await expect(column).toContainText('btest@example.com'); + }); + + await test.step('Check that the role is displayed correctly', async () => { + let column = page.locator('xpath=' + InternalDashboard.selectors.existing_users.table.col_for_user('btest', 'Role(s)')); + await expect(column).toBeVisible(); + await expect(column).toContainText('User'); + }); + }); + + test('Test that settings can be discarded.', async ({page}) => { + await page.goto('/internal_dashboard'); + const internalDash = new InternalDashboard(page, baseUrl, page.sso); + await internalDash.login(roles['mgr'].username, roles['mgr'].password, (roles['mgr'].givenname + " " + roles['mgr'].surname)); + + await test.step('Select User Management tab', async () => { + await expect(page.locator(InternalDashboard.selectors.header.tabs.user_management())).toBeVisible(); + await page.click(InternalDashboard.selectors.header.tabs.user_management()); + await expect(page.locator(InternalDashboard.selectors.user_management.tabs.account_requests())).toBeVisible(); + }); + + for (const index in settings) { + const setting = settings[index]; + await test.step(`${setting.label}: Selecting the "Existing Users" tab`, async () => { + await expect(page.locator(InternalDashboard.selectors.user_management.tabs.existing_users())).toBeVisible(); + await page.click(InternalDashboard.selectors.user_management.tabs.existing_users()); + await expect(page.locator(InternalDashboard.selectors.existing_users.table.container)).toBeVisible(); + }); + + await test.step(`${setting.label}: Double click the users row in the "Existing Users" table`, async () => { + const selector = 'xpath=' + InternalDashboard.selectors.existing_users.table.col_for_user('btest', "Username"); + const column = page.locator(selector); + await expect(column).toBeVisible(); + await page.dblclick(selector); + + await expect(page.locator(InternalDashboard.selectors.create_manage_users.window)).toBeVisible(); + await expect(page.locator(InternalDashboard.selectors.create_manage_users.current_users.container)).toBeVisible(); + await expect(page.locator(InternalDashboard.selectors.create_manage_users.loading_mask)).toBeHidden(); + }); + + await test.step(`${setting.label}: Change the "${setting.label}" to "${setting.updated}"`, async () => { + await expect(page.locator(InternalDashboard.selectors.create_manage_users.current_users.settings.noUserSelectedModal())).toBeHidden(); + if ('dropdown' === setting.type) { + let inputSelector = InternalDashboard.selectors.create_manage_users.current_users.settings.dropDownTriggerByLabel(setting.label); + let inputTrigger = page.locator(inputSelector); + await expect(inputTrigger).toBeVisible(); + await page.click(inputSelector); + + let inputDropDown = page.locator(InternalDashboard.selectors.combo.container); + await expect(inputDropDown).toBeVisible(); + + let dropDownValueSelector = InternalDashboard.selectors.combo.itemByText(setting.updated); + let dropDownValue = page.locator(dropDownValueSelector); + await expect(dropDownValue).toBeVisible(); + await page.click(dropDownValueSelector); + + await expect(inputDropDown).toBeHidden(); + } else if ('text' === setting.type) { + const inputSelector = InternalDashboard.selectors.create_manage_users.current_users.settings.inputByLabel(setting.label, setting.type); + const input = page.locator(inputSelector); + await expect(input).toBeVisible(); + await page.fill(inputSelector, setting.updated); + await page.keyboard.press('Tab'); + } + }); + + await test.step(`${setting.label}: Ensure that the user dirty message is shown`, async () => { + await expect(page.locator(InternalDashboard.selectors.create_manage_users.bottom_bar.messageByText('unsaved changes'))).toBeVisible(); + }); + + await test.step(`${setting.label}: Click the save button`, async () => { + let saveButtonSelector = InternalDashboard.selectors.create_manage_users.current_users.button('Save Changes'); + let saveButton = page.locator(saveButtonSelector); + await expect(saveButton).toBeVisible(); + await page.click(saveButtonSelector); + + let updateModal = page.locator(InternalDashboard.selectors.updateSuccessNotification('btest')); + await expect(updateModal).toBeVisible(); + await expect(updateModal).toBeHidden(); + }); + + if ('User Type' === setting.label) { + + await test.step(`${setting.label}: Check that the user is not still selected`, async () => { + await expect(page.locator(InternalDashboard.selectors.create_manage_users.current_users.settings.noUserSelectedModal())).toBeVisible(); + }); + + await test.step(`${setting.label}: Check that the user is not listed in the Existing Users table`, async () => { + await expect(page.locator(InternalDashboard.selectors.create_manage_users.current_users.user_list.col_for_user('btest'))).toBeHidden(); + }); + + await test.step(`${setting.label}: Change the Displayed User Type to: ${setting.updated}`, async () => { + const displayedUserTypeSelector = InternalDashboard.selectors.create_manage_users.current_users.user_list.toolbar.buttonByLabel('Displaying', setting.original); + const displayedUserType = page.locator(displayedUserTypeSelector); + await expect(displayedUserType).toBeVisible(); + await page.click(displayedUserTypeSelector); + + const newUserTypeItemSelector = InternalDashboard.selectors.create_manage_users.current_users.user_list.dropDownItemByText(setting.updated); + const newUserTypeItem = page.locator(newUserTypeItemSelector); + await expect(newUserTypeItem).toBeVisible(); + await page.click(newUserTypeItemSelector); + }); + + await test.step(`${setting.label}: Check that the user is listed in the Existing Users table`, async () => { + await expect(page.locator(InternalDashboard.selectors.create_manage_users.current_users.user_list.col_for_user('btest'))).toBeVisible(); + }); + } else { + await test.step(`${setting.label}: Check that ${setting.label} has been updated successfully to "${setting.updated}"`, async () => { + const inputType = 'dropdown' === setting.type ? 'text' : setting.type; + const selector = InternalDashboard.selectors.create_manage_users.current_users.settings.inputByLabel(setting.label, inputType); + const input = page.locator(selector); + await expect(input).toBeVisible(); + const updated = await page.inputValue(selector); + await expect(updated).toEqual(setting.updated); + }); + } + + await test.step(`${setting.label}: Close the edit user modal`, async () => { + const closeButtonSelector = InternalDashboard.selectors.create_manage_users.current_users.button('Close'); + await expect(page.locator(closeButtonSelector)).toBeVisible(); + await page.click(closeButtonSelector); + }); + } + }); + + test('Remove the newly created user', async ({page}) => { + await page.goto('/internal_dashboard'); + const internalDash = new InternalDashboard(page, baseUrl, page.sso); + await internalDash.login(roles['mgr'].username, roles['mgr'].password, (roles['mgr'].givenname + " " + roles['mgr'].surname)); + + await test.step('Select User Management tab', async () => { + await expect(page.locator(InternalDashboard.selectors.header.tabs.user_management())).toBeVisible(); + await page.click(InternalDashboard.selectors.header.tabs.user_management()); + await expect(page.locator(InternalDashboard.selectors.user_management.tabs.account_requests())).toBeVisible(); + }); + + await test.step('Ensure that were on the "Existing Users" Tab', async () => { + const selector = InternalDashboard.selectors.user_management.tabs.existing_users(); + await expect(page.locator(selector)).toBeVisible(); + await page.click(selector); + await expect(page.locator(InternalDashboard.selectors.existing_users.table.container)).toBeVisible(); + }); + + await test.step('Double click the newly created user', async () => { + const selector = 'xpath=' + InternalDashboard.selectors.existing_users.table.col_for_user('btest', 'Username'); + await expect(page.locator(selector)).toBeVisible(); + await page.dblclick(selector); + await expect(page.locator(InternalDashboard.selectors.create_manage_users.window)).toBeVisible(); + }); + + await test.step('Ensure that the "Actions" button is visible and click it', async () => { + const selector = InternalDashboard.selectors.create_manage_users.current_users.settings.toolbar.actions.button(); + await expect(page.locator(selector)).toBeVisible(); + await page.click(selector); + + await expect(page.locator(InternalDashboard.selectors.create_manage_users.current_users.settings.toolbar.actions.container)).toBeVisible(); + }); + + await test.step('Click the "Delete This User" menu item', async () => { + const selector = InternalDashboard.selectors.create_manage_users.current_users.settings.toolbar.actions.itemWithText('Delete This Account'); + await expect(page.locator(selector)).toBeVisible(); + await page.click(selector); + }); + + await test.step('Confirm the deletion of the user', async () => { + const selector = InternalDashboard.selectors.modal.buttonByText('Delete User', 'Yes'); + await expect(page.locator(selector)).toBeVisible(); + await page.click(selector); + + const deleteNotification = page.locator(InternalDashboard.selectors.deleteSuccessNotification('btest')); + await expect(deleteNotification).toBeVisible(); + await expect(deleteNotification).toBeHidden(); + }); + + await test.step('Close the User Management Dialog', async () => { + const selector = InternalDashboard.selectors.create_manage_users.current_users.button('Close'); + await expect(page.locator(selector)).toBeVisible(); + await page.click(selector); + }); + + await test.step('Ensure that the "Existing Users" table is displayed', async () => { + await expect(page.locator(InternalDashboard.selectors.existing_users.table.container)).toBeVisible(); + }); + + await test.step('Check that there is not a username', async () => { + let selector = 'xpath=' + InternalDashboard.selectors.existing_users.table.col_for_user('btest', 'Username'); + let locator = page.locator(selector); + await expect(locator).toBeVisible(); + const value = await page.textContent(selector); + expect(value).toMatch(/^((?!btest).)*$/); + }); + + await test.step('Check that there is no First Name', async () => { + let selector = 'xpath=' + InternalDashboard.selectors.existing_users.table.col_for_user('btest', 'First Name'); + let locator = page.locator(selector); + await expect(locator).toBeVisible(); + const value = await page.textContent(selector); + expect(value).toMatch(/^((?!Bob).)*$/); + }); + + await test.step('Check that there is no Last Name', async () => { + let selector = 'xpath=' + InternalDashboard.selectors.existing_users.table.col_for_user('btest', 'Last Name'); + let locator = page.locator(selector); + await expect(locator).toBeVisible(); + const value = await page.textContent(selector); + expect(value).toMatch(/^((?!Test).)*$/); + }); + + await test.step('Check that there is no E-Mail Address', async () => { + let selector = 'xpath=' + InternalDashboard.selectors.existing_users.table.col_for_user('btest', 'E-Mail Address'); + let locator = page.locator(selector); + await expect(locator).toBeVisible(); + const value = await page.textContent(selector); + expect(value).toMatch(/^((?!btest@example.com).)*$/); + }); + + await test.step('Check that there are no Role(s)', async () => { + let selector = 'xpath=' + InternalDashboard.selectors.existing_users.table.col_for_user('btest', 'Role(s)'); + let locator = page.locator(selector); + await expect(locator).toBeVisible(); + const value = await page.textContent(selector); + const matches = value === 'User'; + expect(matches).toEqual(false); + }); + }); +}); diff --git a/tests/playwright/tests/mainToolbar/mainToolbar.spec.ts b/tests/playwright/tests/mainToolbar/mainToolbar.spec.ts new file mode 100644 index 0000000000..2663e9b4a5 --- /dev/null +++ b/tests/playwright/tests/mainToolbar/mainToolbar.spec.ts @@ -0,0 +1,52 @@ +import {test} from '@playwright/test'; +import MainToolbar from "../../lib/mainToolbar.page"; + +let mainTab; + +test.describe('Main Toolbar', async () => { + test('Check Tab', async ({page}) => { + await page.goto('/'); + await page.waitForLoadState(); + const mTb = new MainToolbar(page, page.baseUrl); + + await test.step('Get Browswer Tab ID', async () => { + mainTab = await page.evaluateHandle(() => Promise.resolve(window)); + }); + + await test.step("About should change 'Tabs'", async () =>{ + await mTb.checkAbout(); + }); + }); + + test('Contact Us - Send Message', async ({page}) => { + await page.goto('/'); + await page.waitForLoadState(); + const mTb = new MainToolbar(page, page.baseUrl); + await mTb.contactFunc('Send Message'); + }); + + test('Contact Us - Request Feature', async ({page}) => { + await page.goto('/'); + await page.waitForLoadState(); + const mTb = new MainToolbar(page, page.baseUrl); + await mTb.contactFunc('Request Feature'); + }); + + test('Contact Us - Submit Support Request', async ({page}) => { + await page.goto('/'); + await page.waitForLoadState(); + const mTb = new MainToolbar(page, page.baseUrl); + await mTb.contactFunc('Submit Support Request'); + }); + + test('Help', async ({page}) =>{ + await page.goto('/'); + await page.waitForLoadState(); + const mTb = new MainToolbar(page, page.baseUrl); + for (let type in mTb.selectors.helpTypes){ + if (mTb.selectors.helpTypes.hasOwnProperty(type)){ + await mTb.helpFunc(type, mainTab); + } + } + }); +}); diff --git a/tests/playwright/tests/metricExplorer/metricExplorer.spec.ts b/tests/playwright/tests/metricExplorer/metricExplorer.spec.ts new file mode 100644 index 0000000000..7490dc29fb --- /dev/null +++ b/tests/playwright/tests/metricExplorer/metricExplorer.spec.ts @@ -0,0 +1,478 @@ +import {test, expect} from '@playwright/test'; +import {LoginPage} from "../../lib/login.page"; +import MetricExplorer from '../../lib/metricExplorer.page'; +import artifacts from "../helpers/artifacts"; +let expected = artifacts.getArtifact('metricExplorer'); +let XDMOD_REALMS = process.env.XDMOD_REALMS; +import globalConfig from '../../playwright.config'; +import XDMoD from '../../lib/xdmod.page'; +import testing from '../../../ci/testing.json'; +let roles = testing.role; + +test.describe('Metric Explorer', async () => { + let baselineDate = { + start: '2016-12-22', + end: '2017-01-01' + }; + let actions = { + chart: { + load: async (chartNumber) => { + let mychartNumber = chartNumber || 0; + await test.step('Load Chart', async () => { + await me.actionLoadChart(mychartNumber); + }); + }, + contextMenu: { + open: async () => { + await test.step('Open Chart Context Menu', async () => { + await page.click('#hc-panelmetric_explorer'); + }); + }, + addData: async () => { + await test.step('Should Display', async () => { + await page.click('#hc-panelmetric_explorer'); + await page.click(me.selectors.chart.contextMenu.addData); + await page.isVisible('#metric-explorer-chartoptions-add-data-menu'); + await page.click('#logo'); + }); + }, + addFilter: async () => { + await test.step('Should Display', async () => { + await page.click('#hc-panelmetric_explorer'); + await page.click(me.selectors.chart.contextMenu.addFilter); + await page.isVisible('#metric-explorer-chartoptions-add-filter-menu'); + await page.click('#logo'); + }); + }, + legend: async () => { + await test.step('Click Legend', async () => { + await page.click(me.selectors.chart.contextMenu.legend); + }); + }, + setLegendPosition: async (position) => { + await test.step('Set Legend ' + position, async () => { + await actions.chart.contextMenu.open(); + await actions.chart.contextMenu.legend(); + await test.step('Click ' + position, async () => { + let posId = me.selectors.chart.contextMenu.legend + '-' + + await position.toLowerCase().replace(/ /g, '-'); + await page.click(posId); + }); + }); + } + } + } + }; + const chartName = 'ME autotest chart ' + Date.now(); + test('Select Tab', async ({page}) => { + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + const me = new MetricExplorer(page, baseUrl); + await loginPage.login(roles['cd'].username, roles['cd'].password, (roles['cd'].givenname + " " + roles['cd'].surname)); + await test.step('Selected', async () => { + await page.click(me.selectors.tab); + await page.isVisible(me.selectors.container); + await page.isVisible(me.selectors.catalog.container); + await page.click(me.selectors.catalog.collapseButton); + }); + }); + // There are no tests for storage and cloud realms currently + if (XDMOD_REALMS.includes('jobs')) { + test('Create and save a chart', async ({page}) => { + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + const me = new MetricExplorer(page, baseUrl); + const xdmod = new XDMoD(page, baseUrl); + await loginPage.login(roles['cd'].username, roles['cd'].password, (roles['cd'].givenname + " " + roles['cd'].surname)); + await xdmod.selectTab('metric_explorer'); + await test.step('Add data via metric catalog', async () => { + await me.createNewChart(chartName, 'Timeseries', 'Line'); + await page.click(me.selectors.toolbar.configureTime.frameButton); + await page.click(me.selectors.toolbar.configureTime.UserDefinedSelect); + await me.setDateRange('2016-12-30', '2017-01-02'); + await me.addDataViaCatalog('Jobs', 'Node Hours: Total', 'None'); + await me.checkChart(chartName, 'Node Hours: Total', expected.legend); + await me.saveChanges(); + await me.clear(); + }); + }); + test('Basic Scenarios', async ({page}) => { + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + const me = new MetricExplorer(page, baseUrl); + const xdmod = new XDMoD(page, baseUrl); + await loginPage.login(roles['cd'].username, roles['cd'].password, (roles['cd'].givenname + " " + roles['cd'].surname)); + await xdmod.selectTab('metric_explorer'); + await test.step('Add Filters in Toolbar', async () => { + await me.loadExistingChartByName(chartName); + await page.locator(me.chart.titleByText(chartName)).waitFor({state:'visible'}); + await expect(page.locator(me.chart.titleByText(chartName))).toBeVisible(); + const startDate = await page.locator(me.startDate).inputValue(); + const endDate = await page.locator(me.endDate).inputValue(); + await expect(startDate).toEqual('2016-12-30'); + await expect(endDate).toEqual('2017-01-02'); + await me.checkChart(chartName, 'Node Hours: Total', expected.legend); + await me.addFiltersFromToolbar('PI'); + await me.cancelFiltersFromToolbar(); + }); + await test.step('Edit Filters in Toolbar', async () => { + await me.editFiltersFromToolbar('Alpine'); + await me.clear(); + }); + await test.step('Add/Edit Filters in Data Series Definition', async () => { + await me.clickLogo(); + await me.loadExistingChartByName(chartName); + await me.addFiltersFromDataSeriesDefinition('PI', 'Alpine'); + await me.cancelFiltersFromDataSeriesDefinition(); + }); + await test.step('Edit Filters in Data Series Definition', async () => { + await me.editFiltersFromDataSeriesDefinition('Alpine'); + await me.clear(); + }); + await test.step('Has Instructions', async () => { + await me.verifyInstructions(); + }); + await test.step('Has three toolbars', async () => { + const toolbars = await page.$$(me.selectors.toolbar.toolbars()); + await expect(toolbars.length).toEqual(3); + }); + await test.step('Has one canned Date Picker', async () => { + // Datepicker does not have a unique name currently + // This check is done by using strict mode + await expect(page.locator(me.selectors.toolbar.cannedDatePicker())).toBeVisible(); + }); + + await test.step('Set a known start date', async () => { + await page.click(me.selectors.toolbar.configureTime.frameButton); + await page.click(me.selectors.toolbar.configureTime.UserDefinedSelect); + await expect(page.locator(me.selectors.startDate)).toBeVisible(); + await page.click(me.selectors.startDate); + await page.fill(me.selectors.startDate, baselineDate.start); + }); + + await test.step('Set a known end date', async () => { + await expect(page.locator(me.selectors.endDate)).toBeVisible(); + await page.click(me.selectors.endDate); + await page.fill(me.selectors.endDate, baselineDate.end); + }); + await test.step("'Add Data' via toolbar", async () => { + // click on CPU Hours: Total + await me.addDataViaMenu('.ext-el-mask-msg', 'CPU Hours: Total'); + await me.addDataSeriesByDefinition(); + }); + await test.step('Chart contains correct information', async () => { + await me.checkChart('untitled query 1', 'CPU Hours: Total', expected.legend); + }); + + await test.step("'Add Data' again via toolbar", async () => { + await me.addDataViaToolbar(); + }); + await test.step('Chart contains correct information', async () => { + await me.checkChart('untitled query 1', 'CPU Hour', [expected.legend + ' [CPU Hours: Total]', expected.legend + ' [CPU Hours: Per Job]']); + }); + + await test.step('Switch to aggregate chart', async () => { + await me.switchToAggregate(); + }); + await test.step('Chart contains correct information', async () => { + await me.checkChart('untitled query 1', 'CPU Hour', ['CPU Hours: Total', 'CPU Hours: Per Job']); + }); + await test.step('Undo Scratch Pad switch to aggregate', async () => { + await me.undoAggregateOrTrendLine(me.container); + }); + await test.step('Check first undo works', async () => { + await me.checkChart('untitled query 1', 'CPU Hour', ['CPU Hours: Total', 'CPU Hours: Per Job']); + }); + await test.step('Undo Scratch Pad second source', async () => { + await me.undoAggregateOrTrendLine(me.container); + }); + await test.step('Check second undo works', async () => { + await me.checkChart('untitled query 1', 'CPU Hours: Total', expected.legend); + }); + await test.step('Attempt Delete Scratchpad Chart', async () => { + await me.attemptDeleteScratchpad(); + }); + await test.step('Chart looks the same as previous run', async () => { + await me.verifyInstructions(); + }); + await test.step('Open chart from load dialog', async () => { + await me.loadExistingChartByName(chartName); + }); + await test.step('Loaded chart looks the same as previous run', async () => { + await me.checkChart(chartName, 'Node Hours: Total', expected.legend); + }); + await test.step('Open Chart Options', async () => { + await page.click(me.selectors.options.button); + await page.locator(me.selectors.options.menu).waitFor({state:'visible'}); + }); + await test.step('Chart options looks the same as previous run', async () => { + // Can only check this with screenshot for now + // browser.takeScreenshot(moduleName, me.selectors.container, "chart.options") + // browser.pause(1000); + }); + await test.step('Show Trend Line via Chart Options', async () => { + await page.click(me.selectors.options.trendLine); + await me.clickSelector(me.selectors.options.button); + }); + await test.step('Trend Line looks the same as previous run', async () => { + await me.checkChart(chartName, 'Node Hours: Total', [expected.legend, 'Trend Line: ' + expected.legend + ' ' + expected.trend_line]); + }); + await test.step('Undo Trend Line', async () => { + await me.undoAggregateOrTrendLine(me.container); + }); + await test.step('Undo Trend Line looks the same as previous run', async () => { + await me.checkChart(chartName, 'Node Hours: Total', expected.legend); + }); + }); + /* The following tests are disabled until such a time as they can be changed to work + * reliably without browser.pause() + + describe('Context Menu', function contextMenu() { + it('Start with scratchpad', function () { + browser.refresh(); + browser.pause(10000); + me.clear(); + browser.pause(2000); + }); + + it('Attempt Delete Scratchpad Chart', function meDeleteScratchPad() { + me.attemptDeleteScratchpad(); + }); + + it('Set a known start date', function meSetStartEnd() { + browser.waitForVisible('#metric_explorer input[id^=start_field_ext]', 10000); + browser.setValue('#metric_explorer input[id^=start_field_ext]', baselineDate.start); + }); + + it('Set a known end date', function meSetStartEnd() { + browser.waitForVisible('#metric_explorer input[id^=end_field_ext]', 10000); + browser.setValue('#metric_explorer input[id^=end_field_ext]', baselineDate.end); + }); + + it('Wait For Metric Catalog', function () { + browser.pause(3000); + browser.waitUntilNotExist('.ext-el-mask-msg'); + browser.waitForVisible(me.selectors.catalog.container, 10000); + }); + it('Generic Starting Point', function () { + me.genericStartingPoint(); + }); + + describe('Add Data Menu', function () { + actions.chart.contextMenu.addData(); + }); + describe('Add Filter Menu', function () { + actions.chart.contextMenu.addFilter(); + }); + describe('Legend Menus', function () { + actions.chart.contextMenu.setLegendPosition('Top Left'); + actions.chart.contextMenu.setLegendPosition('Top Right'); + browser.pause(5000); + describe('Verify after load', function () { + actions.chart.load(1); + actions.chart.contextMenu.open(); + actions.chart.contextMenu.legend(); + it('legend Items set Properly', function () { + browser.waitForVisible('#metric-explorer-chartoptions-legend-options', 2000); + var legendText = browser.getText('#metric-explorer-chartoptions-legend-options .x-menu-item-checked'); + expect(legendText).to.equal('Bottom Center (Default)', 'Loaded chart has non default legend'); + }); + }); + }); + }); + describe('Pie Charts', function pieCharts() { + describe("Can't use timeseries data", function noTimeSeries() { + it('Start with scratchpad', function () { + me.clear(); + }); + it("Add Data via the 'Add Data' Menu", function addData() { + // 'Add Data' via the 'Add Data' menu: + // 'Add Data' -> Jobs -> CPU Hours: Total -> 'Add' + // wait for the global mask to disapear + me.addDataViaMenu('.ext-el-mask-msg', '2'); + }); + it("Set 'Group By' to resource", function findGroupBy() { + me.setGroupByToResource(); + }); + it("'Add' data", function add() { + browser.waitAndClick('#adp_submit_button'); + browser.waitUntilNotExist('.ext-el-mask'); + }); + it('Verify that the chart has been rendered', function chartRendered() { + // Attempt to ascertain if the HighChartPanel ( the panel that contains the chart contents ) is hidden or not. + // Pause so we can determine if the chart was actually loaded. + browser.waitForVisible('#hc-panelmetric_explorer', 3000); + // If the chart was successfully loaded / rendered this should return false. + var execReturn = browser.execute('return CCR.xdmod.ui.metricExplorer.chartViewPanel.items.get(0).hidden;').value; + expect(execReturn).to.equal(false); + }); + it('Chart looks the same as previous run', function () { + // Can only be checked using screenshot currently + // browser.takeScreenshot(moduleName, me.selectors.container, "pie.loaded"); + }); + it("Select the 'Data' toolbar button", function selectData() { + browser.click(me.selectors.data.button); + }); + it('Click the First Row', function jobsRow() { + browser.waitForVisible('.x-menu-floating:not(.x-hide-offsets)'); + browser.execute('return CCR.xdmod.ui.metricExplorer.datasetsGridPanel.getView().getRow(0)').doubleClick(); + }); + it("Set 'Display Type' to 'Pie'", function clickDisplayType() { + me.setToPie(); + }); + it('Verify error message', function checkErrorMessage() { + me.verifyError(); + }); + }); + describe('Aggregate data', function useAggregateData() { + it('Start with scratchpad', function freshChart() { + me.clear(); + }); + }); // Aggregate data + }); // Pie Charts + describe('Chart Interactions', function chartInteractions() { + it('Should start with a new chart', function newChart() { + me.clear(); + }); + describe('Should start start with a dataset', function dataset() { + it("Add Data via the 'Add Data' Menu", function addData() { + // 'Add Data' via the 'Add Data' menu: + // 'Add Data' -> Jobs -> CPU Hours: Total -> 'Add' + // wait for the global mask to disapear + me.addDataViaMenu('div.x-panel.x-masked-relative.x-masked', '2'); + }); + it("Set 'Group By' to resource", function findGroupBy() { + me.setGroupByToResource(); + }); + it("'Add' data", function add() { + browser.waitAndClick('#adp_submit_button'); + browser.waitUntilNotExist('.ext-el-mask'); + }); + it('Verify that the chart has been rendered', function chartRendered() { + // Attempt to ascertain if the HighChartPanel ( the panel that contains the chart contents ) is hidden or not. + // Pause so we can determine if the chart was actually loaded. + browser.waitForChart(); + browser.pause(750); + // If the chart was successfully loaded / rendered this should return false. + var execReturn = browser.execute('return CCR.xdmod.ui.metricExplorer.chartViewPanel.items.get(0).hidden;'); + expect(execReturn.value).to.equal(false); + }); + it('Chart looks the same as previous run', function () { + // Can only be checked using screenshot for now + // browser.takeScreenshot(moduleName, me.selectors.container, "highcharts.loaded") + }); + }); // Should start with a dataset. + + describe('Chart Titles', function chartTitle() { + describe('Update the Chart Title with a value that contains html entities', function editChartTitleDialog() { + it('Click the Chart Title element', function clickChartTitleElement() { + browser.waitAndClick(me.selectors.chart.title); + browser.waitForVisible(me.selectors.chart.titleInput, 3000); + }); + + it('Update the Chart Title input element', function editChartTitle() { + browser.pause(2000); + browser.clearElement(me.selectors.chart.titleInput); + browser.setValue(me.selectors.chart.titleInput, me.newTitle); + }); + + it('Click the Ok element', function clickOkElement() { + browser.pause(2000); + browser.waitAndClick(me.selectors.chart.titleOkButton); + }); + + it('Chart Title element should be updated', function checkChartTitle() { + browser.pause(2500); + me.verifyHighChartsTitle(me.newTitle); + }); + + it('Chart Title in Chart Options should be updated', function verifyChartOptions() { + me.chartTitleInOptionsUpdated(); + }); + }); + + describe('Set the title to a value that does not include html entities', function noHtmlEntities() { + it('click the Chart Title', function clickChartTitle() { + browser.waitForVisible(me.selectors.chart.title, 3000); + browser.click(me.selectors.chart.title); + browser.waitForVisible(me.selectors.chart.titleInput, 3000); + }); + + it('clear the title field', function clearTitleField() { + browser.clearElement(me.selectors.chart.titleInput); + }); + + it('set the title field to a value that does not contain html entities.', function setWithNoEntities() { + browser.pause(500); + browser.setValue(me.selectors.chart.titleInput, me.originalTitle); + browser.pause(2000); + }); + + it("click 'ok' to confirm the change", function confirmTheChange() { + browser.click(me.selectors.chart.titleOkButton); + browser.waitForVisible(me.selectors.chart.title, 3000); + }); + + it('get the new value from the chart title to verify that it has in fact changed', function checkNewValue() { + browser.pause(500); + var execReturn = browser.execute('return document.querySelector("' + me.selectors.chart.title + '").textContent;'); + expect(execReturn.value).to.be.a('string'); + expect(execReturn.value).to.equal(me.originalTitle); + }); + }); + + describe('Arrow Keys', function () { + it('Arrow keys can be used in chart options title', function () { + me.arrowKeys(); + }); + }); + describe('Accept html entity input via the Chart Options -> Chart Title element.', function editChartTitleChartOptions() { + it('set the Chart Options -> Chart Title value with html entities', function setTitleWithHtmlEntities() { + browser.pause(2000); + me.setTitleWithOptionsMenu(me.newTitle); + browser.pause(2000); + }); + it('click the Chart Options button again', function clickChartOptions() { + browser.waitForChart(); + browser.waitAndClick(me.selectors.options.button); + browser.waitForChart(); + browser.pause(3000); + }); + it('verify that the HighCharts chart title has the new value', function highChartsTitle() { + me.verifyHighChartsTitle(me.newTitle); + }); + it('verify that the Edit Chart Title Modal has the new value', function chartTitleModal() { + me.verifyEditChartTitle(); + }); + }); + + describe('Providing an empty title value', function shouldAcceptEmptyTitle() { + it('Set the chart title to an empty value', function setTitleEmpty() { + me.setChartTitleViaChart(''); + }); + + it('Get the new chart title and confirm that no svg element was generated', function confirmEmptyChartTitle() { + browser.pause(500); + var titleChange = browser.execute('return document.querySelector("' + me.selectors.chart.title + '");'); + // expect(titleChange.state).to.equal('success'); + expect(titleChange._status).to.equal(0); + expect(titleChange.value).to.equal(null); + }); + }); + + describe('Providing an exceptionally large title should be ok', function shouldNotAcceptLargeTitle() { + var largeTitle = me.generateTitle(900); + it('Set the chart title to an exceptionally large value (' + largeTitle.length + ')', function setALargeTitle() { + me.setTitleWithOptionsMenu(largeTitle); + browser.waitAndClick(me.selectors.options.button); + }); + + it('Confirm that the chart title has been changed', function confirmTheTitleWasSet() { + me.confirmChartTitleChange(largeTitle); + }); + }); + }); + }); + */ + } +}); diff --git a/tests/playwright/tests/myProfile/myProfile.spec.ts b/tests/playwright/tests/myProfile/myProfile.spec.ts new file mode 100644 index 0000000000..3c5ca3652a --- /dev/null +++ b/tests/playwright/tests/myProfile/myProfile.spec.ts @@ -0,0 +1,75 @@ +import {test, expect} from '@playwright/test'; +import {LoginPage} from "../../lib/login.page"; +import MyProfile from '../../lib/myProfile.page'; +let selectors = MyProfile.selectors; +import testing from '../../../ci/testing.json'; +import artifacts from "../helpers/artifacts"; +import globalConfig from '../../playwright.config'; +let roles = testing.role; +let expected = artifacts.getArtifact('myProfile'); + +test.describe('My Profile Tests', async () => { + let keys = Object.keys(roles); + for (let key in keys) { + if (keys.hasOwnProperty(key)) { + let role = keys[key]; + test(`${role} Tests`, async ({page}) => { + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login(roles[role].username, roles[role].password, (roles[role].givenname + " " + roles[role].surname)); + await test.step('Click the `My Profile` button', async () => { + await page.isVisible(MyProfile.toolbarButton); + await page.click(MyProfile.toolbarButton); + await page.isVisible(MyProfile.container); + }); + + await test.step('Check User Information', async () => { + await test.step('First Name', async () => { + // the normal user does not have a first name so the value returned from + // // the first name field is the default empty text ( 1 min, 50 max ). + let givenname = role !== 'usr' ? roles[role].givenname : '1 min, 50 max'; + let firstNameControl = selectors.general.user_information.first_name(); + + await page.isVisible(firstNameControl); + await expect(page.locator(firstNameControl)).toHaveValue(givenname); + }); + await test.step('Last Name', async () => { + let surname = roles[role].surname; + let lastNameControl = selectors.general.user_information.last_name(); + + await page.isVisible(lastNameControl); + await expect(page.locator(lastNameControl)).toHaveValue(surname); + }); + await test.step('E-Mail Address', async () => { + let username = roles[role].username; + // the admin user has a different email format than the rest of 'um. + let email = role !== 'mgr' ? `${username}@example.com` : `${username}@localhost`; + let emailControl = selectors.general.user_information.email_address(); + + await page.isVisible(emailControl); + await expect(page.locator(emailControl)).toHaveValue(email); + }); + await test.step('Top Role', async () => { + // We need to account for the different displays for users + // with center related acls and the others. + let expectedValue = role === 'cd' || role === 'cs' ? `${expected.top_roles[role]} - ${expected.organization.name}` : expected.top_roles[role]; + let topRoleControl = selectors.general.user_information.top_role(); + + await page.isVisible(topRoleControl); + const computed = await page.textContent(topRoleControl); + await expect(computed).toEqual(expectedValue); + }); + await test.step('Click the `Close` button', async () => { + const profile = new MyProfile(page, page.baseUrl); + let closeButton = await profile.button(selectors.buttons.close); + + await page.isVisible(closeButton); + await page.waitForLoadState(); + await page.click(closeButton); + await page.isHidden(MyProfile.container); + }); + }); + }); + } + } +}); diff --git a/tests/playwright/tests/reportGenerator/reportGenerator.spec.ts b/tests/playwright/tests/reportGenerator/reportGenerator.spec.ts new file mode 100644 index 0000000000..b1ab36eae2 --- /dev/null +++ b/tests/playwright/tests/reportGenerator/reportGenerator.spec.ts @@ -0,0 +1,1372 @@ +import {test, expect} from '@playwright/test'; +import {LoginPage} from "../../lib/login.page"; +import Usage from '../../lib/usageTab.page'; +import {MyReportsRow, AvailableChart, IncludedChart, ReportGenerator} from '../../lib/reportGenerator.page'; +import artifacts from "../helpers/artifacts"; +const expected = artifacts.getArtifact('reportGenerator'); +let XDMOD_REALMS = process.env.XDMOD_REALMS; +import globalConfig from '../../playwright.config'; +import testing from '../../../ci/testing.json'; +let roles = testing.role; + +test.describe('Report Generator', async () => { + // These dates correspond to the dates of the test job data. + const startDate = '2016-12-20'; + const endDate = '2017-01-01'; + + // Current date used in calculations below. + const currentDate = new Date(); + + // Calculate start and end dates of previous month. + const previousMonth = new Date(); + previousMonth.setDate(1); + previousMonth.setMonth(previousMonth.getMonth() - 1); + const previousMonthStartDate = previousMonth.toISOString().substring(0, 10); + + // Setting the date to day "0" of a month will result in the last + // day of the previous month. + previousMonth.setMonth(previousMonth.getMonth() + 1); + previousMonth.setDate(0); + const previousMonthEndDate = previousMonth.toISOString().substring(0, 10); + + // Calculate start and end dates of previous quarter. + const previousQuarter = new Date(); + previousQuarter.setDate(1); + const currentMonth = previousQuarter.getMonth(); + const monthModThree = currentMonth % 3; + previousQuarter.setMonth(currentMonth - 3 - monthModThree); + const previousQuarterStartDate = previousQuarter.toISOString().substring(0, 10); + previousQuarter.setMonth(previousQuarter.getMonth() + 3); + previousQuarter.setDate(0); + const previousQuarterEndDate = previousQuarter.toISOString().substring(0, 10); + + // Calculate start and end dates of previous year. + const previousYearStartDate = (currentDate.getFullYear() - 1) + '-01-01'; + const previousYearEndDate = (currentDate.getFullYear() - 1) + '-12-31'; + + // Calculate year-to-date start and end dates. + const yearToDateStartDate = currentDate.getFullYear() + '-01-01'; + const yearToDateEndDate = currentDate.toISOString().substring(0, 10); + + // Descriptive text displayed in empty fields of a newly created + // report. + const reportEmptyText = { + title: 'Optional, 50 max', + header: 'Optional, 40 max', + footer: 'Optional, 40 max' + }; + + // Default values expected when a new report is created. + const defaultReport = { + name: 'TAS Report 1', + chartsPerPage: 1, + schedule: 'Once', + deliveryFormat: 'PDF', + derivedFrom: 'Manual', + charts: [] + }; + + // These charts correspond to those that will be added to the + // "Available Charts" list from the usage tab. + const usageTabCharts = [ + { + realm: 'Jobs', + startDate: startDate, + endDate: endDate, + title: 'CPU Hours: Total', + drillDetails: '', + timeframeType: 'User Defined' + }, + { + realm: 'Jobs', + startDate: startDate, + endDate: endDate, + title: 'CPU Hours: Per Job', + drillDetails: '', + timeframeType: 'User Defined' + }, + { + realm: 'Jobs', + startDate: previousMonthStartDate, + endDate: previousMonthEndDate, + title: 'CPU Hours: Total', + drillDetails: '', + timeframeType: 'Previous month' + }, + { + realm: 'Jobs', + startDate: previousYearStartDate, + endDate: previousYearEndDate, + title: 'CPU Hours: Total', + drillDetails: '', + timeframeType: 'Previous year' + } + ]; + + const centerDirectorReportTemplates = [ + { + name: expected.centerdirector.report_templates[0].name, + chartsPerPage: 1, + schedule: 'Quarterly', + deliveryFormat: 'PDF', + charts: [ + { + realm: 'Jobs', + startDate: previousQuarterStartDate, + endDate: previousQuarterEndDate, + title: 'PREVIOUS QUARTER: Total CPU Hours and Jobs', + drillDetails: '', + timeframeType: 'Previous quarter' + }, + { + realm: 'Jobs', + startDate: yearToDateStartDate, + endDate: yearToDateEndDate, + title: 'YEAR TO DATE: Total CPU Hours and Jobs', + drillDetails: '', + timeframeType: 'Year to date' + }, + { + realm: 'Jobs', + startDate: previousQuarterStartDate, + endDate: previousQuarterEndDate, + title: 'PREVIOUS QUARTER: Percent Utilization', + drillDetails: '', + timeframeType: 'Previous quarter' + }, + { + realm: 'Jobs', + startDate: yearToDateStartDate, + endDate: yearToDateEndDate, + title: 'YEAR TO DATE: Percent Utilization', + drillDetails: '', + timeframeType: 'Year to date' + }, + { + realm: 'Jobs', + startDate: previousQuarterStartDate, + endDate: previousQuarterEndDate, + title: 'PREVIOUS QUARTER: CPU Hours and Number of Jobs - Top 20 Users', + drillDetails: '', + timeframeType: 'Previous quarter' + }, + { + realm: 'Jobs', + startDate: yearToDateStartDate, + endDate: yearToDateEndDate, + title: 'YEAR TO DATE: CPU Hours and Number of Jobs - Top 20 Users', + drillDetails: '', + timeframeType: 'Year to date' + }, + { + realm: 'Jobs', + startDate: previousQuarterStartDate, + endDate: previousQuarterEndDate, + title: 'PREVIOUS QUARTER: CPU Hours and Number of Jobs', + drillDetails: 'by Resource', + timeframeType: 'Previous quarter' + }, + { + realm: 'Jobs', + startDate: yearToDateStartDate, + endDate: yearToDateEndDate, + title: 'YEAR TO DATE: CPU Hours and Number of Jobs', + drillDetails: 'by Resource', + timeframeType: 'Year to date' + }, + { + realm: 'Jobs', + startDate: previousQuarterStartDate, + endDate: previousQuarterEndDate, + title: 'PREVIOUS QUARTER: CPU Hours, Number of Jobs, and Wait Time per Job', + drillDetails: 'by Job Size', + timeframeType: 'Previous quarter' + }, + { + realm: 'Jobs', + startDate: yearToDateStartDate, + endDate: yearToDateEndDate, + title: 'YEAR TO DATE: CPU Hours, Number of Jobs, and Wait Time per Job', + drillDetails: 'by Job Size', + timeframeType: 'Year to date' + }, + { + realm: 'Jobs', + startDate: previousQuarterStartDate, + endDate: previousQuarterEndDate, + title: 'PREVIOUS QUARTER: CPU Hours and User Expansion Factor', + drillDetails: 'by Job Size', + timeframeType: 'Previous quarter' + }, + { + realm: 'Jobs', + startDate: yearToDateStartDate, + endDate: yearToDateEndDate, + title: 'YEAR TO DATE: CPU Hours and User Expansion Factor', + drillDetails: 'by Job Size', + timeframeType: 'Year to date' + }, + { + realm: 'Jobs', + startDate: previousQuarterStartDate, + endDate: previousQuarterEndDate, + title: 'PREVIOUS QUARTER: Wait Hours per Job', + drillDetails: 'by Queue', + timeframeType: 'Previous quarter' + }, + { + realm: 'Jobs', + startDate: yearToDateStartDate, + endDate: yearToDateEndDate, + title: 'YEAR TO DATE: Wait Hours per Job', + drillDetails: 'by Queue', + timeframeType: 'Year to date' + }, + { + realm: 'Jobs', + startDate: previousQuarterStartDate, + endDate: previousQuarterEndDate, + title: 'PREVIOUS QUARTER: CPU Hours and Number of Jobs', + drillDetails: 'by Queue', + timeframeType: 'Previous quarter' + }, + { + realm: 'Jobs', + startDate: yearToDateStartDate, + endDate: yearToDateEndDate, + title: 'YEAR TO DATE: CPU Hours and Number of Jobs', + drillDetails: 'by Queue', + timeframeType: 'Year to date' + } + ] + } + ]; + + // Public user + test('Public user default report generator state', async ({page}) => { + await page.goto('/'); + await page.waitForLoadState(); + const reportGeneratorPage = new ReportGenerator(page); + await test.step('Report Generator is not enabled', async () => { + const isPageEnabled = await reportGeneratorPage.isEnabled(); + await expect(isPageEnabled).toBe(false); + }); + }); + + // User + test('Normal user default report generator state', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login(roles['usr'].username, roles['usr'].password, (roles['usr'].givenname + " " + roles['usr'].surname)); + await test.step('Report Generator is enabled', async () => { + await expect(reportGeneratorPage.isEnabled()).toBeTruthy(); + }); + await test.step('Select Report Generator tab', async () => { + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + }); + await test.step('No reports listed', async () => { + const first = await reportGeneratorPage.getMyReportsRows(); + await expect(first.length, 'No rows in the list of reports').toEqual(0); + }); + await test.step('No available charts listed', async () => { + const first = await reportGeneratorPage.getAvailableCharts(); + await expect(first.length, 'No charts in the list of available charts').toEqual(0); + }); + await test.step('No report templates available', async () => { + const isOptionEnabled = await reportGeneratorPage.isNewBasedOnEnabled(); + await expect(isOptionEnabled).toBe(false); + }); + }); + + // There are no tests for storage and cloud realms currently + if (XDMOD_REALMS.includes('jobs')) { + // PI + test('Principal investigator default report generator state', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login(roles['pi'].username, roles['pi'].password, (roles['pi'].givenname + " " + roles['pi'].surname)); + await test.step('Report Generator is enabled', async () => { + await expect(reportGeneratorPage.isEnabled()).toBeTruthy(); + }); + await test.step('Select Report Generator tab', async () => { + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + }); + await test.step('No reports listed', async () => { + const first = await reportGeneratorPage.getMyReportsRows(); + await expect(first.length, 'No rows in the list of reports').toEqual(0); + }); + await test.step('No available charts listed', async () => { + const first = await reportGeneratorPage.getAvailableCharts(); + await expect(first.length, 'No charts in the list of available charts').toEqual(0); + }); + await test.step('No report templates available', async () => { + const isOptionEnabled = await reportGeneratorPage.isNewBasedOnEnabled(); + await expect(isOptionEnabled).toBe(false); + }); + }); + } + + // Center staff + test('Center staff default report generator state', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login(roles['cs'].username, roles['cs'].password, (roles['cs'].givenname + " " + roles['cs'].surname)); + await test.step('Report Generator is enabled', async () => { + await expect(reportGeneratorPage.isEnabled()).toBeTruthy(); + }); + await test.step('Select Report Generator tab', async () => { + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + }); + await test.step('Reports listed', async () => { + const first = await reportGeneratorPage.getMyReportsRows(); + await expect(first.length, 'Rows in the list of reports').toEqual(0); + }); + await test.step('No available charts listed', async () => { + const first = await reportGeneratorPage.getAvailableCharts(); + await expect(first.length, 'No charts in the list of available charts').toEqual(0); + }); + await test.step('No report templates available', async () => { + const isOptionEnabled = await reportGeneratorPage.isNewBasedOnEnabled(); + await expect(isOptionEnabled).toEqual(expected.centerstaff.report_templates_available); + }); + }); + + // There are no tests for storage and cloud realms currently + if (XDMOD_REALMS.includes('jobs')) { + // Center director + test('Center director default report generator state', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login(roles['cd'].username, roles['cd'].password, (roles['cd'].givenname + " " + roles['cd'].surname)); + await test.step('Report Generator is enabled', async () => { + await expect(reportGeneratorPage.isEnabled()).toBeTruthy(); + }); + await test.step('Select Report Generator tab', async () => { + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + }); + + await test.step('No reports listed', async () => { + const first = await reportGeneratorPage.getMyReportsRows(); + await expect(first.length, 'No rows in the list of reports').toEqual(0); + }); + await test.step('No available charts listed', async () => { + const first = await reportGeneratorPage.getAvailableCharts(); + await expect(first.length, 'No charts in the list of available charts').toEqual(0); + }); + await test.step('Buttons are disabled', async () => { + const edit = await reportGeneratorPage.isEditSelectedReportsEnabled(); + await expect(edit, '"Edit" button is disabled').toBeFalsy(); + const preview = await reportGeneratorPage.isPreviewSelectedReportsEnabled(); + await expect(preview, '"Preview" button is disabled').toBeFalsy(); + const send = await reportGeneratorPage.isSendSelectedReportsEnabled(); + await expect(send, '"Send" button is disabled').toBeFalsy(); + const download = await reportGeneratorPage.isDownloadSelectedReportsEnabled(); + await expect(download, '"Download" button is disabled').toBeFalsy(); + const deleteSelect = await reportGeneratorPage.isDeleteSelectedReportsEnabled(); + await expect(deleteSelect, '"Delete" button is disabled').toBeFalsy(); + }); + }); + test('Make usage tab charts available in the Report Generator', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const usagePage = new Usage(page, baseUrl); + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login(roles['cd'].username, roles['cd'].password, (roles['cd'].givenname + " " + roles['cd'].surname)); + let index = 0; + for (const testChart of usageTabCharts){ + await test.step('Select Usage Tab', async () => { + await usagePage.selectTab(); + }); + await test.step(`Select "${testChart.title}" chart`, async () => { + const topNodeName = testChart.realm + ' ' + (testChart.drillDetails === '' ? 'Summary' : testChart.drillDetails); + await usagePage.selectChildTreeNode(topNodeName, testChart.title); + }); + await test.step('Set chart timeframe', async () => { + await page.click(reportGeneratorPage.selectors.configureTime.frameButton); + await page.click(reportGeneratorPage.selectors.configureTime.byTimeFrameName(testChart.timeframeType)); + await usagePage.setStartDate(testChart.startDate); + await usagePage.setEndDate(testChart.endDate); + await usagePage.refresh(); + }); + await test.step(`Make "${testChart.title}" chart available in the Report Generator`, async () => { + const checkbox = await page.$eval(usagePage.selectors.availableForReportCheckbox, node => node.checked); + if (!checkbox){ + await usagePage.makeCurrentChartAvailableForReport(); + } + }); + await test.step('Check available charts', async () => { + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + let charts; + for (let i = 0; i < 100; i++) { + charts = await reportGeneratorPage.getAvailableCharts(); + if (charts.length === (index + 1)) { + break; + } + } + await expect(charts.length, `${index + 1} chart(s) in the list of available charts`).toEqual(index + 1); + for (let i = 0; i <= index; ++i) { + const chart:AvailableChart = charts[i]; + const title = await chart.getTitle(); + await expect(title, 'Chart title is correct').toEqual(usageTabCharts[i].title); + const drillDetails = await chart.getDrillDetails(); + await expect(drillDetails, 'Drill details are correct').toEqual(usageTabCharts[i].drillDetails); + const dateDescription = await chart.getDateDescription(); + await expect(dateDescription, 'Date description is correct').toEqual(`${usageTabCharts[i].startDate} to ${usageTabCharts[i].endDate}`); + const timeframeType = await chart.getTimeframeType(); + await expect(timeframeType, 'Timeframe type is correct').toEqual(usageTabCharts[i].timeframeType); + } + }); + index+=1; + } + }); + test('Create report with default options', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login(roles['cd'].username, roles['cd'].password, (roles['cd'].givenname + " " + roles['cd'].surname)); + // Copy default report data for the report being tested. + const testReport = Object.assign({}, defaultReport); + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + const rows = await reportGeneratorPage.getMyReportsRows(); + if (rows != 0){ + await reportGeneratorPage.selectAllReports(); + await reportGeneratorPage.deleteSelectedReports(); + await reportGeneratorPage.confirmDeleteSelectedReports(); + } + await test.step('Create a new report', async () => { + await reportGeneratorPage.createNewReport(); + }); + await test.step('Check default values', async () => { + const name = await reportGeneratorPage.getReportName(); + await expect(name, 'Default report name').toEqual(defaultReport.name); + const numChartsPerPage = await reportGeneratorPage.getNumberOfChartsPerPage(); + await expect(numChartsPerPage, 'Default report number of charts per page').toEqual(defaultReport.chartsPerPage); + const schedule = await reportGeneratorPage.getSchedule(); + await expect(schedule, 'Default report schedule').toEqual(defaultReport.schedule); + const deliveryForm = await reportGeneratorPage.getDeliveryFormat(); + await expect(deliveryForm, 'Default report delivery format').toEqual(defaultReport.deliveryFormat); + const first = await reportGeneratorPage.getIncludedCharts(); + await expect(first.length, 'Default report chart count').toEqual(defaultReport.charts.length); + }); + await test.step('Empty text fields', async () => { + const title = await reportGeneratorPage.getReportTitle(); + await expect(title, 'Empty report title').toEqual(reportEmptyText.title); + const headerText = await reportGeneratorPage.getHeaderText(); + await expect(headerText, 'Empty report header').toEqual(reportEmptyText.header); + const footerText = await reportGeneratorPage.getFooterText(); + await expect(footerText, 'Empty report footer').toEqual(reportEmptyText.footer); + }); + await test.step('Add chart to report', async () => { + await reportGeneratorPage.addChartToReport(0); + + // Add chart to test report data to reflect the change made + // to the report. + await testReport.charts.push(usageTabCharts[0]); + }); + await test.step('Save report', async () => { + await reportGeneratorPage.saveReport(); + }); + await test.step('Return to "My Reports"', async () => { + await reportGeneratorPage.returnToMyReports(); + }); + await test.step('Check report list', async () => { + const reportRows = await reportGeneratorPage.getMyReportsRows(); + await expect(reportRows.length, '1 report in list').toEqual(1); + const report = reportRows[0]; + const name = await report.getName(); + await expect(name, 'Name is correct').toEqual(testReport.name); + const derivedFrom = await report.getDerivedFrom(); + await expect(derivedFrom, '"Derived From" is correct').toEqual(testReport.derivedFrom); + const schedule = await report.getSchedule(); + await expect(schedule, 'Schedule is correct').toEqual(testReport.schedule); + const deliveryform = await report.getDeliveryFormat(); + await expect(deliveryform, 'Delivery format is correct').toEqual(testReport.deliveryFormat); + const numOfCharts = await report.getNumberOfCharts(); + await expect(numOfCharts, 'Number of charts of is correct').toEqual(testReport.charts.length); + const numOfChartsPerPage = await report.getNumberOfChartsPerPage(); + await expect(numOfChartsPerPage, 'Number of charts per page is correct').toEqual(testReport.chartsPerPage); + }); + }); + + test('Create report and change options', async ({page}) => { + const testReport = { + name: 'Test Report 1', + title: 'Test Report', + header: 'Test header', + footer: 'Test footer', + chartsPerPage: 2, + schedule: 'Monthly', + deliveryFormat: 'Word Document', + derivedFrom: 'Manual', + charts: [] + }; + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login(roles['cd'].username, roles['cd'].password, (roles['cd'].givenname + " " + roles['cd'].surname)); + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + + await test.step('Create a new report', async () => { + await reportGeneratorPage.createNewReport(); + }); + await test.step('Set report name', async () => { + await reportGeneratorPage.setReportName(testReport.name); + }); + await test.step('Set report title', async () => { + await reportGeneratorPage.setReportTitle(testReport.title); + }); + await test.step('Set header text', async () => { + await reportGeneratorPage.setHeaderText(testReport.header); + }); + await test.step('Set footer text', async () => { + await reportGeneratorPage.setFooterText(testReport.footer); + }); + await test.step('Set number of charts per page', async () => { + await reportGeneratorPage.setNumberOfChartsPerPage(testReport.chartsPerPage); + }); + await test.step('Set schedule', async () => { + await reportGeneratorPage.setSchedule(testReport.schedule); + }); + await test.step('Set delivery format', async () => { + await reportGeneratorPage.setDeliveryFormat(testReport.deliveryFormat); + }); + await test.step('Add chart to report', async () => { + await reportGeneratorPage.addChartToReport(0); + + // Add chart to test report data to reflect the change made + // to the report. + testReport.charts.push(usageTabCharts[0]); + }); + await test.step('Edit the timeframe of the chart', async () => { + const charts = await reportGeneratorPage.getIncludedCharts(); + const chart:IncludedChart = charts[0]; + await chart.editTimeframe(); + }); + await test.step('Select "Specific" for the timeframe type', async () => { + await reportGeneratorPage.selectSpecificChartTimeframe(); + }); + await test.step('Clear start and end date', async () => { + await reportGeneratorPage.setSpecificChartTimeframeStartDate(''); + await reportGeneratorPage.setSpecificChartTimeframeEndDate(''); + }); + await test.step('Click the update button, expect error about start date', async () => { + await reportGeneratorPage.confirmEditTimeframeOfSelectedCharts(); + const msg = await reportGeneratorPage.getEditChartTimeframeErrorMessage(); + await expect(msg, 'Start date error').toEqual('Valid start date required'); + // Give time for msg to go away on its own + await page.locator('.overlay_message').waitFor({state:'detached'}); + }); + await test.step('Set a start date', async () => { + await reportGeneratorPage.setSpecificChartTimeframeStartDate(startDate); + }); + await test.step('Click the update button, expect error about end date', async () => { + await reportGeneratorPage.confirmEditTimeframeOfSelectedCharts(); + const msg = await reportGeneratorPage.getEditChartTimeframeErrorMessage(); + await expect(msg, 'End date error').toEqual('Valid end date required'); + // Give time for msg to go away on its own + await page.locator('.overlay_message').waitFor({state:'detached'}); + }); + await test.step('Set an end date', async () => { + await reportGeneratorPage.setSpecificChartTimeframeEndDate(endDate); + }); + await test.step('Click the update button', async () => { + await reportGeneratorPage.confirmEditTimeframeOfSelectedCharts(); + }); + await test.step('Verify that the date has been changed', async () => { + const charts = await reportGeneratorPage.getIncludedCharts(); + const chart:IncludedChart = charts[0]; + const timeframetype = await chart.getTimeframeType(); + await expect(timeframetype, 'Timeframe type').toEqual('User Defined'); + const date = await chart.getDateDescription(); + await expect(date, 'Date description').toEqual(startDate + ' to ' + endDate); + }); + await test.step('Save report', async () => { + await reportGeneratorPage.saveReport(); + }); + await test.step('Return to "My Reports"', async () => { + await reportGeneratorPage.returnToMyReports(); + //Give time for msg to go away + await page.locator(reportGeneratorPage.selectors.message.titleElement()).waitFor({state:'detached'}); + }); + await test.step('Check report list', async () => { + const reportRows = await reportGeneratorPage.getMyReportsRows(); + await expect(reportRows.length, '2 reports in list').toEqual(2); + const report = reportRows[1]; + const name = await report.getName(); + await expect(name, 'Name is correct').toEqual(testReport.name); + const derivedfrom = await report.getDerivedFrom(); + await expect(derivedfrom, '"Derived From" is correct').toEqual(testReport.derivedFrom); + const schedule = await report.getSchedule(); + await expect(schedule, 'Schedule is correct').toEqual(testReport.schedule); + const deliveryform = await report.getDeliveryFormat(); + await expect(deliveryform, 'Delivery format is correct').toEqual(testReport.deliveryFormat); + const numOfCharts = await report.getNumberOfCharts(); + await expect(numOfCharts, 'Number of charts of is correct').toEqual(testReport.charts.length); + const numOfChartsPerPage = await report.getNumberOfChartsPerPage(); + await expect(numOfChartsPerPage, 'Number of charts per page is correct').toEqual(testReport.chartsPerPage); + }); + await test.step('Edit report and compare values', async () => { + const reportRows = await reportGeneratorPage.getMyReportsRows(); + const row = reportRows[1]; + await row.doubleClick(); + await page.isVisible(reportGeneratorPage.selectors.reportDisplay); + const name = await reportGeneratorPage.getReportName(); + await expect(name, 'Report name is correct').toEqual(testReport.name); + const title = await reportGeneratorPage.getReportTitle(); + await expect(title, 'Report title is correct').toEqual(testReport.title); + const header = await reportGeneratorPage.getHeaderText(); + await expect(header, 'Header text is correct').toEqual(testReport.header); + const footer = await reportGeneratorPage.getFooterText(); + await expect(footer, 'Footer text is correct').toEqual(testReport.footer); + //reportGeneratorPage.getNumberOfChartsPerPage() method doesn't return proper value + //sometimes despite set correctly and in image, so alternatively a check is done here + if (testReport.chartsPerPage === 2){ + await page.isChecked(reportGeneratorPage.selectors.reportEditor.chartLayout.twoChartsPerPageRadioButton()); + } else if (testReport.chartsPerPage === 1) { + await page.isChecked(reportGeneratorPage.selectors.reportEditor.chartLayout.oneChartPerPageRadioButton()); + } else { + throw new Error('No charts per page option selected'); + } + const schedule = await reportGeneratorPage.getSchedule(); + await expect(schedule, 'Schedule is correct').toEqual(testReport.schedule); + const deliveryform = await reportGeneratorPage.getDeliveryFormat(); + await expect(deliveryform, 'Delivery format is correct').toEqual(testReport.deliveryFormat); + await reportGeneratorPage.returnToMyReports(); + }); + }); + + test('Edit report and "Save As"', async ({page}) => { + const testReport = { + name: 'Copied Report', + derivedFrom: '', + schedule: '', + deliveryFormat: '', + numberOfCharts: 0, + chartsPerPage: 0, + header: '', + }; + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login(roles['cd'].username, roles['cd'].password, (roles['cd'].givenname + " " + roles['cd'].surname)); + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + await test.step('Store data for report that will be copied', async () => { + //to ensure reportGeneratorPage fully loaded + await reportGeneratorPage.fullyLoaded(); + //continue test + const selector = await reportGeneratorPage.getMyReportsRows(); + const reportRow:MyReportsRow = selector[1]; + testReport.derivedFrom = await reportRow.getDerivedFrom(); + testReport.schedule = await reportRow.getSchedule(); + testReport.deliveryFormat = await reportRow.getDeliveryFormat(); + testReport.numberOfCharts = await reportRow.getNumberOfCharts(); + testReport.chartsPerPage = await reportRow.getNumberOfChartsPerPage(); + }); + await test.step('Edit report', async () => { + const reportRows = await reportGeneratorPage.getMyReportsRows(); + await reportRows[1].doubleClick(); + }); + await test.step('Click "Save As" and set report name', async () => { + await reportGeneratorPage.saveReportAs(testReport.name); + }); + await test.step('Click "Save" in "Save As" window', async () => { + await reportGeneratorPage.confirmSaveReportAs(); + }); + await test.step('Check copied report name', async () => { + const name = await reportGeneratorPage.getReportName(); + await expect(name, 'Report name is correct').toEqual(testReport.name); + //Give time for msg window to go away + await page.locator(reportGeneratorPage.selectors.message.titleElement()).waitFor({state:'detached'}); + }); + await test.step('Edit copied report', async () => { + testReport.header = 'Header for copied report'; + await reportGeneratorPage.setHeaderText(testReport.header); + testReport.chartsPerPage = testReport.chartsPerPage === 1 ? 2 : 1; + await reportGeneratorPage.setNumberOfChartsPerPage(testReport.chartsPerPage); + //Give time for msg window to go away + await page.locator(reportGeneratorPage.selectors.message.titleElement()).waitFor({state:'detached'}); + }); + await test.step('Save report', async () => { + await reportGeneratorPage.saveReport(); + //Give time for msg window to go away + await page.locator(reportGeneratorPage.selectors.message.titleElement()).waitFor({state:'detached'}); + }); + await test.step('Return to "My Reports"', async () => { + await reportGeneratorPage.returnToMyReports(); + }); + await test.step('Check report list', async () => { + const reportRows = await reportGeneratorPage.getMyReportsRows(); + await expect(reportRows.length, '3 reports in list').toEqual(3); + const report:MyReportsRow = reportRows[2]; + const name = await report.getName(); + await expect(name, 'Name is correct').toEqual(testReport.name); + const derivedfrom = await report.getDerivedFrom(); + await expect(derivedfrom, '"Derived From" is correct').toEqual(testReport.derivedFrom); + const schedule = await report.getSchedule(); + await expect(schedule, 'Schedule is correct').toEqual(testReport.schedule); + const deliveryform = await report.getDeliveryFormat(); + await expect(deliveryform, 'Delivery format is correct').toEqual(testReport.deliveryFormat); + const numOfCharts = await report.getNumberOfCharts(); + await expect(numOfCharts, 'Number of charts of is correct').toEqual(testReport.numberOfCharts); + const numOfChartsPerPage = await report.getNumberOfChartsPerPage(); + await expect(numOfChartsPerPage, 'Number of charts per page is correct').toEqual(testReport.chartsPerPage); + }); + await test.step('Edit copied report and compare values', async () => { + const reportRows = await reportGeneratorPage.getMyReportsRows(); + await reportRows[2].doubleClick(); + const header = await reportGeneratorPage.getHeaderText(); + await expect(header, 'Header text is correct').toEqual(testReport.header); + const numOfChartsPerPage = await reportGeneratorPage.getNumberOfChartsPerPage(); + await expect(numOfChartsPerPage, 'Number of charts per page is correct').toEqual(testReport.chartsPerPage); + }); + await test.step('Return to "My Reports"', async () => { + await reportGeneratorPage.returnToMyReports(); + }); + }); + + test('Edit report and make changes', async ({page}) => { + // The row index of the report that will be edited. + const reportIndex = 2; + + // The data that will be changed in the report. + const testReport = { + name: 'Edited Test Report 1', + title: 'Edited Test Report', + header: 'Edited header', + footer: 'Edited footer', + chartsPerPage: 1, + schedule: 'Quarterly', + deliveryFormat: 'PDF', + derivedFrom: 'Manual', + // The report being edited contains one chart already, but this + // test does not check it's contents. + charts: [{}] + }; + + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login(roles['cd'].username, roles['cd'].password, (roles['cd'].givenname + " " + roles['cd'].surname)); + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + + await test.step('Open the report', async () => { + //to ensure reportGeneratorPage fully loaded + await reportGeneratorPage.fullyLoaded(); + //continue test + const rows = await reportGeneratorPage.getMyReportsRows(); + const report:MyReportsRow = rows[reportIndex]; + await report.doubleClick(); + }); + await test.step('Set report name', async () => { + await reportGeneratorPage.setReportName(testReport.name); + }); + await test.step('Set report title', async () => { + await reportGeneratorPage.setReportTitle(testReport.title); + }); + await test.step('Set header text', async () => { + await reportGeneratorPage.setHeaderText(testReport.header); + }); + await test.step('Set footer text', async () => { + await reportGeneratorPage.setFooterText(testReport.footer); + }); + await test.step('Set number of charts per page', async () => { + await reportGeneratorPage.setNumberOfChartsPerPage(testReport.chartsPerPage); + }); + await test.step('Set schedule', async () => { + await reportGeneratorPage.setSchedule(testReport.schedule); + }); + await test.step('Set delivery format', async () => { + await reportGeneratorPage.setDeliveryFormat(testReport.deliveryFormat); + }); + await test.step('Add chart to report', async () => { + await reportGeneratorPage.addChartToReport(1); + + // Add chart to test report data to reflect the change made + // to the report. + testReport.charts.push(usageTabCharts[1]); + }); + await test.step('Save report', async () => { + await reportGeneratorPage.saveReport(); + }); + await test.step('Return to "My Reports"', async () => { + await reportGeneratorPage.returnToMyReports(); + }); + await test.step('Check report list', async () => { + const reportRows = await reportGeneratorPage.getMyReportsRows(); + await expect(reportRows.length, '3 reports in list').toEqual(3); + const reportRow:MyReportsRow = reportRows[reportIndex]; + const name = await reportRow.getName(); + await expect(name, 'Name is correct').toEqual(testReport.name); + const derivedFrom = await reportRow.getDerivedFrom(); + await expect(derivedFrom, '"Derived From" is correct').toEqual(testReport.derivedFrom); + const schedule = await reportRow.getSchedule(); + await expect(schedule, 'Schedule is correct').toEqual(testReport.schedule); + const deliveryForm = await reportRow.getDeliveryFormat(); + await expect(deliveryForm, 'Delivery format is correct').toEqual(testReport.deliveryFormat); + const numOfCharts = await reportRow.getNumberOfCharts(); + await expect(numOfCharts, 'Number of charts of is correct').toEqual(testReport.charts.length); + const numOfChartsPerPage = await reportRow.getNumberOfChartsPerPage(); + await expect(numOfChartsPerPage, 'Number of charts per page is correct').toEqual(testReport.chartsPerPage); + }); + await test.step('Edit report and compare values', async () => { + const rows = await reportGeneratorPage.getMyReportsRows(); + await rows[reportIndex].doubleClick(); + const name = await reportGeneratorPage.getReportName(); + await expect(name, 'Report name is correct').toEqual(testReport.name); + const title = await reportGeneratorPage.getReportTitle(); + await expect(title, 'Report title is correct').toEqual(testReport.title); + const header = await reportGeneratorPage.getHeaderText(); + await expect(header, 'Header text is correct').toEqual(testReport.header); + const footer = await reportGeneratorPage.getFooterText(); + await expect(footer, 'Footer text is correct').toEqual(testReport.footer); + const numOfChartsPerPage = await reportGeneratorPage.getNumberOfChartsPerPage(); + await expect(numOfChartsPerPage, 'Number of charts per page is correct').toEqual(testReport.chartsPerPage); + const schedule = await reportGeneratorPage.getSchedule(); + await expect(schedule, 'Schedule is correct').toEqual(testReport.schedule); + const deliveryform = await reportGeneratorPage.getDeliveryFormat(); + await expect(deliveryform, 'Delivery format is correct').toEqual(testReport.deliveryFormat); + await reportGeneratorPage.returnToMyReports(); + }); + }); + + test('Create report from template', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login('centerdirector', 'centerdirector', 'Reed Bunting'); + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + + await test.step('Click "New Based On"', async () => { + //to ensure reportGeneratorPage fully loaded + await reportGeneratorPage.fullyLoaded(); + //continue test + await reportGeneratorPage.clickNewBasedOn(); + await page.locator(reportGeneratorPage.selectors.myReports.toolbar.newBasedOnMenu()).waitFor({state:'visible'}); + }); + await test.step('Check list of report templates', async () => { + const reportTemplateNames = await reportGeneratorPage.getReportTemplateNames(); + let i = 0; + for (const reportTemplateName of reportTemplateNames){ + await expect(reportTemplateName, 'Report template ' + i).toEqual(centerDirectorReportTemplates[i].name); + } + }); + await test.step('Click "New Based On" to close menu', async () => { + // Close the menu so that it can be re-opened below. + await reportGeneratorPage.clickNewBasedOn(); + if (page.locator(reportGeneratorPage.selectors.myReports.toolbar.newBasedOnMenu()).isVisible()){ + await reportGeneratorPage.clickNewBasedOn(); + } + await expect(page.locator(reportGeneratorPage.selectors.myReports.toolbar.newBasedOnMenu())).toBeHidden(); + // mouse is stuck hovering over the "New Based On" button, + // so another area on the page, or the "Report Generator" + // tab is clicked and the delay is to give the page time + await page.click(reportGeneratorPage.selectors.tab(), {delay:250}); + }); + let report_template_index = 0; + let reportIndex = 3; + const template = centerDirectorReportTemplates[0]; + await test.step('Click "New Based On"', async () => { + await reportGeneratorPage.clickNewBasedOn(); + await page.locator(reportGeneratorPage.selectors.myReports.toolbar.newBasedOnMenu()).waitFor({state:'visible'}); + }); + await test.step(`Select "${template.name}"`, async () => { + await reportGeneratorPage.selectNewBasedOnTemplate(template.name, expected.centerdirector.center); + }); + await test.step('Check list of reports', async () => { + const reportRows = await reportGeneratorPage.getMyReportsRows(); + await expect(reportRows.length, 'New report added').toEqual(reportIndex + expected.centerdirector.report_templates[report_template_index].reports_created); + const report:MyReportsRow = reportRows[reportIndex]; + const name = await report.getName(); + await expect(name, 'Name is correct').toEqual(expected.centerdirector.report_templates[report_template_index].created_name + ' 1'); + const derivedFrom = await report.getDerivedFrom(); + await expect(derivedFrom, '"Derived From" is correct').toEqual(template.name); + const schedule = await report.getSchedule(); + await expect(schedule, 'Schedule is correct').toEqual(template.schedule); + const deliveryform = await report.getDeliveryFormat(); + await expect(deliveryform, 'Delivery format is correct').toEqual(expected.centerdirector.report_templates[report_template_index].delivery_format); + const numOfCharts = await report.getNumberOfCharts(); + await expect(numOfCharts, 'Number of charts of is correct').toEqual(expected.centerdirector.report_templates[report_template_index].created_reports_count); + const numOfChartsPerPage = await report.getNumberOfChartsPerPage(); + await expect(numOfChartsPerPage, 'Number of charts per page is correct').toEqual(template.chartsPerPage); + }); + await test.step('Edit report based on template', async () => { + const rows = await reportGeneratorPage.getMyReportsRows(); + const row:MyReportsRow = rows[reportIndex]; + await row.doubleClick(); + }); + await test.step('Check charts', async () => { + const templateCharts = await reportGeneratorPage.getCharts( + 'centerdirector', + report_template_index, + { + startDate: startDate, + endDate: endDate, + previousMonthStartDate: previousMonthStartDate, + previousMonthEndDate: previousMonthEndDate, + previousQuarterStartDate: previousQuarterStartDate, + previousQuarterEndDate: previousQuarterEndDate, + previousYearStartDate: previousYearStartDate, + previousYearEndDate: previousYearEndDate, + yearToDateStartDate: yearToDateStartDate, + yearToDateEndDate: yearToDateEndDate + } + ); + const reportCharts = await reportGeneratorPage.getIncludedCharts(); + let i = 0; + for (const charts of reportCharts){ + const chart:AvailableChart = charts[i]; + const templateChart = templateCharts[i]; + const title = await chart.getTitle(); + await expect(title, 'Chart title').toEqual(templateChart.title); + const drillDetails = await chart.getDrillDetails(); + await expect(drillDetails, 'Drill details').toEqual(templateChart.drillDetails); + const timeframetype = await chart.getTimeframeType(); + await expect(timeframetype, 'Timeframe type').toEqual(templateChart.timeframeType); + const dateDescription = await chart.getDateDescription(); + await expect(dateDescription, 'Date description').toEqual(templateChart.startDate + ' to ' + templateChart.endDate); + i +=1; + } + }); + await test.step('Return to "My Reports"', async () => { + await reportGeneratorPage.returnToMyReports(); + }); + report_template_index += 1; + }); + + test('Preview report', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login('centerdirector', 'centerdirector', 'Reed Bunting'); + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + await test.step('Select a report', async () => { + //to ensure reportGeneratorPage fully loaded + await reportGeneratorPage.fullyLoaded(); + //continue test + const rows = await reportGeneratorPage.getMyReportsRows(); + const report:MyReportsRow = rows[0]; + await report.click(); + }); + await test.step('Preview selected report', async () => { + await reportGeneratorPage.previewSelectedReports(); + }); + await test.step('Return to reports overview', async () => { + await reportGeneratorPage.returnToReportsOverview(); + }); + await test.step('Deselect reports', async () => { + await reportGeneratorPage.deselectAllReports(); + }); + }); + + test('Download report', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login('centerdirector', 'centerdirector', 'Reed Bunting'); + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + await test.step('Select a report', async () => { + //to ensure reportGeneratorPage fully loaded + await reportGeneratorPage.fullyLoaded(); + //continue test + const rows = await reportGeneratorPage.getMyReportsRows(); + const report:MyReportsRow = rows[0]; + await report.click(); + }); + await test.step('Click "Download" button', async () => { + await reportGeneratorPage.downloadSelectedReports(); + }); + await test.step('Click "As PDF"', async () => { + await reportGeneratorPage.downloadSelectedReportsAsPdf(); + }); + await test.step('Close "Report Built" window', async () => { + await reportGeneratorPage.closeReportBuiltWindow(); + }); + await test.step('Click "Download" button', async () => { + await reportGeneratorPage.downloadSelectedReports(); + }); + await test.step('Click "As Word Document"', async () => { + await reportGeneratorPage.downloadSelectedReportsAsWordDocument(); + }); + await test.step('Close "Report Built" window', async () => { + await reportGeneratorPage.closeReportBuiltWindow(); + }); + await test.step('Deselect reports', async () => { + await reportGeneratorPage.deselectAllReports(); + }); + }); + + test('Select reports', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login('centerdirector', 'centerdirector', 'Reed Bunting'); + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + await test.step('Select all', async () => { + //to ensure reportGeneratorPage fully loaded + await reportGeneratorPage.fullyLoaded(); + //continue test + await reportGeneratorPage.selectAllReports(); + const reportRows = await reportGeneratorPage.getMyReportsRows(); + let i = 0; + for (const row:MyReportsRow of reportRows){ + await expect(row.isSelected(), `Row ${i} is selected`).toBeTruthy(); + i+=1; + } + }); + await test.step('Select none', async () => { + await reportGeneratorPage.deselectAllReports(); + const reportRows = await reportGeneratorPage.getMyReportsRows(); + let i = 0; + for (const row:MyReportsRow of reportRows){ + const isRowSelected = await row.isSelected(); + await expect(isRowSelected, `Row ${i} is not selected`).toBeFalsy(); + i+=1; + } + }); + await test.step('Invert selection', async () => { + // Select one row then invert selection. + const row:MyReportsRow = (await reportGeneratorPage.getMyReportsRows())[1]; + await row.toggleSelection(); + const first = await reportGeneratorPage.getMyReportsRows(); + const selectedStatus = await Promise.all(first.map(row => row.isSelected())); + await reportGeneratorPage.invertReportSelection(); + const reportRows = await reportGeneratorPage.getMyReportsRows(); + let i = 0; + for (const row:MyReportsRow of reportRows){ + const isRowSelected = await row.isSelected(); + await expect(isRowSelected, `Row ${i} has been inverted`).toEqual(!selectedStatus[i]); + i +=1; + } + }); + }); + + test('Attempt to edit multiple reports from "My Reports"', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login('centerdirector', 'centerdirector', 'Reed Bunting'); + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + await test.step('Select reports', async () => { + //to ensure reportGeneratorPage fully loaded + await reportGeneratorPage.fullyLoaded(); + //continue test + await reportGeneratorPage.selectAllReports(); + }); + await test.step('Edit reports (should not be possible)', async () => { + const isReportEnabled = await reportGeneratorPage.isEditSelectedReportsEnabled(); + await expect(isReportEnabled, '"Edit" button is disabled').toBeFalsy(); + }); + await test.step('Deselect reports', async () => { + await reportGeneratorPage.deselectAllReports(); + }); + }); + + test('Attempt to preview multiple reports from "My Reports"', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login('centerdirector', 'centerdirector', 'Reed Bunting'); + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + await test.step('Select reports', async () => { + //to ensure reportGeneratorPage fully loaded + await reportGeneratorPage.fullyLoaded(); + //continue test + await reportGeneratorPage.selectAllReports(); + }); + await test.step('Preview reports (should not be possible)', async () => { + const isReportEnabled = await reportGeneratorPage.isPreviewSelectedReportsEnabled(); + await expect(isReportEnabled, '"Preview" button is disabled').toBeFalsy(); + }); + await test.step('Deselect reports', async () => { + await reportGeneratorPage.deselectAllReports(); + }); + }); + + test('Attempt to send multiple reports from "My Reports"', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login('centerdirector', 'centerdirector', 'Reed Bunting'); + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + await test.step('Select reports', async () => { + //to ensure reportGeneratorPage fully loaded + await reportGeneratorPage.fullyLoaded(); + //continue test + await reportGeneratorPage.selectAllReports(); + }); + await test.step('Send reports (should not be possible)', async () => { + const isReportEnabled = await reportGeneratorPage.isSendSelectedReportsEnabled(); + await expect(isReportEnabled, '"Send" button is disabled').toBeFalsy(); + }); + await test.step('Deselect reports', async () => { + await reportGeneratorPage.deselectAllReports(); + }); + }); + + test('Attempt to download multiple reports from "My Reports"', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login('centerdirector', 'centerdirector', 'Reed Bunting'); + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + await test.step('Select reports', async () => { + //to ensure reportGeneratorPage fully loaded + await reportGeneratorPage.fullyLoaded(); + //continue test + await reportGeneratorPage.selectAllReports(); + }); + await test.step('Download reports (should not be possible)', async () => { + const isReportEnabled = await reportGeneratorPage.isDownloadSelectedReportsEnabled(); + await expect(isReportEnabled, '"Download" button is disabled').toBeFalsy(); + }); + await test.step('Deselect reports', async () => { + await reportGeneratorPage.deselectAllReports(); + }); + }); + + test('Delete report from "My Reports"', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login('centerdirector', 'centerdirector', 'Reed Bunting'); + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + let reportCount; + + await test.step('Select report', async () => { + //to ensure reportGeneratorPage fully loaded + await reportGeneratorPage.fullyLoaded(); + //continue test + const reports = await reportGeneratorPage.getMyReportsRows(); + reportCount = reports.length; + await reports[0].click(); + await expect(reports[0].isSelected(), 'Report is selected').toBeTruthy(); + }); + await test.step('Click delete button', async () => { + await reportGeneratorPage.deleteSelectedReports(); + }); + await test.step('Cancel deletion', async () => { + await reportGeneratorPage.cancelDeleteSelectedReports(); + const reports = await reportGeneratorPage.getMyReportsRows(); + await expect(reports.length, 'Report count has not changed').toEqual(reportCount); + await expect(reports[0].isSelected(), 'Report is still selected').toBeTruthy(); + }); + await test.step('Click delete button', async () => { + await reportGeneratorPage.deleteSelectedReports(); + }); + await test.step('Confirm deletion', async () => { + await reportGeneratorPage.confirmDeleteSelectedReports(); + reportCount-=1; + }); + await test.step('Check list of reports', async () => { + const first = await reportGeneratorPage.getMyReportsRows(); + await expect(first.length).toEqual(reportCount); + }); + }); + + test('Select charts listed in "Available Charts"', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login('centerdirector', 'centerdirector', 'Reed Bunting'); + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + await test.step('Select all', async () => { + //to ensure reportGeneratorPage fully loaded + await reportGeneratorPage.fullyLoaded(); + //continue test + await reportGeneratorPage.selectAllAvailableCharts(); + const reportCharts = await reportGeneratorPage.getAvailableCharts(); + let i = 0; + for (const chart:AvailableChart of reportCharts){ + await expect(chart.isSelected(), `Chart ${i} is selected`).toBeTruthy(); + i+=1; + } + }); + await test.step('Select none', async () => { + await reportGeneratorPage.deselectAllAvailableCharts(); + const reportCharts = await reportGeneratorPage.getAvailableCharts(); + let i = 0; + for (const chart:AvailableChart of reportCharts){ + const isChartSelected = await chart.isSelected(); + await expect(isChartSelected, `Chart ${i} is not selected`).toBeFalsy(); + i+=1; + } + }); + await test.step('Invert selection', async () => { + // Select one chart then invert selection. + const charts = await reportGeneratorPage.getAvailableCharts(); + const chart:AvailableChart = charts[1]; + await chart.toggleSelection(); + const first = await reportGeneratorPage.getAvailableCharts(); + const selectedStatus = await Promise.all(first.map(chart => chart.isSelected())); + await reportGeneratorPage.invertAvailableChartsSelection(); + const reportCharts = await reportGeneratorPage.getAvailableCharts(); + let i = 0; + for (const chart:AvailableChart of reportCharts){ + const isChartSelected = await chart.isSelected(); + await expect(isChartSelected, `Chart ${i} selection has been inverted`).toEqual(!selectedStatus[i]); + i+=1; + } + }); + }); + + // Removes all but the first chart. + test('Remove charts from "Available Charts"', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login('centerdirector', 'centerdirector', 'Reed Bunting'); + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + let chartCount; + await test.step('Select all charts', async () => { + //to ensure reportGeneratorPage fully loaded + await reportGeneratorPage.fullyLoaded(); + //continue test + const first = await reportGeneratorPage.getAvailableCharts(); + chartCount = first.length; + await reportGeneratorPage.selectAllAvailableCharts(); + }); + await test.step('Deselect first chart', async () => { + const charts = await reportGeneratorPage.getAvailableCharts(); + const chart:AvailableChart = charts[0]; + await chart.toggleSelection(); + }); + await test.step('Click delete button', async () => { + await reportGeneratorPage.deleteSelectedAvailableCharts(); + }); + await test.step('Cancel deletion', async () => { + await reportGeneratorPage.cancelDeleteSelectedAvailableCharts(); + }); + await test.step('Confirm that no charts were removed', async () => { + const first = await reportGeneratorPage.getAvailableCharts(); + await expect(first.length, 'Chart count not changed').toEqual(chartCount); + }); + await test.step('Click delete button again', async () => { + await reportGeneratorPage.deleteSelectedAvailableCharts(); + }); + await test.step('Confirm deletion', async () => { + await reportGeneratorPage.confirmDeleteSelectedAvailableCharts(); + }); + await test.step('Confirm that charts were removed', async () => { + const first = await reportGeneratorPage.getAvailableCharts(); + await expect(first.length, 'All but one chart removed').toEqual(1); + }); + }); + + // Removes the first chart. + test('Remove chart from Report Generator from the Usage tab', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + const usagePage = new Usage(page, page.baseUrl); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login('centerdirector', 'centerdirector', 'Reed Bunting'); + await test.step('Click on the "Usage" tab', async () => { + await usagePage.selectTab(); + }); + await test.step('Set date range', async () => { + await page.click(reportGeneratorPage.selectors.configureTime.frameButton); + await page.click(reportGeneratorPage.selectors.configureTime.byTimeFrameName("User Defined")); + await usagePage.setStartDate(usageTabCharts[0].startDate); + await usagePage.setEndDate(usageTabCharts[0].endDate); + await usagePage.refresh(); + }); + await test.step('Make chart unavailable', async () => { + await usagePage.makeCurrentChartUnavailableForReport(); + }); + await test.step('Select Report Generator tab', async () => { + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + }); + await test.step('No available charts listed', async () => { + const first = await reportGeneratorPage.getAvailableCharts(); + await expect(first.length, 'No charts in the list of available charts').toEqual(0); + }); + }); + + test('Delete multiple reports from "My Reports"', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login('centerdirector', 'centerdirector', 'Reed Bunting'); + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + await test.step('Select reports', async () => { + //to ensure reportGeneratorPage fully loaded + await reportGeneratorPage.fullyLoaded(); + //continue test + await reportGeneratorPage.selectAllReports(); + }); + await test.step('Delete reports', async () => { + await reportGeneratorPage.deleteSelectedReports(); + await reportGeneratorPage.confirmDeleteSelectedReports(); + const first = await reportGeneratorPage.getMyReportsRows(); + await expect(first.length).toEqual(0); + }); + }); + + // These tests confirm that the report generator state is the same + // at the end of the tests as it was at the beginning. + test('Confirm that there are no reports or available charts', async ({page}) => { + //Generate pages + const reportGeneratorPage = new ReportGenerator(page); + let baseUrl = globalConfig.use.baseURL; + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login('centerdirector', 'centerdirector', 'Reed Bunting'); + await reportGeneratorPage.selectTab(); + await reportGeneratorPage.waitForMyReportsPanelVisible(); + await test.step('No reports listed', async () => { + const first = await reportGeneratorPage.getMyReportsRows(); + await expect(first.length, 'No rows in the list of reports').toEqual(0); + }); + await test.step('No available charts listed', async () => { + const first = await reportGeneratorPage.getAvailableCharts(); + await expect(first.length, 'No charts in the list of available charts').toEqual(0); + }); + }); + } +}); diff --git a/tests/playwright/tests/usageTab/usageTab.spec.ts b/tests/playwright/tests/usageTab/usageTab.spec.ts new file mode 100644 index 0000000000..8ec05c914b --- /dev/null +++ b/tests/playwright/tests/usageTab/usageTab.spec.ts @@ -0,0 +1,116 @@ +import {test, expect} from '@playwright/test'; +import Usage from "../../lib/usageTab.page"; +import {LoginPage} from "../../lib/login.page"; +import artifacts from "../helpers/artifacts"; +let expected = artifacts.getArtifact('usage'); +let XDMOD_REALMS = process.env.XDMOD_REALMS; +import globalConfig from '../../playwright.config'; +import testing from '../../../ci/testing.json'; +let roles = testing.role; + +test.describe('Usage', async () => { + const baselineDate={ + start: '2016-12-25', + end: '2017-01-02' + }; + // There are no tests for storage and cloud realms currently + if (XDMOD_REALMS.includes('jobs')){ + test('(Center Director)', async ({page}) => { + let baseUrl = globalConfig.use.baseURL; + const usg = new Usage(page, baseUrl); + const loginPage = new LoginPage(page, baseUrl, page.sso); + await loginPage.login(roles['cd'].username, roles['cd'].password, (roles['cd'].givenname + " " + roles['cd'].surname), {timeout:10000}); + await test.step('Select "Usage" tab', async () => { + await usg.selectTab(); + await expect(page.locator(usg.selectors.chart)).toBeVisible(); + await expect(page.locator(usg.selectors.mask)).toBeHidden(); + await expect(page.locator(usg.selectors.chartByTitle(expected.centerdirector.default_chart_title, true))).toBeVisible(); + + // by refreshing we ensure that there are not stale legend-item elements + // on the page. + await page.reload(); + await expect(page.locator(usg.selectors.chartByTitle(expected.centerdirector.default_chart_title, true))).toBeVisible(); + }); + await test.step('Set a known start and end date', async () => { + await usg.setStartDate(baselineDate.start); + await usg.setEndDate(baselineDate.end); + await usg.refresh(); + await expect(page.locator(usg.selectors.chartXAxisLabelByName(baselineDate.start))).toBeVisible(); + }); + await test.step('Select Job Size Min', async () =>{ + await expect(page.locator(usg.selectors.treeNodeByPath('Jobs Summary', 'Job Size: Min'))).toBeVisible(); + await page.locator(usg.selectors.treeNodeByPath('Jobs Summary', 'Job Size: Min')).click(); + await expect(page.locator(usg.selectors.chartByTitle('Job Size: Min (Core Count)', true))).toBeVisible(); + await usg.checkLegendText(expected.centerdirector.legend); + + //Check to make sure that the 'Std Err' display menu items are disabled. + await expect(page.locator(usg.selectors.toolbarButtonByText('Display'))).toBeVisible(); + await page.locator(usg.selectors.toolbarButtonByText('Display')).click(); + const menuLabels = ['Std Err Bars', 'Std Err Labels']; + for (const menuLabel of menuLabels){ + await expect(page.locator(usg.selectors.displayMenuItemByText(menuLabel))).toBeVisible(); + const check = await usg.toolbarMenuItemIsEnabled(menuLabel); + await expect(check).toBe(false); + } + }); + await test.step('View CPU Hours by System Username', async () => { + await expect(page.locator(usg.selectors.unfoldTreeNodeByName('Jobs Summary'))).toBeVisible(); + await page.locator(usg.selectors.unfoldTreeNodeByName('Jobs Summary')).click(); + await expect(page.locator(usg.selectors.unfoldTreeNodeByName('Jobs by System Username'))).toBeVisible(); + await page.locator(usg.selectors.unfoldTreeNodeByName('Jobs by System Username')).click(); + await expect(page.locator(usg.selectors.treeNodeByPath('Jobs by System Username', 'CPU Hours: Per Job'))).toBeVisible(); + await page.locator(usg.selectors.treeNodeByPath('Jobs by System Username', 'CPU Hours: Per Job')).click(); + await expect(page.locator(usg.selectors.chartByTitle('CPU Hours: Per Job: by System Username', true))).toBeVisible(); + }); + await test.step('View CPU Hours: Per Job', async () => { + await expect(page.locator(usg.selectors.unfoldTreeNodeByName('Jobs Summary', 'CPU Hours: Per Job'))).toBeVisible(); + await page.locator(usg.selectors.unfoldTreeNodeByName('Jobs Summary', 'CPU Hours: Per Job')).click(); + await expect(page.locator(usg.selectors.chartByTitle('CPU Hours: Per Job', true))).toBeVisible(); + + ///Check to make sure that the 'Std Err' display menu items are disabled. + await expect(page.locator(usg.selectors.toolbarButtonByText('Display'))).toBeVisible(); + await page.locator(usg.selectors.toolbarButtonByText('Display')).click(); + const menuLabels = ['Std Err Bars', 'Std Err Labels']; + for (const menuLabel of menuLabels){ + await expect(page.locator(usg.selectors.displayMenuItemByText(menuLabel))).toBeVisible(); + const check = await usg.toolbarMenuItemIsEnabled(menuLabel); + await expect(check).toBe(true); + } + }); + }); + test('(Public User)', async ({page}) => { + await page.goto('/'); + await page.waitForLoadState(); + const usg = new Usage(page, page.baseUrl); + await page.locator(usg.selectors.signInLink).waitFor({state:'visible'}); + await test.step('Selected', async () => { + await usg.selectTab(); + await page.locator(usg.selectors.chartByTitle(expected.centerdirector.default_chart_title, true)).waitFor({state:'visible'}); + + // by refreshing we ensure that there are not stale legend-item elements + // on the page. + await page.reload(); + await expect(page.locator(usg.selectors.chartByTitle(expected.centerdirector.default_chart_title, true))).toBeVisible(); + }); + await test.step('Set a known start and end date', async () => { + await usg.setStartDate(baselineDate.start); + await usg.setEndDate(baselineDate.end); + await usg.refresh(); + await expect(page.locator(usg.selectors.chartXAxisLabelByName(baselineDate.start))).toBeVisible(); + }); + await test.step('View Job Size Min', async () => { + await expect(page.locator(usg.selectors.treeNodeByPath('Jobs Summary', 'Job Size: Min'))).toBeVisible(); + await page.locator(usg.selectors.treeNodeByPath('Jobs Summary', 'Job Size: Min')).click(); + await expect(page.locator(usg.selectors.chartByTitle('Job Size: Min (Core Count)', true))).toBeVisible(); + await usg.checkLegendText(expected.centerdirector.legend); + }); + await test.step('Confirm System Username is not selectable', async () => { + await expect(page.locator(usg.selectors.unfoldTreeNodeByName('Jobs Summary'))).toBeVisible(); + await page.locator(usg.selectors.unfoldTreeNodeByName('Jobs Summary')).click(); + await expect(page.locator(usg.selectors.topTreeNodeByName('Jobs by System Username'))).toBeVisible(); + await page.locator(usg.selectors.topTreeNodeByName('Jobs by System Username')).click(); + await expect(page.locator(usg.selectors.chartByTitle('Job Size: Min (Core Count)', true))).toBeVisible(); + }); + }); + } +}); diff --git a/tests/playwright/tsconfig.json b/tests/playwright/tsconfig.json new file mode 100644 index 0000000000..e3cbf1c6a2 --- /dev/null +++ b/tests/playwright/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "resolveJsonModule": true, + "esModuleInterop": true + } +} diff --git a/tests/playwright/unit_tests/mocha.spec.ts b/tests/playwright/unit_tests/mocha.spec.ts new file mode 100644 index 0000000000..00abd2b2dd --- /dev/null +++ b/tests/playwright/unit_tests/mocha.spec.ts @@ -0,0 +1,249 @@ +import {test, expect} from '@playwright/test'; +import fs from 'fs'; +import selectors from '../lib/mocha.selectors'; + +test.describe('Mocha Tests', async() => { + const fileName = 'stats.md'; + test('All Pass', async({page}) => { + await page.goto(selectors.index); + let total = 0; + let values = ''; + for (let i = 0; i < 20; i++) { + await test.step('Check initial passes/failures', async() => { + await page.reload(); + await page.waitForLoadState(); + const numOfPasses = await page.locator(selectors.passes).textContent(); + await expect(numOfPasses).toEqual('18'); + const numOfFails = await page.locator(selectors.fails).textContent(); + await expect(numOfFails).toEqual('0'); + const duration = Number(await page.locator(selectors.time).textContent()); + const percentage = Number(numOfPasses)/18; + const num = i + 1; + data.push({ + num, + duration, + percentage + }); + total += duration; + values += 'Num: ' + num + ' | Time: ' + duration + ' | Percentage: ' + percentage + '\n'; + }); + await test.step('Message Box error - authentication needed', async() => { + //when testing, received "0 communication failure", but locally + //results in a 404/401 error because of XDMoD.REST.url issue + await expect(page.locator(selectors.messageBox.window)).toBeVisible(); + const windowContent = await page.locator(selectors.messageBox.window).textContent(); + await expect(windowContent).not.toBeNull(); + await expect(page.locator(selectors.messageBox.button.ok())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.yes())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.no())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.cancel())).toBeVisible(); + }); + } + await test.step('Code blocks hidden', async() => { + const blocks = await page.$$(selectors.codeBlocks); + await expect(blocks.length).toEqual(18); + }); + fs.writeFileSync(fileName, (Date() + '\n' + values)); + fs.appendFileSync(fileName, ('\nAverage Duration: ' + total/20 + '\n')); + }); + test('Sectional Pass - ChangeStack', async({page}) => { + const changeStack = [ + 'empty config', + 'baseParams', + 'add some changes', + 'linear push pop', + 'save state' + ]; + await page.goto(selectors.index); + for (const task of changeStack){ + let subheader; + if (task == 'add some changes'){ + subheader = 'Auto commit'; + } else if (task == 'linear push pop'|| task == 'save state'){ + subheader = 'Stack Operations'; + } else { + subheader = 'Object Initialization'; + } + await test.step(`XDMoD.ChangeStack - ${task}`, async () => { + await page.click(selectors.taskNav(task)); + await page.waitForLoadState(); + await expect(page.locator(selectors.headers.navHeader('XDMoD.ChangeStack'))).toBeVisible(); + await expect(page.locator(selectors.headers.navHeader(subheader))).toBeVisible(); + const numOfTests = await page.$$(selectors.tasksDisplayed); + const numOfPasses = await page.locator(selectors.passes).textContent(); + const numOfFails = await page.locator(selectors.fails).textContent(); + if (task !== 'save state'){ + await expect(numOfPasses).toEqual(String(numOfTests.length)); + await expect(numOfFails).toEqual('0'); + } else { + // save state is meant to fail on its own since + // it depends on previous step ("linear push pop") to pass + await expect(numOfPasses).toEqual('0'); + await expect(numOfFails).toEqual('1'); + } + await page.goBack(); + }); + } + await test.step('Message Box error - authentication needed', async() => { + await expect(page.locator(selectors.messageBox.window)).toBeVisible(); + const windowContent = await page.locator(selectors.messageBox.window).textContent(); + await expect(windowContent).not.toBeNull(); + await expect(page.locator(selectors.messageBox.button.ok())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.yes())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.no())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.cancel())).toBeVisible(); + }); + }); + test('Sectional Pass - Viewer', async({page}) => { + const viewer = [ + 'tab panel / tab', + 'tab only', + 'tab only params', + 'tab panel / tab w/ params', + 'tab panel / tab / subtab w/ params' + ]; + await page.goto(selectors.index); + for (const task of viewer){ + await test.step(`XDMoD.Viewer - ${task}`, async () => { + await page.click(selectors.taskNav(task)); + await page.waitForLoadState(); + await expect(page.locator(selectors.headers.navHeader('XDMoD.Viewer'))).toBeVisible(); + await expect(page.locator(selectors.headers.navHeader('Various Successful Tokenizations'))).toBeVisible(); + const numOfTests = await page.$$(selectors.tasksDisplayed); + const numOfPasses = await page.locator(selectors.passes).textContent(); + const numOfFails = await page.locator(selectors.fails).textContent(); + await expect(numOfPasses).toEqual(String(numOfTests.length)); + await expect(numOfFails).toEqual('0'); + await page.goBack(); + }); + } + await test.step('Message Box error - authentication needed', async() => { + await expect(page.locator(selectors.messageBox.window)).toBeVisible(); + const windowContent = await page.locator(selectors.messageBox.window).textContent(); + await expect(windowContent).not.toBeNull(); + await expect(page.locator(selectors.messageBox.button.ok())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.yes())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.no())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.cancel())).toBeVisible(); + }); + }); + test('Sectional Pass - JobViewer', async({page}) => { + const jobViewer = [ + 'matching', + 'diff dtype', + 'diff array longer', + 'diff node path longer', + 'data format functions' + ]; + await page.goto(selectors.index); + for (const task of jobViewer){ + await test.step(`XDMoD.jobViewer - ${task}`, async () => { + await page.click(selectors.taskNav(task)); + await page.waitForLoadState(); + await expect(page.locator(selectors.headers.navHeader('XDMoD.JobViewer'))).toBeVisible(); + await expect(page.locator(selectors.headers.navHeader('compareNodePath tests'))).toBeVisible(); + const numOfTests = await page.$$(selectors.tasksDisplayed); + const numOfPasses = await page.locator(selectors.passes).textContent(); + const numOfFails = await page.locator(selectors.fails).textContent(); + await expect(numOfPasses).toEqual(String(numOfTests.length)); + await expect(numOfFails).toEqual('0'); + await page.goBack(); + }); + } + await test.step('Message Box error - authentication needed', async() => { + await expect(page.locator(selectors.messageBox.window)).toBeVisible(); + const windowContent = await page.locator(selectors.messageBox.window).textContent(); + await expect(windowContent).not.toBeNull(); + await expect(page.locator(selectors.messageBox.button.ok())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.yes())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.no())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.cancel())).toBeVisible(); + }); + }); + test('Sectional Pass - Format', async({page}) => { + const format = [ + 'SI formatting', + 'Binary formatting', + 'Elapsed time' + ]; + await page.goto(selectors.index); + for (const task of format){ + await test.step(`XDMoD.Format - ${task}`, async () => { + await page.click(selectors.taskNav(task)); + await page.waitForLoadState(); + await expect(page.locator(selectors.headers.navHeader('XDMoD.Format'))).toBeVisible(); + await expect(page.locator(selectors.headers.navHeader('Check Format functions'))).toBeVisible(); + const numOfTests = await page.$$(selectors.tasksDisplayed); + const numOfPasses = await page.locator(selectors.passes).textContent(); + const numOfFails = await page.locator(selectors.fails).textContent(); + await expect(numOfPasses).toEqual(String(numOfTests.length)); + await expect(numOfFails).toEqual('0'); + await page.goBack(); + }); + } + await test.step('Message Box error - authentication needed', async() => { + await expect(page.locator(selectors.messageBox.window)).toBeVisible(); + const windowContent = await page.locator(selectors.messageBox.window).textContent(); + await expect(windowContent).not.toBeNull(); + await expect(page.locator(selectors.messageBox.button.ok())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.yes())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.no())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.cancel())).toBeVisible(); + }); + }); + test('Message Box Window', async({page}) => { + await page.goto(selectors.index) + await test.step('Initial State', async() => { + await expect(page.locator(selectors.messageBox.window)).toBeVisible(); + const windowContent = await page.locator(selectors.messageBox.window).textContent(); + await expect(windowContent).not.toBeNull(); + await expect(page.locator(selectors.messageBox.button.ok())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.yes())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.no())).toBeVisible(); + await expect(page.locator(selectors.messageBox.button.cancel())).toBeVisible(); + }); + await test.step('Ok Pressed', async() =>{ + await page.click(selectors.messageBox.button.ok()); + await expect(page.locator(selectors.messageBox.button.ok())).toBeHidden(); + await expect(page.locator(selectors.messageBox.window)).toBeHidden(); + }); + await test.step('Yes Pressed', async() =>{ + await page.reload(); + await page.click(selectors.messageBox.button.yes()); + await expect(page.locator(selectors.messageBox.button.yes())).toBeHidden(); + await expect(page.locator(selectors.messageBox.window)).toBeHidden(); + }); + await test.step('No Pressed', async() =>{ + await page.reload(); + await page.click(selectors.messageBox.button.no()); + await expect(page.locator(selectors.messageBox.button.no())).toBeHidden(); + await expect(page.locator(selectors.messageBox.window)).toBeHidden(); + }); + await test.step('Cancel Pressed', async() =>{ + await page.reload(); + await page.click(selectors.messageBox.button.cancel()); + await expect(page.locator(selectors.messageBox.button.cancel())).toBeHidden(); + await expect(page.locator(selectors.messageBox.window)).toBeHidden(); + }); + }); + test('Code Block Pop ups', async({page}) => { + await page.goto(selectors.index); + await page.click(selectors.messageBox.button.cancel()); + const allTasks = await page.locator(selectors.tasksDisplayed); + for (let i = 0; i < await allTasks.count(); i++){ + await allTasks.nth(i).click(); + } + const allBlocks = await page.$$(selectors.codeBlocks); + for (const block of allBlocks){ + const isBlockVisible = await block.isVisible(); + await expect(isBlockVisible).toBeTruthy(); + } + for (let i = 0; i < await allTasks.count(); i++){ + await allTasks.nth(i).click(); + } + for (const block of allBlocks){ + const isBlockHidden = await block.isHidden(); + await expect(isBlockHidden).toBeTruthy(); + } + }); +}); diff --git a/tests/ui/.eslintrc.json b/tests/ui/.eslintrc.json deleted file mode 100644 index 66703a57a0..0000000000 --- a/tests/ui/.eslintrc.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "env": { - "mocha": true, - "node": true, - "es6": true - }, - "globals": { - "require": false, - "expect": false, - "browser": false, - "testHelpers": false, - "$": false, - "$$": false - }, - "rules": { - "class-methods-use-this": [0], - "no-underscore-dangle": [0], - "no-unused-expressions": [0], - "object-shorthand": ["off"] - } -} diff --git a/tests/ui/.gitignore b/tests/ui/.gitignore deleted file mode 100644 index bae8c22473..0000000000 --- a/tests/ui/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Screenshots -/xdmod -/xdmod.tar.gz -/errorShots diff --git a/tests/ui/package.json b/tests/ui/package.json deleted file mode 100644 index 164e6406fa..0000000000 --- a/tests/ui/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "private": true, - "name": "ui-tests", - "version": "1.0.0", - "description": "UI Tests for Open XDMoD", - "engines": { - "node": ">=6.0" - }, - "scripts": { - "test": "wdio wdio.conf.js", - "test-sso": "SSO=true npm test -- --spec ./test/specs/xdmod/SSOLogin.js" - }, - "author": "Ben Plessinger", - "license": "GPL-3.0", - "dependencies": { - "chai": "4.1.2", - "cheerio": "1.0.0-rc.3", - "chromedriver": "99.0.0", - "mocha": "5.2", - "require-dir": "1.0", - "selenium-standalone": "6.16.0", - "wdio-chromedriver-service": "0.1.3", - "wdio-junit-reporter": "0.4.4", - "wdio-mocha-framework": "0.6.4", - "wdio-sauce-service": "0.4.14", - "wdio-selenium-standalone-service": "0.0.12", - "wdio-spec-reporter": "0.1.5", - "webdriverio": "4.13.0" - }, - "repository": "https://github.com/ubccr/xdmod" -} diff --git a/tests/ui/readme.md b/tests/ui/readme.md deleted file mode 100644 index f889b0c04e..0000000000 --- a/tests/ui/readme.md +++ /dev/null @@ -1,108 +0,0 @@ -# Automated Regression tests for the XDMoD Frontend - -Based on [webdriver.io][wd] - -## Setup - -```bash -npm install -``` - -This will not work with a "clean" reference database yet. - -You will need to perform the following steps - -1. Login as the user you want the tests to run as -2. Goto the Metric Explorer -3. Add Data - - 1. Jobs - 2. CPU Hours: Total - 3. Group By None (if using Metric Catalog) - -4. Save - -5. Save Changes - -6. Check Available For Report - -7. Switch to the Report Generator Tab - -8. Click new - -9. Drag untitled query 1 from the Available Charts to Included Charts -10. Click Save - -### wdio.conf.js/wdio-sauce.conf.js - -#### host - -Change the baseUrl to point to the environment you want it pointed to. - -By default this uses the "reference" database set up on . - -```javascript - // Set a base URL in order to shorten url command calls. If your url parameter starts - // with "/", the base url gets prepended. - baseUrl: "https://tas-reference-dbs.ccr.xdmod.org", -``` - -#### browser Options - -Change the browser to run the tests in by changing the list of browsers in the capabilities array. - -``` -InternetExplorer -FireFox -Chrome -Safari -PhantomJS -``` - -By default the tests run only in Chrome. - -```javascript -capabilities: [ - Chrome -], -``` - -##### SauceLabs - -Tests will automatically use [SauceLabs][sl] if you have SAUCE_USER and SAUCE_KEY environment variables set. - -To choose/add another browser to `wdio.conf.js`, use the [Platform Configurator][sl-conf] and add specifications using the same syntax of the `Chrome` and `FireFox` browsers. and then update the capabilities array in the check for the SAUCE environment variables. - -#### Other Options - -In `mochaOpts`, set the `timeout` to desired time length. - -Set `maxInstances` to the desired number of concurrent tests to run at a time. - -_Note:_ The XDMoD SauceLabs account allows for a max of 5 concurrent tests at a time. - -If running tests through SauceLabs, enter the correct `key` for the `xdmod-sauce` account. - -#### testing.json - -The user names and passwords are read from a file called testing.json in the ci directory under testing. - -## Run - -### run tests locally - -```bash -npm test -``` - -### run tests through sauce labs - -```bash -SAUCE_USER=sauce-user SAUCE_KEY=X-X-X npm run test -``` - -and see the fun that is automated ui testing. - -[sl]: https://saucelabs.com/ -[sl-conf]: https://wiki.saucelabs.com/display/DOCS/Platform+Configurator#/ -[wd]: http://webdriver.io/ diff --git a/tests/ui/runtests.sh b/tests/ui/runtests.sh deleted file mode 100755 index 5efbb16e7a..0000000000 --- a/tests/ui/runtests.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash -BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -source $BASEDIR/../ci/runtest-include.sh - -SHMSIZEK=`df -k /dev/shm | grep shm | awk '{print $2}'` -if (( $SHMSIZEK < 2000000 )); then - echo "***************************************************************" - echo "Shared memory is less than 2G, tests may fail randomly" - echo "If you are using Docker use the option of --shm-size 2g" - echo "***************************************************************" -fi - -# This file is generally used in the docker build to speed things up. -# Set it to something different if you want to use your own. -CACHEFILE='/root/browser-tests-node-modules.tar.gz' - -set -e -set -o pipefail - -echo "UI tests beginning:" `date +"%a %b %d %H:%M:%S.%3N %Y"` - -if [ "$1" = "--headless" ]; -then - WDIO_MODE=headless - export WDIO_MODE -fi - -if [ "$2" = "--log-junit" ]; -then - JUNIT_OUTDIR="$3" - export JUNIT_OUTDIR -fi - -pushd ${BASEDIR} -if [[ ! -d 'node_modules' ]]; then - if [[ -f "${CACHEFILE}" ]]; then - echo "using cache file" - tar -moxf "${CACHEFILE}" - # fibers needs to be installed because the cache file was built with a - # different version of node that is needed for chromedriver. - npm install fibers - else - echo "No cache file found." - exit 1 - fi -fi - -if [ "$4" = "--sso" ]; -then - npm run test-sso -else - npm test -fi diff --git a/tests/ui/test/helpers/artifacts.js b/tests/ui/test/helpers/artifacts.js deleted file mode 100644 index d9fd4bc9fc..0000000000 --- a/tests/ui/test/helpers/artifacts.js +++ /dev/null @@ -1,11 +0,0 @@ -const path = require('path'); -const fs = require('fs'); - -module.exports = { - testEnv: process.env.TEST_ENV !== undefined ? process.env.TEST_ENV : 'xdmod', - artifactPath: path.join(__dirname, './../../../artifacts/'), - getArtifact: function (name, type = 'output') { - var filePath = path.join(this.artifactPath, this.testEnv, 'ui', type, name + '.json'); - return JSON.parse(fs.readFileSync(filePath)); - } -}; diff --git a/tests/ui/test/helpers/index.js b/tests/ui/test/helpers/index.js deleted file mode 100644 index f7c749d520..0000000000 --- a/tests/ui/test/helpers/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('require-dir')(); diff --git a/tests/ui/test/helpers/metricExplorer.js b/tests/ui/test/helpers/metricExplorer.js deleted file mode 100644 index 7096775fe1..0000000000 --- a/tests/ui/test/helpers/metricExplorer.js +++ /dev/null @@ -1,88 +0,0 @@ -module.exports = { - tab: '#main_tab_panel__metric_explorer', - metricCatalogEntryByName: function (name) { - return '//div[@id="metric_explorer"]//ul[@class="x-tree-root-ct x-tree-arrows"]//a[@class="x-tree-node-anchor"]//span[contains(text(), "' + name + '")]'; - }, - metricCatalogEntryExpanderByName: function (name) { - return '//div[@id="metric_explorer"]//ul[@class="x-tree-root-ct x-tree-arrows"]//span[contains(text(), "' + name + '")]/ancestor::li//img[@class="x-tree-ec-icon x-tree-elbow-end-plus"]'; - }, - metricContextEntryByName: function (name) { - return '//div[@class="x-menu x-menu-floating x-layer x-menu-nosep"]//span[contains(text(), "' + name + '")]'; - }, - title: "//*[local-name()='svg']/*[local-name()='text' and @class='highcharts-title']/*[local-name()='tspan']", - contextMenuItemByText: function (text) { - return '//div[@class="x-menu x-menu-floating x-layer x-menu-nosep"]//span[contains(text(), "' + text + '")]'; - }, - resizableWindowByTitle: function (title) { - return '//div[contains(@class, "x-window") and contains(@class, "x-resizable-pinned") and contains(@style, "visibility: visible")]//span[@class="x-window-header-text" and text()[contains(.,"' + title + '")]]'; - }, - rawDataEntryByIndex: function (index) { - var thisIndex = (index !== undefined) ? index : 1; - return "(//div[contains(@class, 'x-window') and contains(@style, 'visibility: visible')]//div[contains(@class, 'x-grid3-body')]//div[contains(@class, 'x-grid3-cell-inner') and contains(@class, 'x-grid3-col-local_job_id')])[" + thisIndex + ']'; - }, - startDate: '#metric_explorer input[id^=start_field_ext]', - endDate: '#metric_explorer input[id^=end_field_ext]', - container: '#metric_explorer', - load: { - button: function meLoadButtonId() { - return 'button=Load Chart'; - }, - firstSaved: '.x-menu-floating:not(.x-hide-offsets) .x-grid3-body .x-grid3-row-first', - // This will return the id for the chart number specified in a zero based manner. - // This does not take into account the selected item - chartNum: function meChartByIndex(number) { - var thisNumber = number + 1; - return '.x-menu-floating:not(.x-hide-offsets) .x-grid3-body > div:nth-child(' + thisNumber + ')'; - } - }, - addData: { - button: '.x-btn-text.add_data', - secondLevel: '.x-menu-floating:not(.x-hide-offsets):not(.x-menu-nosep)' - }, - data: { - button: 'button=Data', - container: '', - modal: { - updateButton: 'button=Update', - groupBy: { - input: 'input[name=dimension]' - } - } - }, - deleteChart: '', - undo: function meUndoButtonId() { - // eslint-disable-next-line no-undef - return '#' + $container('button.x-btn-text-icon') - .closest('table') - .attr('id'); - }, - options: { - aggregate: '#aggregate_cb', - button: '#metric_explorer button.chartoptions', - trendLine: '#me_trend_line', - swap: '#me_chart_swap_xy', - title: 'div.x-menu.x-menu-floating.x-layer.x-menu-nosep[style*="visibility: visible"] #me_chart_title' - }, - chart: { - svg: '#metric_explorer > div > .x-panel-body-noborder > .x-panel-noborder svg', - title: '#hc-panelmetric_explorer svg .undefinedtitle', - titleInput: 'div.x-menu.x-menu-floating.x-layer.x-menu-nosep[style*="visibility: visible"] input[type=text]', - titleOkButton: 'div.x-menu.x-menu-floating.x-layer.x-menu-nosep[style*="visibility: visible"] table.x-btn.x-btn-noicon.x-box-item:first-child button', - titleCancelButton: 'div.x-menu.x-menu-floating.x-layer.x-menu-nosep[style*="visibility: visible"] table.x-btn.x-btn-noicon.x-box-item:last-child button', - contextMenu: { - container: '#metric-explorer-chartoptions-context-menu', - legend: '#metric-explorer-chartoptions-legend', - addData: '#metric-explorer-chartoptions-add-data', - addFilter: '#metric-explorer-chartoptions-add-filter' - - }, - axis: '#metric_explorer .highcharts-xaxis-labels' - }, - catalog: { - container: '#metric_explorer > div > .x-panel-body-noborder > .x-border-panel:not(.x-panel-noborder)', - tree: '#metric_explorer > div > .x-panel-body-noborder > .x-border-panel:not(.x-panel-noborder) .x-tree-root-ct' - }, - buttonMenu: { - firstLevel: '.x-menu-floating:not(.x-hide-offsets)' - } -}; diff --git a/tests/ui/test/specs/xdmod/SSOLogin.js b/tests/ui/test/specs/xdmod/SSOLogin.js deleted file mode 100644 index b02f4709c3..0000000000 --- a/tests/ui/test/specs/xdmod/SSOLogin.js +++ /dev/null @@ -1,51 +0,0 @@ -describe('Single Sign On Login', () => { - it('Should have the Single Sign On option', () => { - browser.url('https://'); - browser.waitForInvisible('.ext-el-mask-msg'); - browser.waitAndClick('a[href*=actionLogin]'); - }); - it('Should let us select the SSO Login button', () => { - browser.waitForVisible('#SSOLoginLink'); - browser.waitAndClick('#SSOLoginLink'); - }); - it('Should goto the Single Sign On login page and login', () => { - browser.waitForVisible('form[action="/signin"]', 30000); - browser.submitForm('form[action="/signin"]'); - }); - it('Display Logged in Users Name', () => { - browser.frame(); - $('#welcome_message').waitForVisible(60000); - expect($('#welcome_message').getText()).to.equal('Saml Jackson'); - $('#main_tab_panel__about_xdmod').waitForVisible(); - }); - it('Should prompt with My Profile', () => { - browser.waitForVisible('#xdmod-profile-editor button.general_btn_close', 30000); - browser.waitAndClick('#xdmod-profile-editor button.general_btn_close'); - browser.waitForInvisible('#xdmod-profile-editor'); - }); - it('Logout', () => { - browser.waitForInvisible('.ext-el-mask-msg'); - browser.waitAndClick('#logout_link'); - browser.waitForInvisible('.ext-el-mask-msg'); - $('a[href*=actionLogin]').waitForVisible(); - $('#main_tab_panel__about_xdmod').waitForVisible(); - }); -}); - -describe('Single Sign On Login w/ deep link', () => { - it('Should have the Single Sign On option', () => { - browser.url('/#main_tab_panel:metric_explorer'); - browser.waitForVisible('#SSOLoginLink'); - browser.waitAndClick('#SSOLoginLink'); - }); - it('Should goto the Single Sign On login page and login', () => { - browser.waitForExist('form[action="/signin"]'); - browser.submitForm('form[action="/signin"]'); - }); - it('Load Metric Explorer tab', () => { - browser.frame(); - $('#welcome_message').waitForVisible(60000); - expect($('#welcome_message').getText()).to.equal('Saml Jackson'); - $('#metric_explorer').waitForVisible(); - }); -}); diff --git a/tests/ui/test/specs/xdmod/about.js b/tests/ui/test/specs/xdmod/about.js deleted file mode 100644 index 5230e5004a..0000000000 --- a/tests/ui/test/specs/xdmod/about.js +++ /dev/null @@ -1,98 +0,0 @@ -var logIn = require('./loginPage.page.js'); -var Abt = require('./about.page.js'); - -describe('About', function about() { - logIn.login('centerdirector'); - describe('Logged In Test', function loggedInTests() { - it('Verify About is the Last Tab', function aboutIsTheLastTab() { - browser.waitForAllInvisible('.ext-el-mask'); - browser.waitForVisible(Abt.tab, 30000); - expect(browser.getText(Abt.last_tab)).to.equal('About'); - }); - - it('Select About Tab', function selectTab() { - browser.waitForLoadedThenClick(Abt.tab, 50000); - browser.waitForVisible(Abt.container, 20000); - }); - describe('Check Nav Entries', function checkNavEntries() { - it('XDMoD', function checkNavEntryXDMoD() { - Abt.checkTab('XDMoD'); - }); - it('Open XDMoD', function checkNavEntryXDMoD() { - Abt.checkTab('Open XDMoD'); - }); - it('SUPReMM', function checkNavEntrySUPReMM() { - Abt.checkTab('SUPReMM'); - }); - it('Roadmap', function checkNavEntryXDMoD() { - Abt.checkRoadmap(); - }); - it('Team', function checkNavEntryXDMoD() { - Abt.checkTab('Team'); - }); - it('Publications', function checkNavEntryXDMoD() { - Abt.checkTab('Publications'); - }); - it('Presentations', function checkNavEntryXDMoD() { - Abt.checkTab('Presentations'); - }); - it('Links', function checkNavEntryXDMoD() { - Abt.checkTab('Links'); - }); - it('Release Notes', function checkNavEntryXDMoD() { - Abt.checkTab('Release Notes'); - }); - }); - }); - - describe('Logged Out Tests', function loggedInTests() { - it('Click the logout link', function clickLogout() { - browser.waitForLoadedThenClick('#logout_link', 50000); - }); - it('Display Logged out State', function clickLogout() { - browser.waitUntilNotExist('.ext-el-mask-msg'); - $('a[href*=actionLogin]').waitForExist(); - }); - - it('Verify About is the Last Tab', function aboutIsTheLastTab() { - browser.waitForVisible(Abt.tab, 30000); - expect(browser.getText(Abt.last_tab)).to.equal('About'); - }); - - it('Select Tab', function selectTab() { - browser.waitForLoadedThenClick(Abt.tab, 50000); - browser.waitForVisible(Abt.container, 20000); - }); - describe('Check Nav Entries', function checkNavEntries() { - it('XDMoD', function checkNavEntryXDMoD() { - Abt.checkTab('XDMoD'); - }); - it('Open XDMoD', function checkNavEntryXDMoD() { - Abt.checkTab('Open XDMoD'); - }); - it('SUPReMM', function checkNavEntrySUPReMM() { - Abt.checkTab('SUPReMM'); - }); - it('Roadmap', function checkNavEntryXDMoD() { - Abt.checkRoadmap(); - }); - it('Team', function checkNavEntryXDMoD() { - Abt.checkTab('Team'); - }); - it('Publications', function checkNavEntryXDMoD() { - Abt.checkTab('Publications'); - }); - it('Presentations', function checkNavEntryXDMoD() { - Abt.checkTab('Presentations'); - }); - it('Links', function checkNavEntryXDMoD() { - Abt.checkTab('Links'); - }); - it('Release Notes', function checkNavEntryXDMoD() { - Abt.checkTab('Release Notes'); - }); - }); - }); - logIn.login('cd'); - logIn.logout(); -}); diff --git a/tests/ui/test/specs/xdmod/about.page.js b/tests/ui/test/specs/xdmod/about.page.js deleted file mode 100644 index 52421e4095..0000000000 --- a/tests/ui/test/specs/xdmod/about.page.js +++ /dev/null @@ -1,35 +0,0 @@ -class About { - constructor() { - this.tab = '//ul[contains(@class, "x-tab-strip")]//span[text()="About"]'; - this.container = '//div[@id="about_xdmod"]'; - this.center = '//div[@id="about_xdmod"]//div[contains(@class, "x-panel-body") and contains(@class, "x-border-layout-ct")]/div[contains(@class,"x-panel") and contains(@class,"x-panel-reset") and contains(@class,"x-border-panel")]'; - this.last_tab = '//ul[contains(@class, "x-tab-strip")]//li[contains(@class, "tab-strip")][last()]'; - } - - navEntry(name) { - return '//div[@class="x-tree-root-node"]//div[contains(@class,"x-tree-node-el")]//span[contains(text(),"' + name + '")]'; - } - - checkTab(name) { - browser.waitForLoadedThenClick(this.navEntry(name), 50000); - $(this.container).waitForText(50000); - // TODO: Determine Pass case for this without using screenshot - // browser.takeScreenshot(name.replace(' ',''), this.center, "xdmod"); - } - - checkRoadmap() { - browser.waitForLoadedThenClick(this.navEntry('Roadmap')); - browser.waitForExist('iframe#about_roadmap', 30000); - /* - browser.frame('about_roadmap', function (err, result) { - expect(err).to.be.a('undefined'); - expect(result).to.not.be.a('null'); - }); - browser.waitForExist('.full-bleed-trello-board', 30000); - browser.waitForText('.full-bleed-trello-board', 30000); - browser.frameParent(); - */ - } - -} -module.exports = new About(); diff --git a/tests/ui/test/specs/xdmod/exportDialog.js b/tests/ui/test/specs/xdmod/exportDialog.js deleted file mode 100644 index 484d29f1d3..0000000000 --- a/tests/ui/test/specs/xdmod/exportDialog.js +++ /dev/null @@ -1,70 +0,0 @@ -var usage = require('./usageTab.page.js'); -var xdmod = require('./xdmod.page.js'); - -describe('Export Dialog', function () { - it('Select "Usage" tab', function () { - browser.url('/'); - usage.selectTab(); - }); - it('Bring up export dialog', function () { - browser.waitAndClick(usage.toolbar.exportButton); - browser.waitForVisible(xdmod.selectors.exportDialog.window); - }); - it('Check format list', function () { - browser.waitAndClick(xdmod.selectors.exportDialog.formatDropdown()); - browser.waitForVisible(xdmod.selectors.exportDialog.comboList); - var expected = [ - 'PNG - Portable Network Graphics', - 'SVG - Scalable Vector Graphics', - 'CSV - Comma Separated Values', - 'XML - Extensible Markup Language', - 'PDF - Portable Document Format' - ]; - expect(browser.getText(xdmod.selectors.exportDialog.comboListItems)).to.deep.equal(expected); - browser.waitAndClick(xdmod.selectors.exportDialog.formatDropdown()); - browser.waitForInvisible(xdmod.selectors.exportDialog.comboList); - }); - it('Check Image Sizes', function () { - browser.waitAndClick(xdmod.selectors.exportDialog.imageSizeDropdown()); - browser.waitForVisible(xdmod.selectors.exportDialog.comboList); - var expected = [ - 'Small', - 'Medium', - 'Large', - 'Poster', - 'Custom' - ]; - expect(browser.getText(xdmod.selectors.exportDialog.comboListItems)).to.deep.equal(expected); - browser.waitAndClick(xdmod.selectors.exportDialog.imageSizeDropdown()); - browser.waitForInvisible(xdmod.selectors.exportDialog.comboList); - }); - it('Check show chart title exists', function () { - browser.waitForVisible(xdmod.selectors.exportDialog.showTitleCheckbox()); - }); - it('Switch to CSV output', function () { - browser.waitAndClick(xdmod.selectors.exportDialog.formatDropdown()); - browser.waitForVisible(xdmod.selectors.exportDialog.comboList); - browser.waitAndClick(xdmod.selectors.exportDialog.comboListItemByName('CSV')); - browser.waitForInvisible(xdmod.selectors.exportDialog.comboList); - }); - it('Make sure title and image options are not visible', function () { - browser.waitForInvisible(xdmod.selectors.exportDialog.showTitleCheckbox()); - browser.waitForInvisible(xdmod.selectors.exportDialog.imageSizeDropdown()); - }); - it('Switch to PDF output', function () { - browser.waitAndClick(xdmod.selectors.exportDialog.formatDropdown()); - browser.waitForVisible(xdmod.selectors.exportDialog.comboList); - browser.waitAndClick(xdmod.selectors.exportDialog.comboListItemByName('PDF')); - browser.waitForInvisible(xdmod.selectors.exportDialog.comboList); - }); - it('Make sure title and size options are visible', function () { - browser.waitForVisible(xdmod.selectors.exportDialog.showTitleCheckbox()); - browser.waitForVisible(xdmod.selectors.exportDialog.widthInput()); - browser.waitForVisible(xdmod.selectors.exportDialog.heightInput()); - browser.waitForVisible(xdmod.selectors.exportDialog.fontInput()); - }); - it('Close', function () { - browser.waitAndClick(xdmod.selectors.exportDialog.cancelButton()); - browser.waitForInvisible(xdmod.selectors.exportDialog.window); - }); -}); diff --git a/tests/ui/test/specs/xdmod/internalDashboard.js b/tests/ui/test/specs/xdmod/internalDashboard.js deleted file mode 100644 index 5e79322155..0000000000 --- a/tests/ui/test/specs/xdmod/internalDashboard.js +++ /dev/null @@ -1,421 +0,0 @@ -const page = require('./internalDashboard.page.js'); -const settings = [ - { - label: 'E-Mail Address', - type: 'text', - updated: 'btest@test.example.com', - original: 'btest@example.com' - }, - { - label: 'User Type', - type: 'dropdown', - updated: 'Testing', - original: 'External' - }, - { - label: 'Map To', - type: 'dropdown', - updated: 'Auk, Great', - original: 'Unknown, Unknown' - }, - { - label: 'Institution', - type: 'dropdown', - updated: 'Unknown Organization', - original: 'Screwdriver' - } -]; - -describe('Internal Dashboard', function () { - page.login('mgr'); - - describe('Create a new user', function () { - it('Select "User Management" Tab', function () { - browser.waitForVisible(page.selectors.header.tabs.user_management()); - browser.waitAndClick(page.selectors.header.tabs.user_management()); - - browser.waitForVisible(page.selectors.user_management.tabs.account_requests()); - }); - it('Click "Create & Manage Users"', function () { - browser.waitAndClick(page.selectors.account_requests.toolbar.create_manage_users); - - browser.waitForVisible(page.selectors.create_manage_users.window); - }); - it('Select the "New User" tab', function () { - browser.waitAndClick(page.selectors.create_manage_users.tabs.new_user()); - - browser.waitForVisible(page.selectors.create_manage_users.new_user.container()); - }); - it('Populate User Information', function () { - browser.waitForVisible(page.selectors.create_manage_users.new_user.first_name()); - browser.waitForVisible(page.selectors.create_manage_users.new_user.lastName()); - browser.waitForVisible(page.selectors.create_manage_users.new_user.emailAddress()); - - browser.setValue(page.selectors.create_manage_users.new_user.first_name(), 'Bob'); - browser.setValue(page.selectors.create_manage_users.new_user.lastName(), 'Test'); - browser.setValue(page.selectors.create_manage_users.new_user.emailAddress(), 'btest@example.com'); - }); - it('Populate User Details', function () { - browser.waitForVisible(page.selectors.create_manage_users.new_user.username()); - browser.waitForVisible(page.selectors.create_manage_users.new_user.mapTo()); - browser.waitForVisible(page.selectors.create_manage_users.new_user.institution()); - - // the institution drop down should be disabled by default. - expect(browser.isEnabled(page.selectors.create_manage_users.new_user.institution())).to.equal(false); - - browser.setValue(page.selectors.create_manage_users.new_user.username(), 'btest'); - browser.setValue(page.selectors.create_manage_users.new_user.mapTo(), 'Unknown'); - - browser.waitForVisible(page.selectors.combo.container); - browser.waitForVisible(page.selectors.combo.itemByText('Unknown')); - - browser.waitAndClick(page.selectors.combo.itemByText('Unknown')); - browser.waitForInvisible(page.selectors.combo.container); - - const mapTo = browser.getValue(page.selectors.create_manage_users.new_user.mapTo()); - expect(mapTo).to.equal('Unknown'); - - // Wait for the institution combo to be enabled / have a value. - browser.waitForEnabled(page.selectors.create_manage_users.new_user.institution()); - browser.waitForValue(page.selectors.create_manage_users.new_user.institution()); - - // By selecting a person to map our user to the institution should - // have been automatically populated. - const institution = browser.getValue(page.selectors.create_manage_users.new_user.institution()); - expect(institution).to.equal('Unknown Organization'); - - // Institution should also be enabled because the we're mapping the - // 'Unknown' person. - expect(browser.isEnabled(page.selectors.create_manage_users.new_user.institution())).to.equal(true); - }); - it('Change Institution', function () { - browser.waitAndClick(page.selectors.create_manage_users.new_user.institution_trigger()); - browser.waitForVisible(page.selectors.combo.container); - browser.waitForVisible(page.selectors.combo.itemByText('Screwdriver')); - - browser.waitAndClick(page.selectors.combo.itemByText('Screwdriver')); - browser.waitForInvisible(page.selectors.combo.container); - - const newInstitution = browser.getValue(page.selectors.create_manage_users.new_user.institution()); - expect(newInstitution).to.equal('Screwdriver'); - }); - it('Select Acls', function () { - browser.waitAndClick(page.selectors.create_manage_users.new_user.aclByName('User')); - const cls = browser.getAttribute(page.selectors.create_manage_users.new_user.aclByName('User'), 'class'); - expect(cls.indexOf('x-grid3-check-col-on')).to.not.equal(-1); - }); - it('Save User', function () { - browser.waitAndClick(page.selectors.create_manage_users.buttons.create_user()); - - browser.waitForVisible(page.selectors.createSuccessNotification('btest')); - browser.waitForInvisible(page.selectors.createSuccessNotification('btest')); - }); - it('Close "Create & Manage Users"', function () { - browser.waitAndClick(page.selectors.create_manage_users.buttons.close()); - browser.waitForInvisible(page.selectors.create_manage_users.window); - }); - it('Select the "Existing Users" tab', function () { - browser.waitForVisible(page.selectors.user_management.tabs.existing_users()); - browser.waitAndClick(page.selectors.user_management.tabs.existing_users()); - }); - it('Ensure that the "Existing Users" table is displayed', function () { - browser.waitForVisible(page.selectors.existing_users.table.container); - }); - it('Check that the username is displayed correctly', function () { - const usernameCol = page.selectors.existing_users.table.col_for_user('btest', 'Username'); - browser.waitForValue(usernameCol); - const username = browser.getText(usernameCol); - expect(username).to.equal('btest'); - }); - it('Check that the first name is displayed correctly', function () { - const firstNameCol = page.selectors.existing_users.table.col_for_user('btest', 'First Name'); - browser.waitForValue(firstNameCol); - const firstName = browser.getText(firstNameCol); - expect(firstName).to.equal('Bob'); - }); - it('Check that the last name is displayed correctly', function () { - const lastNameCol = page.selectors.existing_users.table.col_for_user('btest', 'Last Name'); - browser.waitForValue(lastNameCol); - const lastName = browser.getText(lastNameCol); - expect(lastName).to.equal('Test'); - }); - it('Check that the email is displayed correctly', function () { - const emailCol = page.selectors.existing_users.table.col_for_user('btest', 'E-Mail Address'); - browser.waitForValue(emailCol); - const email = browser.getText(emailCol); - expect(email).to.equal('btest@example.com'); - }); - it('Check that the role is displayed correctly', function () { - const roleCol = page.selectors.existing_users.table.col_for_user('btest', 'Role(s)'); - browser.waitForValue(roleCol); - const roles = browser.getText(roleCol); - expect(roles).to.equal('User'); - }); - }); - describe('Make sure that updates to the newly created users Settings can be discarded', function () { - settings.forEach(function (setting) { - describe(`Checking: ${setting.label}`, function () { - it('Select the "Existing Users" tab', function () { - browser.waitForVisible(page.selectors.user_management.tabs.existing_users()); - browser.waitAndClick(page.selectors.user_management.tabs.existing_users()); - }); - it('Ensure that the "Existing Users" table is displayed', function () { - browser.waitForVisible(page.selectors.existing_users.table.container); - }); - it('Double click the users row in the `Existing Users` table', function () { - const usernameCol = page.selectors.existing_users.table.col_for_user('btest', 'Username'); - browser.waitForValue(usernameCol); - browser.doubleClick(usernameCol); - - browser.waitForVisible(page.selectors.create_manage_users.window); - browser.waitForVisible(page.selectors.create_manage_users.current_users.container); - browser.waitForInvisible(page.selectors.create_manage_users.loading_mask); - }); - it(`Change the "${setting.label}" to "${setting.updated}"`, function () { - browser.waitForInvisible(page.selectors.create_manage_users.current_users.settings.noUserSelectedModal()); - if ('dropdown' === setting.type) { - const inputTrigger = page.selectors.create_manage_users.current_users.settings.dropDownTriggerByLabel(setting.label); - browser.waitForVisible(inputTrigger); - browser.click(inputTrigger); - - const inputDropDown = page.selectors.combo.container; - browser.waitForVisible(inputDropDown); - - const dropDownValue = page.selectors.combo.itemByText(setting.updated); - browser.waitForVisible(dropDownValue); - browser.waitAndClick(dropDownValue); - - browser.waitForInvisible(inputDropDown); - - const input = page.selectors.create_manage_users.current_users.settings.inputByLabel(setting.label, 'text'); - const updatedValue = browser.getValue(input); - expect(updatedValue).to.equal(setting.updated); - } else if ('text' === setting.type) { - const input = page.selectors.create_manage_users.current_users.settings.inputByLabel(setting.label, setting.type); - browser.waitForVisible(input); - browser.setValue(input, setting.updated); - const updatedValue = browser.getValue(input); - expect(updatedValue).to.equal(setting.updated); - } - }); - it('Ensure that the user dirty message is shown', function () { - const dirtyMessage = page.selectors.create_manage_users.bottom_bar.messageByText('unsaved changes'); - browser.waitForVisible(dirtyMessage); - }); - it('Click the Close button', function () { - const closeButton = page.selectors.create_manage_users.current_users.button('Close'); - browser.waitAndClick(closeButton); - }); - it('Ensure that the Unsaved Changes modal is presented', function () { - browser.waitForVisible(page.selectors.modal.containerByTitle('Unsaved Changes')); - }); - it('Discard Changes', function () { - const noButton = page.selectors.modal.buttonByText('Unsaved Changes', 'No'); - browser.waitForVisible(noButton); - browser.click(noButton); - - // We expect that the modal dialog will disappear - browser.waitForInvisible(page.selectors.modal.containerByTitle('Unsaved Changes')); - }); - it('Edit the User again', function () { - const usernameCol = page.selectors.existing_users.table.col_for_user('btest', 'Username'); - browser.waitForValue(usernameCol); - browser.doubleClick(usernameCol); - - browser.waitForVisible(page.selectors.create_manage_users.window); - browser.waitForVisible(page.selectors.create_manage_users.current_users.container); - browser.waitForInvisible(page.selectors.create_manage_users.loading_mask); - }); - it(`Check that the ${setting.label} is back to ${setting.original}`, function () { - const inputType = 'dropdown' === setting.type ? 'text' : setting.type; - const inputElem = page.selectors.create_manage_users.current_users.settings.inputByLabel(setting.label, inputType); - - // We need to wait for this mask to be hidden as that is the indicator that the user's information - // has been populated. - browser.waitForInvisible(page.selectors.create_manage_users.current_users.settings.noUserSelectedModal()); - - browser.waitUntil(function () { - return browser.getValue(inputElem) === setting.original; - }, 10000, `Expected that ${setting.label} would be back to ${setting.original} `); - const inputValue = browser.getValue(inputElem); - expect(inputValue).to.equal(setting.original); - }); - it('Close the Edit Existing User Modal', function () { - const closeButton = page.selectors.create_manage_users.current_users.button('Close'); - browser.waitForVisible(closeButton); - browser.click(closeButton); - }); - }); - }); - }); - describe('Make sure that the newly created user can have its settings updated successfully', function () { - settings.forEach(function (setting) { - describe(`Checking: ${setting.label}`, function () { - it('Select the "Existing Users" tab', function () { - browser.waitForVisible(page.selectors.user_management.tabs.existing_users()); - browser.waitAndClick(page.selectors.user_management.tabs.existing_users()); - }); - it('Ensure that the "Existing Users" table is displayed', function () { - browser.waitForVisible(page.selectors.existing_users.table.container); - }); - it('Double click the users row in the `Existing Users` table', function () { - const usernameCol = page.selectors.existing_users.table.col_for_user('btest', 'Username'); - browser.waitForValue(usernameCol); - browser.doubleClick(usernameCol); - - browser.waitForVisible(page.selectors.create_manage_users.window); - browser.waitForVisible(page.selectors.create_manage_users.current_users.settings.container); - browser.waitForInvisible(page.selectors.create_manage_users.loading_mask); - }); - it(`Change the "${setting.label}" to "${setting.updated}"`, function () { - const detailsHeader = page.selectors.create_manage_users.current_users.settings.userDetailsHeader('User Details: Bob Test'); - browser.waitForVisible(detailsHeader); - browser.waitForInvisible(page.selectors.create_manage_users.current_users.settings.noUserSelectedModal()); - - if ('dropdown' === setting.type) { - const inputTrigger = page.selectors.create_manage_users.current_users.settings.dropDownTriggerByLabel(setting.label); - browser.waitForVisible(inputTrigger); - browser.click(inputTrigger); - - const inputDropDown = page.selectors.combo.container; - browser.waitForVisible(inputDropDown); - - const dropDownValue = page.selectors.combo.itemByText(setting.updated); - browser.waitForVisible(dropDownValue); - browser.waitAndClick(dropDownValue); - - browser.waitForInvisible(inputDropDown); - } else if ('text' === setting.type) { - const input = page.selectors.create_manage_users.current_users.settings.inputByLabel(setting.label, setting.type); - browser.waitForVisible(input); - browser.setValue(input, setting.updated); - expect(browser.getValue(input)).to.equal(setting.updated); - } - }); - it('Ensure that the user dirty message is shown', function () { - const dirtyMessage = page.selectors.create_manage_users.bottom_bar.messageByText('unsaved changes'); - browser.waitForVisible(dirtyMessage); - }); - it('Click the save button', function () { - const saveButton = page.selectors.create_manage_users.current_users.button('Save Changes'); - browser.waitAndClick(saveButton); - - const updateModal = page.selectors.updateSuccessNotification('btest'); - browser.waitForVisible(updateModal); - browser.waitForInvisible(updateModal); - }); - if ('User Type' === setting.label) { - it('Check that the user is not still selected.', function () { - const noUserSelectedModal = page.selectors.create_manage_users.current_users.settings.noUserSelectedModal(); - browser.waitForVisible(noUserSelectedModal); - }); - it('Check that the user is not listed in the Existing Users table', function () { - const updatedUser = page.selectors.create_manage_users.current_users.user_list.col_for_user('btest', 'Username'); - browser.waitForInvisible(updatedUser); - }); - it(`Change the Displayed User Type to: "${setting.updated}"`, function () { - const displayedUserType = page.selectors.create_manage_users.current_users.user_list.toolbar.buttonByLabel('Displaying', setting.original); - browser.waitForVisible(displayedUserType); - browser.waitAndClick(displayedUserType); - - const newUserTypeItem = page.selectors.create_manage_users.current_users.user_list.dropDownItemByText(setting.updated); - browser.waitForVisible(newUserTypeItem); - browser.waitAndClick(newUserTypeItem); - }); - it('Check that the user is listed in the Existing Users table.', function () { - const updatedUser = page.selectors.create_manage_users.current_users.user_list.col_for_user('btest', 'Username'); - browser.waitForVisible(updatedUser); - }); - } else { - it(`Check that "${setting.label}" has been updated successfully to "${setting.updated}"`, function () { - const inputType = 'dropdown' === setting.type ? 'text' : setting.type; - const input = page.selectors.create_manage_users.current_users.settings.inputByLabel(setting.label, inputType); - browser.waitForVisible(input); - const updatedValue = browser.getValue(input); - expect(updatedValue).to.equal(setting.updated); - }); - } - - it('Close the edit user modal', function () { - const closeButton = page.selectors.create_manage_users.current_users.button('Close'); - browser.waitAndClick(closeButton); - }); - }); - }); - }); - describe('Remove the newly created User', function () { - it('Ensure that were on the "Existing Users" tab', function () { - browser.waitForVisible(page.selectors.user_management.tabs.existing_users()); - browser.waitAndClick(page.selectors.user_management.tabs.existing_users()); - browser.waitForVisible(page.selectors.existing_users.table.container); - }); - it('Double click the newly created user', function () { - const usernameCol = page.selectors.existing_users.table.col_for_user('btest', 'Username'); - browser.waitForValue(usernameCol); - browser.doubleClick(usernameCol); - }); - it('Ensure that the Create & Manage Users Dialog opens', function () { - browser.waitForVisible(page.selectors.create_manage_users.window); - }); - it('Ensure that the "Actions" button is visible and click it', function () { - browser.waitForVisible(page.selectors.create_manage_users.current_users.settings.toolbar.actions.button()); - browser.waitForLoadedThenClick(page.selectors.create_manage_users.current_users.settings.toolbar.actions.button()); - }); - it('Ensure that the Actions menu has been displayed', function () { - browser.waitForVisible(page.selectors.create_manage_users.current_users.settings.toolbar.actions.container); - }); - it('Click the "Delete This User" menu item', function () { - const deleteUserItem = page.selectors.create_manage_users.current_users.settings.toolbar.actions.itemWithText('Delete This Account'); - browser.waitForVisible(deleteUserItem); - browser.click(deleteUserItem); - }); - it('Confirm the deletion of the user', function () { - const yesDelete = page.selectors.modal.buttonByText('Delete User', 'Yes'); - browser.waitAndClick(yesDelete); - - const deleteNotification = page.selectors.deleteSuccessNotification('btest'); - browser.waitForVisible(deleteNotification); - browser.waitForInvisible(deleteNotification); - }); - it('Close the User Management Dialog', function () { - const close = page.selectors.create_manage_users.current_users.button('Close'); - browser.waitForVisible(close); - browser.click(close); - }); - it('Ensure that the "Existing Users" table is displayed', function () { - browser.waitForVisible(page.selectors.existing_users.table.container); - }); - it('Check that there is no username', function () { - const usernameCol = page.selectors.existing_users.table.col_for_user('btest', 'Username'); - browser.waitForValue(usernameCol); - const username = browser.getText(usernameCol); - expect(username).to.not.equal('btest'); - }); - it('Check that there is no first name', function () { - const firstNameCol = page.selectors.existing_users.table.col_for_user('btest', 'First Name'); - browser.waitForValue(firstNameCol); - const firstName = browser.getText(firstNameCol); - expect(firstName).to.not.equal('Bob'); - }); - it('Check that there is no name', function () { - const lastNameCol = page.selectors.existing_users.table.col_for_user('btest', 'Last Name'); - browser.waitForValue(lastNameCol); - const lastName = browser.getText(lastNameCol); - expect(lastName).to.not.equal('Test'); - }); - it('Check that there is no email', function () { - const emailCol = page.selectors.existing_users.table.col_for_user('btest', 'E-Mail Address'); - browser.waitForValue(emailCol); - const email = browser.getText(emailCol); - expect(email).to.not.equal('btest@example.com'); - }); - it('Check that there is no role', function () { - const roleCol = page.selectors.existing_users.table.col_for_user('btest', 'Role(s)'); - browser.waitForValue(roleCol); - const roles = browser.getText(roleCol); - expect(roles).to.not.equal('User'); - }); - }); - - page.logout(); -}); diff --git a/tests/ui/test/specs/xdmod/internalDashboard.page.js b/tests/ui/test/specs/xdmod/internalDashboard.page.js deleted file mode 100644 index 8b6a09556e..0000000000 --- a/tests/ui/test/specs/xdmod/internalDashboard.page.js +++ /dev/null @@ -1,370 +0,0 @@ -/* eslint-env node, es6 */ -let roles = require('./../../../../ci/testing.json').role; -let expected = global.testHelpers.artifacts.getArtifact('internalDashboard'); - -class InternalDashboard { - - constructor() { - const self = this; - this.selectors = { - login: { - username: '//input[@id="field_username"]', - password: '//input[@id="field_password"]', - submit: '//input[@type="submit" and @value="Log In"]' - }, - logoutLink: '//a[@id="header-logout"]', - loggedInDisplayName: '//div[@id="dashboard-header"]//b', - header: { - tabs: { - summary: function () { - return self.selectors.tabByText('Summary'); - }, - user_management: function () { - return self.selectors.tabByText('User Management'); - } - } - }, - summary: { - tabs: { - overview: function () { - return self.selectors.tabByText('Overview'); - }, - log_data: function () { - return self.selectors.tabByText('Log Data'); - } - } - }, - user_management: { - tabs: { - account_requests: function () { - return self.selectors.tabByText('XDMoD Account Requests'); - }, - existing_users: function () { - return self.selectors.tabByText('Existing Users'); - }, - user_stats: function () { - return self.selectors.tabByText('User Stats'); - } - } - }, - account_requests: { - toolbar: { - create_manage_users: '#acct_requests_create_manage_users' - } - }, - existing_users: { - toolbar: { - create_manage_users: '#existing_users_create_manage_users' - }, - user_table: '', - table: { - container: '//div[contains(@class, "existing_user_grid")]', - /** - * Retrieve a column via `column_name`, for a user via - * `username` which corresponds to a value located in the - * `Username` column. - * - * @param username {string} - * @param column_name {string} - * @returns {string} - */ - col_for_user: function (username, column_name) { - return `( - //div[contains(@class, "existing_user_grid")]//div[contains(@class, "x-grid3-body")]//table//td[ - count(preceding-sibling::td) + 1 = - count(//div[contains(@class, "existing_user_grid")]//div[contains(@class, "x-grid3-header")]//table//td[.="${column_name}"]/preceding-sibling::td) + 1 - ] - ) [ - count(//div[contains(@class, "existing_user_grid")]//div[contains(@class, "x-grid3-body")]//table//td[ - .="${username}" and - ( - count(preceding-sibling::td) + 1 = - count(//div[contains(@class, "existing_user_grid")]//div[contains(@class, "x-grid3-header")]//table//td[.="Username"]/preceding-sibling::td) + 1 - ) - ]/preceding::div[contains(@class, "x-grid3-row")]) + 1 - ] - `; - } - } - }, - create_manage_users: { - loading_mask: '.admin_panel_editor_mask', - window: '//div[contains(@class, "xdmod_admin_panel")]', - tabs: { - new_user: function () { - return `${self.selectors.create_manage_users.window}//span[contains(@class, 'x-tab-strip-text') and contains(text(), "New User")]`; - }, - current_users: function () { - return `${self.selectors.create_manage_users.window}//span[contains(@class, 'x-tab-strip-text') and contains(text(), "Current Users")]`; - } - }, - buttons: { - close: function () { - return `${self.selectors.create_manage_users.window}//button[contains(@class, "general_btn_close")]`; - }, - create_user: function () { - return `${self.selectors.create_manage_users.window}//button[contains(@class, "admin_panel_btn_create_user")]`; - }, - save_changes: function () { - return `${self.selectors.create_manage_users.window}//button[contains(text(), "Save Changes")]`; - } - }, - current_users: { - container: '//div[@id="admin_tab_existing_user"]', - dialogs: { - deleteUser: { - container: '//div[contains(@class, "delete_user") and contains(@class, "x-window")]', - button: function (text) { - return `${self.selectors.modal.containerByTitle('Delete User')}//button[.="${text}"]`; - } - } - }, - settings: { - container: '//div[@id="admin_panel_user_editor"]', - userDetailsHeader: function (text) { - return `${self.selectors.create_manage_users.current_users.settings.container}//span[.="${text}"]`; - }, - toolbar: { - container: function () { - return `${self.selectors.create_manage_users.current_users.settings.container}//div[contains(@class, "x-toolbar")]`; - }, - actions: { - button: function () { - return `${self.selectors.create_manage_users.current_users.settings.toolbar.container()}//button[.="Actions"]`; - }, - container: '//div[contains(@class, "existing_users_action_menu")]', - itemWithText: function (text) { - return `${self.selectors.create_manage_users.current_users.settings.toolbar.actions.container}//span[.="${text}"]`; - } - } - }, - inputByLabel: function (labelText, inputType) { - return `${self.selectors.create_manage_users.current_users.settings.container}//label[contains(text(), "${labelText}")]/parent::*//input[@type="${inputType}"]`; - }, - dropDownTriggerByLabel: function (labelText) { - return `${self.selectors.create_manage_users.current_users.settings.container}//label[contains(text(), "${labelText}")]/parent::*//img[contains(@class, "x-form-trigger")]`; - }, - noUserSelectedModal: function () { - return `${self.selectors.create_manage_users.current_users.settings.container}//div[contains(@class, 'ext-el-mask-msg')]//div[contains(text(), 'Select A User From The List To The Left')]`; - } - }, - user_list: { - container: function () { - return `${self.selectors.create_manage_users.current_users.container}//div[contains(@class, 'admin_panel_existing_user_list')]`; - }, - toolbar: { - container: function () { - return `${self.selectors.create_manage_users.current_users.user_list.container()}//div[contains(@class, 'x-panel-tbar')]`; - }, - buttonByLabel: function (labelText, buttonText) { - return `${self.selectors.create_manage_users.current_users.user_list.toolbar.container()}//div[contains(text(), "${labelText}")]/following::*//button[contains(text(), "${buttonText}")]`; - } - }, - dropDownItemByText: function (text) { - return `//div[contains(@class, 'x-menu')]//ul[contains(@class, 'x-menu-list')]//span[contains(text(), '${text}')]/parent::*[contains(@class, 'x-menu-item')]`; - }, - /** - * Retrieve a column via `column_name`, for a user via - * `username` which corresponds to a value located in the - * `Username` column. - * - * @param username {string} - * @returns {string} - */ - col_for_user: function (username) { - return `${self.selectors.create_manage_users.current_users.user_list.container()}//div[contains(@class, "x-grid3-body")]//table//td[.="${username}"]`; - } - }, - button: function (text) { - return `//div[@id="admin_tab_existing_user"]//button[.="${text}"]`; - } - }, - new_user: { - container: function () { - return `${self.selectors.create_manage_users.window}//div[@id="admin_tab_create_user"]`; - }, - first_name: function () { - return `${self.selectors.create_manage_users.new_user.container()}//input[contains(@class, "new_user_first_name")]`; - }, - lastName: function () { - return `${self.selectors.create_manage_users.new_user.container()}//input[contains(@class, "new_user_last_name")]`; - }, - emailAddress: function () { - return `${self.selectors.create_manage_users.new_user.container()}//input[contains(@class, "new_user_email_address")]`; - }, - username: function () { - return `${self.selectors.create_manage_users.new_user.container()}//input[contains(@class, "new_user_username")]`; - }, - mapTo: function () { - return `${self.selectors.create_manage_users.new_user.container()}//input[contains(@class, "new_user_map_to")]`; - }, - mapToTrigger: function () { - return `${self.selectors.create_manage_users.new_user.mapTo()}/following-sibling::img[contains(@class, "x-form-trigger")]`; - }, - institution: function () { - return `${self.selectors.create_manage_users.new_user.container()}//input[contains(@class, "new_user_institution")]`; - }, - institution_trigger: function () { - return `${self.selectors.create_manage_users.new_user.institution()}/following-sibling::img[contains(@class, "x-form-trigger")]`; - }, - userType: function () { - return `${self.selectors.create_manage_users.new_user.container()}//input[contains(@class, "new_user_user_type")]`; - }, - aclByName: function (name) { - return `${self.selectors.create_manage_users.new_user.container()}//div[contains(@class, "admin_panel_section_role_assignment_n")]//table[contains(@class, "x-grid3-row-table")]//td[div="${name}"]/following-sibling::td//div[contains(@class, "x-grid3-cell-inner")]/div`; - }, - dialog: function (text) { - return `//div[contains(@class, "x-window") and contains(@class, "x-notification")]//b[contains(@class, "user_management_message") and contains(text(), "${text}")]`; - } - }, - bottom_bar: { - container: function () { - return `${self.selectors.create_manage_users.window}//div[contains(@class, "x-panel-bbar")]`; - }, - messageByText: function (text) { - return `${self.selectors.create_manage_users.bottom_bar.container()}//span[contains(text(), "${text}")]`; - } - } - }, - tabByText: function (name) { - return `//div[@id="dashboard-tabpanel"]//span[contains(@class, "x-tab-strip-text") and contains(text(), "${name}")]`; - }, - combo: { - container: '//div[contains(@class, "x-combo-list") and contains(@style, "visibility: visible")]', - itemByText: function (text) { - return `${self.selectors.combo.container}//div[contains(@class, "x-combo-list-item") and contains(text(), "${text}")]`; - } - }, - createSuccessNotification: function (username) { - return self.selectors.modal.containerByTitle('User Management') + - '//b[text()[1][contains(., "User")] and text()[2][contains(., "created successfully")]]/b[text() = "' + - username + '"]/ancestor::node()[1]'; - }, - deleteSuccessNotification: function (username) { - return self.selectors.modal.containerByTitle('User Management') + - '//b[text()[1][contains(., "User")] and text()[2][contains(., "deleted from the portal")]]/b[text() = "' + - username + '"]/ancestor::node()[1]'; - }, - updateSuccessNotification: function (username) { - return self.selectors.modal.containerByTitle('User Management') + - '//b[text()[1][contains(., "User")] and text()[2][contains(., "updated successfully")]]/b[text() = "' + - username + '"]/ancestor::node()[1]'; - }, - modal: { - containerByTitle: function (title) { - return `//div[contains(@class, "x-window")]//div[contains(@class, "x-window-header")]//span[contains(@class, "x-window-header-text") and text()="${title}"]/ancestor::node()[5]`; - }, - buttonByText: function (modalTitle, buttonText) { - return `${self.selectors.modal.containerByTitle(modalTitle)}//button[contains(text(), "${buttonText}")]`; - }, - tools: { - close: function (modalTitle) { - return `${self.selectors.modal.containerByTitle(modalTitle)}//div[contains(@class, "x-tool-close")]`; - } - } - } - }; - } - - /** - * Log a user w/ the desired role into the Internal Dashboard. - * - * @param desiredRole {string} the role / user that should be logged into - * the Internal Dashboard. - */ - login(desiredRole) { - let username; - let password; - let displayName; - let role; - switch (desiredRole) { - case 'cd': - case 'centerdirector': - role = 'cd'; - break; - case 'cs': - case 'centerstaff': - role = 'cs'; - break; - case 'pi': - case 'principalinvestigator': - role = 'pi'; - break; - case 'usr': - case 'user': - role = 'usr'; - break; - case 'mgr': - case 'admin': - role = 'mgr'; - break; - default: - role = desiredRole; - break; - } - username = roles[role].username; - password = roles[role].password; - displayName = roles[role].givenname + ' ' + roles[role].surname; - this.loginDirect(username, password, displayName); - } - - /** - * Log a user identified by the provided username, password into the - * Internal Dashboard. - * - * @param username {string} the username to use during the login process. - * @param password {string} the password to use during the login process. - * @param displayName {string} the name that should be displayed when the - * user is logged in. - */ - loginDirect(username, password, displayName) { - const self = this; - describe('General', function () { - it('Verify Title', function () { - browser.url('/internal_dashboard'); - let actual = browser.getTitle(); - expect(actual).to.equal(expected.title); - }); - }); - describe('Login', function () { - it('Should Login', function () { - browser.waitForVisible(self.selectors.login.username); - browser.waitForVisible(self.selectors.login.password); - browser.waitForVisible(self.selectors.login.submit); - - browser.setValue(self.selectors.login.username, username); - browser.setValue(self.selectors.login.password, password); - - browser.waitAndClick(self.selectors.login.submit); - browser.waitForVisible(self.selectors.logoutLink); - }); - it('Display the logged in users name', function () { - browser.waitForVisible(self.selectors.loggedInDisplayName); - - let actual = browser.getText(self.selectors.loggedInDisplayName); - expect(actual).to.equal(displayName); - }); - }); - } - - /** - * Log out of XDMoD's internal dashboard - */ - logout() { - const self = this; - describe('Logout', function () { - it('Click the logout link', function () { - browser.waitForVisible(self.selectors.logoutLink); - - browser.click(self.selectors.logoutLink); - }); - it('Display the login page again', function () { - browser.waitForVisible(self.selectors.login.username); - browser.waitForVisible(self.selectors.login.password); - browser.waitForVisible(self.selectors.login.submit); - }); - }); - } -} - -module.exports = new InternalDashboard(); diff --git a/tests/ui/test/specs/xdmod/loginPage.page.js b/tests/ui/test/specs/xdmod/loginPage.page.js deleted file mode 100644 index 6870f8dca1..0000000000 --- a/tests/ui/test/specs/xdmod/loginPage.page.js +++ /dev/null @@ -1,85 +0,0 @@ -/* eslint-env node, es6 */ -var roles = require('./../../../../ci/testing.json').role; -var expected = global.testHelpers.artifacts.getArtifact('loginPage'); -class LoginPage { - - login(desiredRole) { - let username; - let password; - let displayName; - let role; - switch (desiredRole) { - case 'cd': - case 'centerdirector': - role = 'cd'; - break; - case 'usr': - case 'user': - role = 'usr'; - break; - case 'pi': - case 'principalinvestigator': - role = 'pi'; - break; - case 'cs': - case 'centerstaff': - role = 'cs'; - break; - default: - role = desiredRole; - } - username = roles[role].username; - password = roles[role].password; - displayName = roles[role].givenname + ' ' + roles[role].surname; - displayName = displayName.trim(); - describe('General', function () { - it('Verify Logo and Title', function () { - browser.url('/'); - const actual = browser.getTitle(); - expect(actual).to.equal(expected.title); - // $(this.logo).waitForVisible(2000); - $('#logo').waitForVisible(2000); - var logoSize = browser.getElementSize('#logo'); - expect(logoSize.width).to.equal(93); - expect(logoSize.height).to.equal(32); - }); - }); - describe('Login', function login() { - it('Click the login link', function clickLogin() { - browser.waitForInvisible('.ext-el-mask-msg'); - $('a[href*=actionLogin]').click(); - }); - it('Should Login', function doLogin() { - browser.waitForVisible('#btn_sign_in'); - $('#txt_login_username').setValue(username); - $('#txt_login_password').setValue(password); - browser.waitAndClick('#btn_sign_in'); - }); - it('Display Logged in Users Name', function () { - $('#welcome_message').waitForVisible(60000); - expect($('#welcome_message').getText()).to.equal(displayName); - $('#main_tab_panel__about_xdmod').waitForVisible(); - }); - }); - } - logout() { - describe('Logout', function logout() { - it('Click the logout link', function clickLogout() { - browser.waitForInvisible('.ext-el-mask-msg'); - browser.waitAndClick('#logout_link'); - }); - it('Display Logged out State', function clickLogout() { - browser.waitForInvisible('.ext-el-mask-msg'); - $('a[href*=actionLogin]').waitForVisible(); - $('#main_tab_panel__about_xdmod').waitForVisible(); - }); - }); -/* - describe("Update Screenshot Repository", function screenshots() { - it("Should upload screenshots", function screenshotsSync() { - //return browser.sync(); - }); - });*/ - } -} -module.exports = new LoginPage(); diff --git a/tests/ui/test/specs/xdmod/mainToolbar.js b/tests/ui/test/specs/xdmod/mainToolbar.js deleted file mode 100644 index 346fdc068c..0000000000 --- a/tests/ui/test/specs/xdmod/mainToolbar.js +++ /dev/null @@ -1,39 +0,0 @@ -var logIn = require('./loginPage.page.js'); -var mTb = require('./mainToolbar.page.js'); -var mainTab; - -describe('Main Toolbar', function () { - logIn.login('centerdirector'); - describe('Check Tab', function xdmod() { - it('Get Browser Tab ID', function () { - mainTab = browser.getCurrentTabId(); - }); - it("About should change 'Tabs'", function () { - mTb.checkAbout(); - }); - }); - describe('Contact Us', function () { - for (var type in mTb.contactTypes) { - if (mTb.contactTypes.hasOwnProperty(type)) { - // TODO: Fix this with Page Objects? - // eslint-disable-next-line no-loop-func - it(type, function () { - mTb.contactFunc(type); - }); - } - } - }); - describe('Help', function () { - for (var type in mTb.helpTypes) { - if (mTb.helpTypes.hasOwnProperty(type)) { - // TODO: Fix this with Page Objects? - // eslint-disable-next-line no-loop-func - it(type, function () { - mTb.helpFunc(type, mainTab); - browser.pause(1500); - }); - } - } - }); - logIn.logout(); -}); diff --git a/tests/ui/test/specs/xdmod/mainToolbar.page.js b/tests/ui/test/specs/xdmod/mainToolbar.page.js deleted file mode 100644 index 93c4b483f6..0000000000 --- a/tests/ui/test/specs/xdmod/mainToolbar.page.js +++ /dev/null @@ -1,56 +0,0 @@ -class MainToolbar { - constructor() { - this.helpTypes = { - Manual: '#global-toolbar-help-user-manual' - }; - this.contactTypes = { - 'Submit Support Request': '#global-toolbar-contact-us-submit-support-request', - 'Send Message': '#global-toolbar-contact-us-send-message', - 'Request Feature': '#global-toolbar-contact-us-request-feature' - }; - this.toolbarClose = '.x-window .x-tool-close'; - this.toolbarAbout = '#global-toolbar-about'; - this.contactus = '#global-toolbar-contact-us'; - this.help = '#help_button'; - this.about = '#about_xdmod'; - this.container = '.x-window'; - this.header = '.x-window .x-window-header .x-window-header-text'; - this.floatlayer = 'div.x-menu.x-menu-floating.x-layer'; - this.note = '.x-window.x-notification'; - } - helpFunc(type, mainTab) { - $(this.help).click(); - browser.waitForExist(this.floatlayer); - browser.waitForExist(this.helpTypes[type]); - browser.click(this.helpTypes[type]); - var ids = browser.windowHandles().value; - var id = ids.length; - expect(id).to.equal(2); - while (id--) { - if (ids[id] !== mainTab) { - browser.window(ids[id]); - browser.close(); - } - } - browser.window(mainTab); - } - contactFunc(type) { - browser.waitUntilNotExist(this.note); - $(this.contactus).click(); - browser.waitForExist(this.floatlayer); - browser.waitForExist(this.contactTypes[type]); - browser.click(this.contactTypes[type]); - browser.waitForExist(this.container); - expect(browser.getText(this.header)).to.be.equal(type); - browser.pause(500); - browser.waitForExist(this.toolbarClose); - browser.click(this.toolbarClose); - } - checkAbout() { - browser.waitForExist(this.toolbarAbout); - browser.pause(5000); - browser.click(this.toolbarAbout); - browser.waitForExist(this.about); - } -} -module.exports = new MainToolbar(); diff --git a/tests/ui/test/specs/xdmod/metricExplorer.js b/tests/ui/test/specs/xdmod/metricExplorer.js deleted file mode 100644 index 5221f8a9da..0000000000 --- a/tests/ui/test/specs/xdmod/metricExplorer.js +++ /dev/null @@ -1,450 +0,0 @@ -var expected = global.testHelpers.artifacts.getArtifact('metricExplorer'); -var logIn = require('./loginPage.page.js'); -var me = require('./metricExplorer.page.js'); -var cheerio = require('cheerio'); -var XDMOD_REALMS = process.env.XDMOD_REALMS; -describe('Metric Explorer', function metricExplorer() { - var baselineDate = { - start: '2016-12-22', - end: '2017-01-01' - }; - var $container; - var actions = { - chart: { - load: function (chartNumber) { - var mychartNumber = chartNumber || 0; - it('Load Chart', function () { - me.actionLoadChart(mychartNumber); - }); - }, - contextMenu: { - open: function chartContextMenuOpen() { - it('Open Chart Context Menu', function () { - // TODO: Find a better way to open this. Currently there is a chance - // that the click will open the dataseries menu - browser.waitAndClick('#hc-panelmetric_explorer', 10000); - }); - }, - addData: function () { - it('Should Display', function () { - browser.waitAndClick('#hc-panelmetric_explorer', 10000); - browser.waitAndClick(me.selectors.chart.contextMenu.addData, 10000); - browser.waitForVisible('#metric-explorer-chartoptions-add-data-menu', 10000); - browser.click('#logo'); - }); - }, - addFilter: function () { - it('Should Display', function () { - browser.waitAndClick('#hc-panelmetric_explorer', 10000); - browser.waitAndClick(me.selectors.chart.contextMenu.addFilter, 10000); - browser.waitForVisible('#metric-explorer-chartoptions-add-filter-menu', 10000); - browser.click('#logo'); - }); - }, - legend: function () { - it('Click Legend', function selectLegend() { - browser.waitAndClick(me.selectors.chart.contextMenu.legend, 10000); - }); - }, - setLegendPosition: function chartContextMenuLegendPosition(position) { - describe('Set Legend ' + position, function setLegend() { - actions.chart.contextMenu.open(); - actions.chart.contextMenu.legend(); - it('Click ' + position, function selectLegendPosition() { - var posId = me.selectors.chart.contextMenu.legend + '-' + - position.toLowerCase().replace(/ /g, '-'); - browser.waitAndClick(posId); - browser.waitForChart(); - }); - }); - } - } - } - }; - var chartName = 'ME autotest chart ' + Date.now(); - logIn.login('centerdirector'); - describe('Select Tab', function xdmod() { - it('Selected', function meSelect() { - browser.waitForLoadedThenClick(me.selectors.tab); - browser.waitForVisible(me.selectors.container, 3000); - browser.waitForVisible(me.selectors.catalog.container, 10000); - $container = cheerio.load(browser.getHTML(me.selectors.container)); - browser.waitUntilAnimEnd(me.selectors.catalog.collapseButton); - }); - }); - // TODO: Add tests for storage and cloud realms - if (XDMOD_REALMS.includes('jobs')) { - describe('Create and save a chart', function () { - it('Add data via metric catalog', function () { - me.createNewChart(chartName, 'Timeseries', 'Line'); - me.setDateRange('2016-12-30', '2017-01-02'); - me.addDataViaCatalog('Jobs', 'Node Hours: Total', 'None'); - me.checkChart(chartName, 'Node Hours: Total', expected.legend); - me.saveChanges(); - me.clear(); - }); - }); - describe('Basic Scenarios', function basicScenarios() { - it('Add Filters in Toolbar', function () { - me.loadExistingChartByName(chartName); - me.addFiltersFromToolbar('PI'); - me.cancelFiltersFromToolbar(); - }); - it('Edit Filters in Toolbar', function () { - me.editFiltersFromToolbar('Alpine'); - me.clear(); - }); - it('Add/Edit Filters in Data Series Definition', function () { - me.clickLogoAndWaitForMask(); - me.loadExistingChartByName(chartName); - me.addFiltersFromDataSeriesDefinition('PI', 'Alpine'); - me.cancelFiltersFromDataSeriesDefinition(); - }); - it('Edit Filters in Data Series Definition', function () { - me.editFiltersFromDataSeriesDefinition('Alpine'); - me.clear(); - }); - it('Has Instructions', function meConfirmInstructions() { - me.verifyInstructions(); - }); - it('Has three toolbars', function meConfirmToolbars() { - expect($container('.x-toolbar').length).to.equal(3); - }); - it('Has one canned Date Picker', function meConfirmDatePicker() { - // TODO: Make Datepicker have a unique name - expect($container('table[id^=canned_dates]').length).to.equal(1); - }); - - it('Set a known start date', function meSetStartEnd() { - browser.waitForVisible(me.selectors.startDate, 10000); - browser.click(me.selectors.startDate); - browser.setValue(me.selectors.startDate, baselineDate.start); - }); - - it('Set a known end date', function meSetStartEnd() { - browser.waitForVisible(me.selectors.endDate, 10000); - browser.click(me.selectors.endDate); - browser.setValue(me.selectors.endDate, baselineDate.end); - }); - it("'Add Data' via toolbar", function meAddData1() { - // click on CPU Hours: Total - me.addDataViaMenu('.ext-el-mask-msg', 'CPU Hours: Total'); - me.addDataSeriesByDefinition(); - }); - it('Chart contains correct information', function () { - me.checkChart('untitled query 1', 'CPU Hours: Total', expected.legend); - }); - - it("'Add Data' again via toolbar", function meAddData2() { - me.waitForChartToChange(me.addDataViaToolbar); - }); - it('Chart contains correct information', function () { - me.checkChart('untitled query 1', 'CPU Hour', [expected.legend + ' [CPU Hours: Total]', expected.legend + ' [CPU Hours: Per Job]']); - }); - - it('Switch to aggregate chart', function () { - me.waitForChartToChange(me.switchToAggregate); - }); - it('Chart contains correct information', function () { - me.checkChart('untitled query 1', 'CPU Hour', ['CPU Hours: Total', 'CPU Hours: Per Job']); - }); - it('Undo Scratch Pad switch to aggregate', function () { - me.waitForChartToChange(me.undoAggregateOrTrendLine, $container); - }); - it('Check first undo works', function () { - me.checkChart('untitled query 1', 'CPU Hour', [expected.legend + ' [CPU Hours: Total]', expected.legend + ' [CPU Hours: Per Job]']); - }); - it('Undo Scratch Pad second source', function meUndoScratchPad() { - me.waitForChartToChange(me.undoAggregateOrTrendLine, $container); - }); - it('Check second undo works', function () { - me.checkChart('untitled query 1', 'CPU Hours: Total', expected.legend); - }); - it('Attempt Delete Scratchpad Chart', function meDeleteScratchPad() { - me.attemptDeleteScratchpad(); - }); - it('Chart looks the same as previous run', function () { - me.verifyInstructions(); - }); - it('Open chart from load dialog', function meOpenChart() { - me.loadExistingChartByName(chartName); - }); - it('Loaded chart looks the same as previous run', function () { - me.checkChart(chartName, 'Node Hours: Total', expected.legend); - }); - it('Open Chart Options', function meChartOptions() { - browser.waitAndClick(me.selectors.options.button); - }); - it('Chart options looks the same as previous run', function () { - // TODO: Determine Pass case for this without using screenshot - // browser.takeScreenshot(moduleName, me.selectors.container, "chart.options") - // browser.pause(1000); - }); - it('Show Trend Line via Chart Options', function meAddTrendLine() { - browser.waitAndClick(me.selectors.options.trendLine); - me.clickSelectorAndWaitForMask(me.selectors.options.button); - browser.waitForInvisible(me.selectors.options.trendLine); - }); - it('Trend Line looks the same as previous run', function () { - me.checkChart(chartName, 'Node Hours: Total', [expected.legend, 'Trend Line: ' + expected.legend + ' ' + expected.trend_line]); - }); - it('Undo Trend Line', function meUndoTrendLine() { - me.waitForChartToChange(me.undoAggregateOrTrendLine, $container); - }); - it('Undo Trend Line looks the same as previous run', function () { - me.checkChart(chartName, 'Node Hours: Total', expected.legend); - }); - }); - /* The following tests are disabled until such a time as they can be changed to work - * reliably without browser.pause() - - describe('Context Menu', function contextMenu() { - it('Start with scratchpad', function () { - browser.refresh(); - browser.pause(10000); - me.clear(); - browser.pause(2000); - }); - - it('Attempt Delete Scratchpad Chart', function meDeleteScratchPad() { - me.attemptDeleteScratchpad(); - }); - - it('Set a known start date', function meSetStartEnd() { - browser.waitForVisible('#metric_explorer input[id^=start_field_ext]', 10000); - browser.setValue('#metric_explorer input[id^=start_field_ext]', baselineDate.start); - }); - - it('Set a known end date', function meSetStartEnd() { - browser.waitForVisible('#metric_explorer input[id^=end_field_ext]', 10000); - browser.setValue('#metric_explorer input[id^=end_field_ext]', baselineDate.end); - }); - - it('Wait For Metric Catalog', function () { - browser.pause(3000); - browser.waitUntilNotExist('.ext-el-mask-msg'); - browser.waitForVisible(me.selectors.catalog.container, 10000); - }); - it('Generic Starting Point', function () { - me.genericStartingPoint(); - }); - - describe('Add Data Menu', function () { - actions.chart.contextMenu.addData(); - }); - describe('Add Filter Menu', function () { - actions.chart.contextMenu.addFilter(); - }); - describe('Legend Menus', function () { - actions.chart.contextMenu.setLegendPosition('Top Left'); - actions.chart.contextMenu.setLegendPosition('Top Right'); - browser.pause(5000); - describe('Verify after load', function () { - actions.chart.load(1); - actions.chart.contextMenu.open(); - actions.chart.contextMenu.legend(); - it('legend Items set Properly', function () { - browser.waitForVisible('#metric-explorer-chartoptions-legend-options', 2000); - var legendText = browser.getText('#metric-explorer-chartoptions-legend-options .x-menu-item-checked'); - expect(legendText).to.equal('Bottom Center (Default)', 'Loaded chart has non default legend'); - }); - }); - }); - }); - describe('Pie Charts', function pieCharts() { - describe("Can't use timeseries data", function noTimeSeries() { - it('Start with scratchpad', function () { - me.clear(); - }); - it("Add Data via the 'Add Data' Menu", function addData() { - // 'Add Data' via the 'Add Data' menu: - // 'Add Data' -> Jobs -> CPU Hours: Total -> 'Add' - // wait for the global mask to disapear - me.addDataViaMenu('.ext-el-mask-msg', '2'); - }); - it("Set 'Group By' to resource", function findGroupBy() { - me.setGroupByToResource(); - }); - it("'Add' data", function add() { - browser.waitAndClick('#adp_submit_button'); - browser.waitUntilNotExist('.ext-el-mask'); - }); - it('Verify that the chart has been rendered', function chartRendered() { - // Attempt to ascertain if the HighChartPanel ( the panel that contains the chart contents ) is hidden or not. - // Pause so we can determine if the chart was actually loaded. - browser.waitForVisible('#hc-panelmetric_explorer', 3000); - // If the chart was successfully loaded / rendered this should return false. - var execReturn = browser.execute('return CCR.xdmod.ui.metricExplorer.chartViewPanel.items.get(0).hidden;').value; - expect(execReturn).to.equal(false); - }); - it('Chart looks the same as previous run', function () { - // TODO: Determine Pass case for this without using screenshot - // browser.takeScreenshot(moduleName, me.selectors.container, "pie.loaded"); - }); - it("Select the 'Data' toolbar button", function selectData() { - browser.click(me.selectors.data.button); - }); - it('Click the First Row', function jobsRow() { - browser.waitForVisible('.x-menu-floating:not(.x-hide-offsets)'); - browser.execute('return CCR.xdmod.ui.metricExplorer.datasetsGridPanel.getView().getRow(0)').doubleClick(); - }); - it("Set 'Display Type' to 'Pie'", function clickDisplayType() { - me.setToPie(); - }); - it('Verify error message', function checkErrorMessage() { - me.verifyError(); - }); - }); - describe('Aggregate data', function useAggregateData() { - it('Start with scratchpad', function freshChart() { - me.clear(); - }); - }); // Aggregate data - }); // Pie Charts - describe('Chart Interactions', function chartInteractions() { - it('Should start with a new chart', function newChart() { - me.clear(); - }); - describe('Should start start with a dataset', function dataset() { - it("Add Data via the 'Add Data' Menu", function addData() { - // 'Add Data' via the 'Add Data' menu: - // 'Add Data' -> Jobs -> CPU Hours: Total -> 'Add' - // wait for the global mask to disapear - me.addDataViaMenu('div.x-panel.x-masked-relative.x-masked', '2'); - }); - it("Set 'Group By' to resource", function findGroupBy() { - me.setGroupByToResource(); - }); - it("'Add' data", function add() { - browser.waitAndClick('#adp_submit_button'); - browser.waitUntilNotExist('.ext-el-mask'); - }); - it('Verify that the chart has been rendered', function chartRendered() { - // Attempt to ascertain if the HighChartPanel ( the panel that contains the chart contents ) is hidden or not. - // Pause so we can determine if the chart was actually loaded. - browser.waitForChart(); - browser.pause(750); - // If the chart was successfully loaded / rendered this should return false. - var execReturn = browser.execute('return CCR.xdmod.ui.metricExplorer.chartViewPanel.items.get(0).hidden;'); - expect(execReturn.value).to.equal(false); - }); - it('Chart looks the same as previous run', function () { - // TODO: Determine Pass case for this without using screenshot - // browser.takeScreenshot(moduleName, me.selectors.container, "highcharts.loaded") - }); - }); // Should start with a dataset. - - describe('Chart Titles', function chartTitle() { - describe('Update the Chart Title with a value that contains html entities', function editChartTitleDialog() { - it('Click the Chart Title element', function clickChartTitleElement() { - browser.waitAndClick(me.selectors.chart.title); - browser.waitForVisible(me.selectors.chart.titleInput, 3000); - }); - - it('Update the Chart Title input element', function editChartTitle() { - browser.pause(2000); - browser.clearElement(me.selectors.chart.titleInput); - browser.setValue(me.selectors.chart.titleInput, me.newTitle); - }); - - it('Click the Ok element', function clickOkElement() { - browser.pause(2000); - browser.waitAndClick(me.selectors.chart.titleOkButton); - }); - - it('Chart Title element should be updated', function checkChartTitle() { - browser.pause(2500); - me.verifyHighChartsTitle(me.newTitle); - }); - - it('Chart Title in Chart Options should be updated', function verifyChartOptions() { - me.chartTitleInOptionsUpdated(); - }); - }); - - describe('Set the title to a value that does not include html entities', function noHtmlEntities() { - it('click the Chart Title', function clickChartTitle() { - browser.waitForVisible(me.selectors.chart.title, 3000); - browser.click(me.selectors.chart.title); - browser.waitForVisible(me.selectors.chart.titleInput, 3000); - }); - - it('clear the title field', function clearTitleField() { - browser.clearElement(me.selectors.chart.titleInput); - }); - - it('set the title field to a value that does not contain html entities.', function setWithNoEntities() { - browser.pause(500); - browser.setValue(me.selectors.chart.titleInput, me.originalTitle); - browser.pause(2000); - }); - - it("click 'ok' to confirm the change", function confirmTheChange() { - browser.click(me.selectors.chart.titleOkButton); - browser.waitForVisible(me.selectors.chart.title, 3000); - }); - - it('get the new value from the chart title to verify that it has in fact changed', function checkNewValue() { - browser.pause(500); - var execReturn = browser.execute('return document.querySelector("' + me.selectors.chart.title + '").textContent;'); - expect(execReturn.value).to.be.a('string'); - expect(execReturn.value).to.equal(me.originalTitle); - }); - }); - - describe('Arrow Keys', function () { - it('Arrow keys can be used in chart options title', function () { - me.arrowKeys(); - }); - }); - describe('Accept html entity input via the Chart Options -> Chart Title element.', function editChartTitleChartOptions() { - it('set the Chart Options -> Chart Title value with html entities', function setTitleWithHtmlEntities() { - browser.pause(2000); - me.setTitleWithOptionsMenu(me.newTitle); - browser.pause(2000); - }); - it('click the Chart Options button again', function clickChartOptions() { - browser.waitForChart(); - browser.waitAndClick(me.selectors.options.button); - browser.waitForChart(); - browser.pause(3000); - }); - it('verify that the HighCharts chart title has the new value', function highChartsTitle() { - me.verifyHighChartsTitle(me.newTitle); - }); - it('verify that the Edit Chart Title Modal has the new value', function chartTitleModal() { - me.verifyEditChartTitle(); - }); - }); - - describe('Providing an empty title value', function shouldAcceptEmptyTitle() { - it('Set the chart title to an empty value', function setTitleEmpty() { - me.setChartTitleViaChart(''); - }); - - it('Get the new chart title and confirm that no svg element was generated', function confirmEmptyChartTitle() { - browser.pause(500); - var titleChange = browser.execute('return document.querySelector("' + me.selectors.chart.title + '");'); - // expect(titleChange.state).to.equal('success'); - expect(titleChange._status).to.equal(0); - expect(titleChange.value).to.equal(null); - }); - }); - - describe('Providing an exceptionally large title should be ok', function shouldNotAcceptLargeTitle() { - var largeTitle = me.generateTitle(900); - it('Set the chart title to an exceptionally large value (' + largeTitle.length + ')', function setALargeTitle() { - me.setTitleWithOptionsMenu(largeTitle); - browser.waitAndClick(me.selectors.options.button); - }); - - it('Confirm that the chart title has been changed', function confirmTheTitleWasSet() { - me.confirmChartTitleChange(largeTitle); - }); - }); - }); - }); - */ - } - logIn.logout(); -}); diff --git a/tests/ui/test/specs/xdmod/metricExplorer.page.js b/tests/ui/test/specs/xdmod/metricExplorer.page.js deleted file mode 100644 index 940535c966..0000000000 --- a/tests/ui/test/specs/xdmod/metricExplorer.page.js +++ /dev/null @@ -1,572 +0,0 @@ -class MetricExplorer { - constructor() { - this.meselectors = testHelpers.metricExplorer; - this.originalTitle = '(untitled query 2)'; - this.newTitle = '"& (untitled query) 2 &"'; - this.possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - this.selectors = { - tab: '#main_tab_panel__metric_explorer', - startDate: '#metric_explorer input[id^=start_field_ext]', - endDate: '#metric_explorer input[id^=end_field_ext]', - toolbar: { - buttonByName: function (name) { - return '//div[@id="metric_explorer"]//table[@class="x-toolbar-ct"]//button[text()="' + name + '"]/ancestor::node()[5]'; - }, - addData: function (name) { - return '//div[@id="metric-explorer-chartoptions-add-data-menu"]//span[contains(text(), "' + name + '")]'; - }, - addDataGroupBy: function (groupBy) { - return "//div[contains(@class, 'x-menu')][contains(@class, 'x-menu-floating')][contains(@class, 'x-layer')][contains(@style, 'visibility: visible')]//span[contains(text(), '" + groupBy + "')]"; - } - }, - container: '#metric_explorer', - load: { - button: function meLoadButtonId() { - return 'button=Load Chart'; - }, - firstSaved: '.x-menu-floating:not(.x-hide-offsets) .x-grid3-body .x-grid3-row-first', - chartNum: function meChartByIndex(number) { - var mynumber = number + 1; - return '.x-menu-floating:not(.x-hide-offsets) .x-grid3-body > div:nth-child(' + mynumber + ')'; - }, - dialog: '//div[contains(@class,"x-grid3-header-inner")]//div[contains(@class,"x-grid3-hd-name") and text() = "Chart Name"]/ancestor::node()[8]', - chartByName: function (name) { - return module.exports.selectors.load.dialog + '//div[contains(@class,"x-grid3-cell-inner") and text() = "' + name + '"]'; - } - }, - newChart: { - topMenuByText: function (name) { - return '//div[@id="me_new_chart_menu"]//span[@class="x-menu-item-text" and text() = "' + name + '"]'; - }, - subMenuByText: function (topText, name) { - return '//div[@id="me_new_chart_submenu_' + topText + '"]//span[@class="x-menu-item-text" and contains(text(),"' + name + '")]'; - }, - modalDialog: { - box: '//span[@class="x-window-header-text" and text() = "New Chart"]/ancestor::node()[5]', - textBox: function () { - return module.exports.selectors.newChart.modalDialog.box + '//input[contains(@class,"x-form-text")]'; - }, - checkBox: function () { - return module.exports.selectors.newChart.modalDialog.box + '//input[contains(@class,"x-form-checkbox")]'; - }, - ok: function () { - return module.exports.selectors.newChart.modalDialog.box + '//button[text() = "Ok"]'; - }, - cancel: function () { - return module.exports.selectors.newChart.modalDialog.box + '//button[text() = "Cancel"]'; - } - } - }, - dataSeriesDefinition: { - dialogBox: '//div[contains(@class,"x-panel-header")]/span[@class="x-panel-header-text" and contains(text(),"Data Series Definition")]/ancestor::node()[4]', - addButton: '#adp_submit_button' - }, - deleteChart: { - dialogBox: '//div[contains(@class,"x-window-header")]/span[@class="x-window-header-text" and contains(text(),"Delete Selected Chart")]/ancestor::node()[5]', - buttonByLabel: function (label) { - return module.exports.selectors.deleteChart.dialogBox + '//button[text()="' + label + '"]'; - } - }, - addData: { - button: '.x-btn-text.add_data', - secondLevel: '.x-menu-floating:not(.x-hide-offsets):not(.x-menu-nosep)' - }, - data: { - button: 'button=Data', - container: '', - modal: { - updateButton: 'button=Update', - groupBy: { - input: 'input[name=dimension]' - } - } - }, - options: { - aggregate: '#aggregate_cb', - button: '#metric_explorer button.chartoptions', - trendLine: '#me_trend_line', - swap: '#me_chart_swap_xy', - title: 'div.x-menu.x-menu-floating.x-layer.x-menu-nosep[style*="visibility: visible"] #me_chart_title' - }, - chart: { - svg: '//div[@id="metric_explorer"]//div[contains(@class, "plot-container")]//*[local-name() = "svg"]', - titleByText: function (title) { - return module.exports.selectors.chart.svg + '/*[name()="g" and contains(@class, "infolayer")]//*[name()="g" and contains(@class, "annotation") and @data-index="0"]//*[local-name() = "text" and contains(text(),"' + title + '")]'; - }, - credits: function () { - return module.exports.selectors.chart.svg + '/*[name()="g" and contains(@class, "infolayer")]//*[name()="g" and contains(@class, "annotation") and @data-index="2"]//*[name()="text"and contains(text(),"Powered by XDMoD")]'; - }, - yAxisTitle: function () { - return module.exports.selectors.chart.svg + '//*[name() = "g" and contains(@class, "g-ytitle")]/*[name() = "text" and contains(@class,"ytitle")]'; - }, - legend: function () { - return module.exports.selectors.chart.svg + '//*[name() = "g" and contains(@class, "legend")]//*[name()="text" and contains(@class, "legendtext")]'; - }, - legendContent: (name) => `${module.exports.selectors.chart.svg}//*[name() = "text" and contains(@class, "legendtext") and contains(text(), "${name}")]`, - seriesMarkers: function (seriesId) { - switch (seriesId) { - case 0: - return `${module.exports.selectors.chart.svg}//*[name()="g" and @class="cartesianlayer"]//*[name()="g" and @class="points"]//*[name()="path" and @class="point"]`; - default: - return `${module.exports.selectors.chart.svg}//*[name()="g" and contains(@class, "xy${seriesId}")]//*[name()="path" and @class="point"]`; - } - }, - title: () => `(${module.exports.selectors.chart.svg})[2]//*[name()="g" and @data-index="0" and contains(@class, "annotation")]//*[name()="text"]`, - titleInput: 'div.x-menu.x-menu-floating.x-layer.x-menu-nosep[style*="visibility: visible"] input[type=text]', - titleOkButton: 'div.x-menu.x-menu-floating.x-layer.x-menu-nosep[style*="visibility: visible"] table.x-btn.x-btn-noicon.x-box-item:first-child button', - titleCancelButton: 'div.x-menu.x-menu-floating.x-layer.x-menu-nosep[style*="visibility: visible"] table.x-btn.x-btn-noicon.x-box-item:last-child button', - contextMenu: { - menuByTitle: (title) => '//div[contains(@class, "x-menu x-menu-floating") and contains(@style, "visibility: visible;")]//span[contains(@class, "menu-title") and contains(text(), "' + title + '")]//ancestor::node()[4]/ul', - menuItemByText: function (menuTitle, itemText) { - return module.exports.selectors.chart.contextMenu.menuByTitle(menuTitle) + '//li/a//span[text()="' + itemText + '"]'; - }, - container: '#metric-explorer-chartoptions-context-menu', - legend: '#metric-explorer-chartoptions-legend', - addData: '#metric-explorer-chartoptions-add-data', - addFilter: '#metric-explorer-chartoptions-add-filter' - }, - axis: '#metric_explorer .highcharts-yaxis-labels' - }, - catalog: { - panel: '//div[@id="metric_explorer"]//div[contains(@class,"x-panel")]//span[text()="Metric Catalog"]/ancestor::node()[2]', - collapseButton: '//div[@id="metric_explorer"]//div[contains(@class,"x-panel")]//span[text()="Metric Catalog"]/ancestor::node()[2]//div[contains(@class,"x-tool-collapse-west")]', - expandButton: '//div[@id="metric_explorer"]//div[contains(@class,"x-panel")]//div[contains(@class,"x-tool-expand-west")]', - container: '#metric_explorer > div > .x-panel-body-noborder > .x-border-panel:not(.x-panel-noborder)', - tree: '#metric_explorer > div > .x-panel-body-noborder > .x-border-panel:not(.x-panel-noborder) .x-tree-root-ct', - rootNodeByName: function (name) { - return '//div[@id="metric_explorer"]//div[@class="x-tree-root-node"]/li/div[contains(@class,"x-tree-node-el")]//span[text() = "' + name + '"]'; - }, - nodeByPath: function (topname, childname) { - return module.exports.selectors.catalog.rootNodeByName(topname) + '/ancestor::node()[3]//span[text() = "' + childname + '"]'; - }, - addToChartMenu: { - container: '//span[@class="x-menu-text"]/span[contains(text(),"Add To Chart:")]/ancestor::node()[3]', - itemByName: function (name) { - return module.exports.selectors.catalog.addToChartMenu.container + '//span[@class="x-menu-item-text" and text() = "' + name + '"]'; - } - } - }, - buttonMenu: { - firstLevel: '.x-menu-floating:not(.x-hide-offsets)' - }, - filters: { - toolbar: { - byName: function (name) { - return '//div[@id="grid_filters_metric_explorer"]//div[contains(@class, "x-grid3-col-value_name") and contains(text(), "' + name + '")]/ancestor::node()[2]/td[contains(@class, "x-grid3-td-checked")]/div/div[contains(@class, "x-grid3-check-col-on")]'; - } - } - } - }; - } - undo($container) { - return '#' + $container('button.x-btn-text-icon') - .closest('table') - .attr('id'); - } - createNewChart(chartName, datasetType, plotType) { - browser.waitForChart(); - browser.click(this.selectors.toolbar.buttonByName('New Chart')); - browser.waitAndClick(this.selectors.newChart.topMenuByText(datasetType)); - browser.waitAndClick(this.selectors.newChart.subMenuByText(datasetType, plotType)); - browser.waitAndClick(this.selectors.newChart.modalDialog.textBox()); - browser.setValue(this.selectors.newChart.modalDialog.textBox(), chartName); - browser.click(this.selectors.newChart.modalDialog.ok()); - browser.waitForInvisible(this.selectors.newChart.modalDialog.box); - browser.waitForVisible('//div[@class="x-grid-empty"]/b[text()="No data is available for viewing"]'); - browser.waitForAllInvisible('.ext-el-mask'); - } - waitForLoaded() { - browser.waitForVisible(this.selectors.container, 3000); - browser.waitForVisible(this.selectors.catalog.container, 10000); - browser.waitUntilAnimEnd(this.selectors.catalog.collapseButton); - } - setDateRange(start, end) { - this.clickSelectorAndWaitForMask(this.selectors.startDate); - browser.setValue(this.selectors.startDate, start); - browser.waitAndClick(this.selectors.endDate); - browser.setValue(this.selectors.endDate, end); - browser.click(this.selectors.toolbar.buttonByName('Refresh')); - browser.waitForAllInvisible('.ext-el-mask'); - } - addDataViaCatalog(realm, statistic, groupby) { - browser.waitForAllInvisible('.ext-el-mask'); - browser.waitForVisible(this.selectors.catalog.container, 10000); - browser.waitUntilAnimEndAndClick(this.selectors.catalog.rootNodeByName(realm)); - browser.waitUntilAnimEndAndClick(this.selectors.catalog.nodeByPath(realm, statistic)); - browser.waitForVisible(this.selectors.catalog.addToChartMenu.container); - browser.waitUntilAnimEndAndClick(this.selectors.catalog.addToChartMenu.itemByName(groupby)); - browser.waitForChart(); - } - saveChanges() { - browser.click(this.selectors.toolbar.buttonByName('Save')); - browser.waitAndClick('//span[@class="x-menu-item-text" and contains(text(),"Save Changes")]'); - } - addFiltersFromToolbar(filter) { - let filterByDialogBox = '//div[contains(@class,"x-panel-header")]/span[@class="x-panel-header-text" and contains(text(),"Filter by")]/ancestor::node()[4]'; - this.clickSelectorAndWaitForMask(this.selectors.toolbar.buttonByName('Add Filter')); - browser.waitAndClick(`//div[@id="metric-explorer-chartoptions-add-filter-menu"]//span[@class="x-menu-item-text" and text() = "${filter}"]`); - browser.waitForVisible(filterByDialogBox + '//div[@class="x-grid3-check-col x-grid3-cc-checked"]', 3000); - let checkboxes = browser.elements(filterByDialogBox + '//div[@class="x-grid3-check-col x-grid3-cc-checked"]'); - if (checkboxes.value.length !== 0) { - for (let i = 0; i < checkboxes.value.length; i++) { - browser.elementIdClick(checkboxes.value[i].ELEMENT); - } - } - browser.waitAndClick(filterByDialogBox + '//button[@class=" x-btn-text" and contains(text(), "Ok")]'); - expect(browser.element(this.selectors.chart.svg + '//*[name()="text" and @class="undefinedsubtitle"]')).to.exist; - } - editFiltersFromToolbar(name) { - let subtitleSelector = this.selectors.chart.svg + '/*[name()="g" and contains(@class, "infolayer")]//*[name()="g" and contains(@class, "annotation") and @data-index="1"]'; - for (let i = 0; i < 100; i++) { - if (browser.isVisible('//div[@id="grid_filters_metric_explorer"]')) { - break; - } - browser.click(this.selectors.toolbar.buttonByName('Filters')); - } - browser.waitAndClick(this.selectors.filters.toolbar.byName(name)); - browser.waitUntilNotExist(this.selectors.filters.toolbar.byName(name)); - browser.waitAndClick('//div[@id="grid_filters_metric_explorer"]//button[@class=" x-btn-text" and contains(text(), "Apply")]'); - browser.waitUntilNotExist(subtitleSelector + `//*[contains(text(), "${name}")]`); - } - cancelFiltersFromToolbar() { - this.clickLogoAndWaitForMask(); - let checkboxSelector = '//div[@id="grid_filters_metric_explorer"]//div[contains(@class, "x-grid3-check-col-on")]'; - browser.waitAndClick(this.selectors.toolbar.buttonByName('Filters')); - browser.waitForVisible(checkboxSelector, 3000); - let checkboxes = browser.elements(checkboxSelector); - if (checkboxes.value.length !== 0) { - for (let i = 0; i < 2; i++) { - browser.elementIdClick(checkboxes.value[i].ELEMENT); - } - } - browser.waitAndClick('//div[@id="grid_filters_metric_explorer"]//button[@class=" x-btn-text" and contains(text(), "Cancel")]'); - expect(browser.elements(checkboxSelector).value.length).to.equal(checkboxes.value.length); - this.clickLogoAndWaitForMask(); - } - openDataSeriesDefinitionFromDataPoint() { - this.clickLogoAndWaitForMask(); - this.clickFirstDataPoint(); - browser.waitUntilAnimEndAndClick(this.selectors.chart.contextMenu.menuItemByText('Data Series:', 'Edit Dataset')); - } - addFiltersFromDataSeriesDefinition(filter, name) { - this.clickLogoAndWaitForMask(); - this.openDataSeriesDefinitionFromDataPoint(); - browser.waitAndClick(this.selectors.dataSeriesDefinition.dialogBox + '//button[contains(@class, "add_filter") and text() = "Add Filter"]'); - browser.waitAndClick(`//div[contains(@class, "x-menu x-menu-floating") and contains(@style, "visibility: visible;")]//li/a//span[text()="${filter}"]`); - browser.waitForVisible('//div[contains(@class, "x-menu x-menu-floating") and contains(@style, "visibility: visible;")]//div[@class="x-grid3-check-col x-grid3-cc-checked"]', 3000); - let checkboxes = browser.elements('//div[contains(@class, "x-menu x-menu-floating") and contains(@style, "visibility: visible;")]//div[@class="x-grid3-check-col x-grid3-cc-checked"]'); - if (checkboxes.value.length !== 0) { - for (let i = 0; i < checkboxes.value.length; i++) { - browser.elementIdClick(checkboxes.value[i].ELEMENT); - } - } - browser.waitAndClick('//div[contains(@class, "x-menu x-menu-floating") and contains(@style, "visibility: visible;")]//button[@class=" x-btn-text" and contains(text(), "Ok")]'); - browser.waitAndClick(this.selectors.dataSeriesDefinition.addButton); - browser.waitForExist(this.selectors.chart.legendContent(name)); - } - editFiltersFromDataSeriesDefinition(name) { - this.clickLogoAndWaitForMask(); - this.openDataSeriesDefinitionFromDataPoint(); - browser.waitAndClick(this.selectors.dataSeriesDefinition.dialogBox + '//button[contains(@class, "filter") and contains(text(), "Filters")]'); - browser.waitAndClick('//div[contains(@class, "x-menu x-menu-floating") and contains(@style, "visibility: visible;")]//td[contains(@class, "x-grid3-check-col-td")]'); - browser.waitAndClick('//div[contains(@class, "x-menu x-menu-floating") and contains(@style, "visibility: visible;")]//button[@class=" x-btn-text" and contains(text(), "Apply")]'); - browser.waitForChart(); - browser.waitAndClick(this.selectors.dataSeriesDefinition.dialogBox + '//div[contains(@class, "x-panel-header")]'); - browser.waitAndClick(this.selectors.dataSeriesDefinition.addButton); - browser.waitUntilNotExist(this.selectors.chart.legendContent(name)); - } - cancelFiltersFromDataSeriesDefinition() { - this.clickLogoAndWaitForMask(); - let checkboxSelector = '//div[contains(@class, "x-menu x-menu-floating") and contains(@style, "visibility: visible;")]//div[contains(@class, "x-grid3-check-col-on")]'; - this.openDataSeriesDefinitionFromDataPoint(); - browser.waitAndClick(this.selectors.dataSeriesDefinition.dialogBox + '//button[contains(@class, "filter") and contains(text(), "Filters")]'); - browser.waitForVisible(checkboxSelector, 3000); - let checkboxes = browser.elements(checkboxSelector); - if (checkboxes.value.length !== 0) { - for (let i = 0; i < 2; i++) { - browser.elementIdClick(checkboxes.value[i].ELEMENT); - } - } - browser.waitAndClick('//div[contains(@class, "x-menu x-menu-floating") and contains(@style, "visibility: visible;")]//button[@class=" x-btn-text" and contains(text(), "Cancel")]'); - expect(browser.elements(checkboxSelector).value.length).to.equal(checkboxes.value.length); - browser.waitAndClick(this.selectors.dataSeriesDefinition.dialogBox + '//div[contains(@class, "x-panel-header")]'); - browser.waitAndClick(this.selectors.dataSeriesDefinition.addButton); - } - clear() { - browser.refresh(); - browser.waitForVisible('#logout_link', 3000); - } - generateTitle(size) { - var result = ''; - for (var i = 0; i < size; i++) { - result += this.possible.charAt(Math.floor(Math.random() * this.possible.length)); - } - return result; - } - setToPie() { - browser.execute("return document.getElementsByName('display_type')[0];").click(); - browser.execute("return document.querySelector('div.x-layer.x-combo-list[style*=\"visibility: visible\"] div.x-combo-list-inner div.x-combo-list-item:last-child');").click(); - } - - verifyError() { - var invalidChart = browser.execute("return document.querySelectorAll('div.x-window.x-window-plain.x-window-dlg[style*=\"visibility: visible\"] span.x-window-header-text')[0];").getText(); - expect(invalidChart).to.equal('Invalid Chart Display Type'); - var errorText = browser.execute("return document.querySelectorAll('div.x-window.x-window-plain.x-window-dlg[style*=\"visibility: visible\"] span.ext-mb-text')[0];").getText(); - expect(errorText).to.contain('You cannot display timeseries data in a pie chart.'); - expect(errorText).to.contain('Please change the dataset or display type.'); - } - - arrowKeys() { - browser.waitForChart(); - browser.waitForLoadedThenClick(this.selectors.options.button); - browser.waitForLoadedThenClick(this.selectors.options.title); - var cursorPosition = browser.execute('return document.getElementById("me_chart_title").selectionStart;'); - expect(cursorPosition._status).to.equal(0); - expect(cursorPosition.value).to.equal(this.originalTitle.length, 'Cursor Position not at end'); - browser.keys('Arrow_Up'); - var newPosition = browser.execute('return document.getElementById("me_chart_title").selectionStart;'); - expect(newPosition._status).to.equal(0); - expect(newPosition.value).to.equal(0, 'Cursor Position not at begining'); - browser.waitAndClick(this.selectors.options.button); - } - addDataViaMenu(maskName, n) { - browser.waitUntilNotExist(maskName); - browser.waitForVisible(this.selectors.catalog.container, 5000); - browser.waitAndClick(this.selectors.addData.button); - browser.waitAndClick('//div[@id="metric-explorer-chartoptions-add-data-menu"]//span[contains(text(), "Jobs")]'); - browser.waitAndClick("//div[contains(@class, 'x-menu')][contains(@class, 'x-menu-floating')][contains(@class, 'x-layer')][contains(@style, 'visibility: visible')]//span[contains(text(), '" + n + "')]"); - browser.waitForVisible(this.selectors.dataSeriesDefinition.dialogBox); - } - addDataSeriesByDefinition() { - browser.waitAndClick(this.selectors.dataSeriesDefinition.addButton); - browser.waitForInvisible(this.selectors.dataSeriesDefinition.dialogBox); - } - loadExistingChartByName(name) { - browser.waitUntilAnimEnd(this.selectors.catalog.collapseButton, 5000, 50); - browser.waitForVisible(this.selectors.toolbar.buttonByName('Load Chart')); - browser.click(this.selectors.toolbar.buttonByName('Load Chart')); - browser.waitForVisible(this.selectors.load.dialog); - browser.waitAndClick(this.selectors.load.chartByName(name)); - browser.waitForInvisible(this.selectors.load.dialog); - browser.waitUntilAnimEnd(this.selectors.catalog.expandButton, 5000, 50); - } - checkChart(chartTitle, yAxisLabel, legend, isValidChart = true) { - browser.waitForVisible(this.selectors.chart.titleByText(chartTitle)); - var selToCheck; - if (isValidChart) { - selToCheck = this.selectors.chart.credits(); - } else { - selToCheck = this.selectors.chart.titleByText(chartTitle); - } - browser.waitForVisible(selToCheck); - for (let i = 0; i < 100; i++) { - try { - browser.click(selToCheck); - break; - } catch (e) { - browser.waitForAllInvisible('.ext-el-mask'); - } - } - - if (yAxisLabel) { - browser.waitForExist(this.selectors.chart.yAxisTitle()); - var yAxisElems = browser.elements(this.selectors.chart.yAxisTitle()); - if (typeof yAxisLabel === 'string') { - expect(yAxisElems.value.length).to.equal(1); - expect(browser.elementIdText(yAxisElems.value[0].ELEMENT).value).to.equal(yAxisLabel); - } else { - expect(yAxisElems.value.length).to.equal(yAxisLabel.length); - for (let i = 0; i < legend.length; i++) { - expect(browser.elementIdText(yAxisElems.value[i].ELEMENT).value).to.equal(yAxisLabel[i]); - } - } - } - - if (legend) { - browser.waitForExist(this.selectors.chart.legend()); - var legendElems = browser.elements(this.selectors.chart.legend()); - if (typeof legend === 'string') { - expect(legendElems.value.length).to.equal(1); - expect(browser.elementIdText(legendElems.value[0].ELEMENT).value).to.equal(legend); - } else { - expect(legendElems.value.length).to.equal(legend.length); - for (let i = 0; i < legend.length; i++) { - expect(browser.elementIdText(legendElems.value[i].ELEMENT).value).to.equal(legend[i]); - } - } - } - } - /** - * Call the action function then wait until the current loaded Highcharts chart - * disappears. This function should only be called if there is an active highcharts - * chart and the action should result in a chart change. - * - * @params function() action - */ - waitForChartToChange(action) { - var elem = browser.elements(this.selectors.chart.svg); - if (arguments.length > 1) { - action.apply(this, [].slice.call(arguments, 1)); - } else { - action.call(this); - } - try { - let i = 0; - while (browser.elementIdDisplayed(elem.value[0].ELEMENT) && i < 20) { - browser.pause(100); - i++; - } - } catch (err) { - // OK the element has gone away - } - } - setTitleWithOptionsMenu(title) { - browser.waitForChart(); - browser.waitAndClick(this.selectors.options.button); - browser.waitForVisible(this.selectors.options.title, 5000); - browser.clearElement(this.selectors.options.title); - browser.pause(500); - browser.setValue(this.selectors.options.title, title); - } - verifyHighChartsTitle(title) { - var execReturn = browser.execute('return Ext.util.Format.htmlDecode(document.querySelector("' + this.selectors.chart.title + '").textContent);'); - expect(execReturn._status).to.equal(0); - expect(execReturn.value).to.be.a('string'); - expect(execReturn.value).to.equal(title); - } - verifyEditChartTitle() { - browser.waitAndClick(this.selectors.chart.title); - browser.waitForVisible(this.selectors.chart.titleInput); - var titleValue = browser.getValue(this.selectors.chart.titleInput); - expect(titleValue).to.be.a('string'); - expect(titleValue).to.equal(this.newTitle); - browser.waitAndClick(this.selectors.chart.titleOkButton); - browser.waitForChart(); - } - verifyInstructions() { - browser.waitForVisible('//div[@id="metric_explorer"]//div[@class="x-grid-empty"]//b[text()="No data is available for viewing"]'); - testHelpers.instructions(browser, 'metricExplorer', this.selectors.container); - } - setChartTitleViaChart(title) { - browser.waitForChart(); - browser.waitAndClick(this.selectors.chart.title); - browser.waitForVisible(this.selectors.chart.titleInput); - browser.clearElement(this.selectors.chart.titleInput); - browser.setValue(this.selectors.chart.titleInput, title); - browser.pause(500); - browser.waitAndClick(this.selectors.chart.titleOkButton); - } - setGroupByToResource() { - browser.click(this.selectors.data.modal.groupBy.input); - browser.pause(1000); - browser.execute("return document.querySelectorAll('div.x-layer.x-combo-list[style*=\"visibility: visible\"] .x-combo-list-item:nth-child(10)')[0];").click(); - } - axisSwap() { - var axisFirstChildText = ''; - var axisSecondChildText = ''; - axisFirstChildText = browser.getText(this.selectors.chart.axis + ' text'); - browser.waitAndClick(this.selectors.options.button); - browser.waitAndClick(this.selectors.options.swap); - // browser.pause(1000); - axisSecondChildText = browser.getText(this.selectors.chart.axis + ' text'); - // browser.waitForChart(); - browser.pause(1000); - browser.waitAndClick(this.selectors.options.button); - // browser.pause(1000); - expect(axisFirstChildText[1]).to.not.equal(axisSecondChildText[1]); - browser.pause(10000); - } - chartTitleInOptionsUpdated() { - browser.waitForChart(); - browser.waitAndClick(this.selectors.options.button); - browser.waitForVisible(this.selectors.options.title, 3000); - var res1 = browser.getValue(this.selectors.options.title); - expect(res1).to.be.a('string'); - var res2 = browser.execute(function (text) { - // TODO: Fix this withOut having to use EXT if Possible - // eslint-disable-next-line no-undef - return Ext.util.Format.htmlDecode(text); - }, res1); - expect(res2.value).to.equal(this.newTitle); - browser.waitForChart(); - browser.waitAndClick(this.selectors.options.button); - } - attemptDeleteScratchpad() { - browser.waitAndClick(this.selectors.toolbar.buttonByName('Delete')); - browser.waitForVisible(this.selectors.deleteChart.dialogBox); - browser.waitAndClick(this.selectors.deleteChart.buttonByLabel('Yes')); - browser.waitForInvisible(this.selectors.deleteChart.dialogBox); - browser.waitForAllInvisible('.ext-el-mask'); - } - actionLoadChart(chartNumber) { - browser.pause(3000); - browser.waitAndClick(this.selectors.load.button(), 2000); - browser.waitAndClick(this.selectors.load.chartNum(chartNumber), 2000); - browser.waitForChart(); - } - addDataViaToolbar() { - browser.waitAndClick(this.selectors.addData.button); - browser.waitAndClick(this.selectors.toolbar.addData('Jobs')); - browser.waitAndClick(this.selectors.toolbar.addDataGroupBy('CPU Hours: Per Job')); - this.addDataSeriesByDefinition(); - } - genericStartingPoint() { - browser.waitAndClick(this.selectors.addData.button); - // Click on Jobs (5 on original site) - browser.waitAndClick(this.selectors.buttonMenu.firstLevel + ' ul li:nth-child(3)'); - // click on CPU Hours: Total - browser.waitAndClick(this.selectors.addData.secondLevel + ' ul li:nth-child(3)', 1000); - this.addDataSeriesByDefinition(); - } - confirmChartTitleChange(largeTitle) { - browser.waitForChart(); - browser.pause(2000); - var titleChange = browser.execute('return document.querySelector("' + this.selectors.chart.title + '").textContent;'); - // expect(titleChange.state).to.equal('success'); - expect(titleChange._status).to.equal(0); - expect(titleChange.value).to.be.a('string'); - expect(titleChange.value).to.equal(largeTitle); - } - - switchToAggregate() { - browser.waitAndClick(this.selectors.options.button); - browser.waitAndClick(this.selectors.options.aggregate); - this.clickLogoAndWaitForMask(); - browser.waitForInvisible(this.selectors.options.aggregate); - browser.waitForAllInvisible('.ext-el-mask'); - } - - undoAggregateOrTrendLine($container) { - browser.waitAndClick(this.undo($container)); - // The mouse stays and causes a hover, lets move the mouse somewhere else - this.clickLogoAndWaitForMask(); - } - - clickFirstDataPoint() { - const elems = browser.elements(this.selectors.chart.seriesMarkers(0)); - elems.value[0].doubleClick(); - } - - /** - * Best effort to try to wait until the load mask has been and gone. - */ - clickSelectorAndWaitForMask(selector) { - browser.waitForVisible(selector); - browser.waitForAllInvisible('.ext-el-mask'); - - for (let i = 0; i < 100; i++) { - try { - browser.click(selector); - break; - } catch (e) { - browser.waitForAllInvisible('.ext-el-mask'); - } - } - } - - clickLogoAndWaitForMask() { - this.clickSelectorAndWaitForMask('.xtb-text.logo93'); - } -} - -module.exports = new MetricExplorer(); diff --git a/tests/ui/test/specs/xdmod/myProfile.js b/tests/ui/test/specs/xdmod/myProfile.js deleted file mode 100644 index 05831e1b03..0000000000 --- a/tests/ui/test/specs/xdmod/myProfile.js +++ /dev/null @@ -1,71 +0,0 @@ -let expected = global.testHelpers.artifacts.getArtifact('myProfile'); -let roles = require('./../../../../ci/testing.json').role; -let logIn = require('./loginPage.page.js'); -let myProfile = require('./myProfile.page.js'); -let selectors = myProfile.selectors; - -describe('My Profile Tests', function generalTests() { - let keys = Object.keys(roles); - for (let key in keys) { - if (keys.hasOwnProperty(key)) { - let role = keys[key]; - logIn.login(role); - - describe(`${role} Tests`, function perUserTests() { - it('Click the `My Profile` button', function clickMyProfile() { - browser.waitForAllInvisible('.ext-el-mask'); - browser.waitForVisible(myProfile.toolbarButton, 3000); - browser.waitForLoadedThenClick(myProfile.toolbarButton); - browser.waitForVisible(myProfile.container, 1000); - browser.waitForEnabled(selectors.general.user_information.first_name(), 3000); - }); - - describe('Check User Information', function checkUserInformation() { - it('First Name', function checkFirstName() { - // the normal user does not have a first name so the value returned from - // the first name field is the default empty text ( 1 min, 50 max ). - let givenname = role !== 'usr' ? roles[role].givenname : '1 min, 50 max'; - let firstNameControl = selectors.general.user_information.first_name(); - - browser.waitForVisible(firstNameControl); - expect(browser.getValue(firstNameControl)).to.equal(givenname); - }); - it('Last Name', function checkLastName() { - let surname = roles[role].surname; - let lastNameControl = selectors.general.user_information.last_name(); - - browser.waitForVisible(lastNameControl); - expect(browser.getValue(lastNameControl)).to.equal(surname); - }); - it('E-Mail Address', function checkEmailAddress() { - let username = roles[role].username; - // the admin user has a different email format than the rest of 'um. - let email = role !== 'mgr' ? `${username}@example.com` : `${username}@localhost`; - let emailControl = selectors.general.user_information.email_address(); - - browser.waitForVisible(emailControl); - expect(browser.getValue(emailControl)).to.equal(email); - }); - it('Top Role', function checkTopRole() { - // We need to account for the different displays for users - // with center related acls and the others. - let expectedValue = role === 'cd' || role === 'cs' ? `${expected.top_roles[role]} - ${expected.organization.name}` : expected.top_roles[role]; - let topRoleControl = selectors.general.user_information.top_role(); - - browser.waitForVisible(topRoleControl); - expect(browser.getText(topRoleControl)).to.equal(expectedValue); - }); - it('Click the `Close` button', function closeMyProfile() { - let closeButton = myProfile.button(selectors.buttons.close); - - browser.waitForVisible(closeButton); - browser.waitForLoadedThenClick(closeButton); - browser.waitForInvisible(myProfile.container, 20000); - }); - }); - }); - - logIn.logout(); - } - } -}); diff --git a/tests/ui/test/specs/xdmod/myProfile.page.js b/tests/ui/test/specs/xdmod/myProfile.page.js deleted file mode 100644 index d823b7be10..0000000000 --- a/tests/ui/test/specs/xdmod/myProfile.page.js +++ /dev/null @@ -1,87 +0,0 @@ -class MyProfile { - - constructor() { - let self = this; - this.toolbarButton = '#global-toolbar-profile'; - this.container = '//div[@id="xdmod-profile-editor"]'; - - this.general = `${this.container}//div[@id="xdmod-profile-general-settings"]`; - this.userInformation = `${this.general}//div[contains(@class, "user_profile_section_general")]`; - - this.selectors = { - tabs: { - general: 'General', - role_delegation: 'Role Delegation' - }, - general: { - user_information: { - top_role: function () { - return self.generalUserInformation('user_profile_most_privileged_role'); - }, - first_name: function () { - return self.generalUserInformation('first_name'); - }, - last_name: function () { - return self.generalUserInformation('last_name'); - }, - email_address: function () { - return self.generalUserInformation('email_address'); - } - }, - update_password: { - update: 'user_profile_option_password_update', - password: 'new_password', - password_again: 'password_again' - } - }, - role_delegation: { - staff_member: 'staff_member' - }, - buttons: { - update: 'user_profile_btn_update', - close: 'general_btn_close' - } - }; - } - - /** - * Retrieve an XPath for a tab that contains the parameter text within the - * My Profile window. Values can be found within `this.names.tabs`. - * - * @param text {string} the text found within the tab to be returned. - * @returns {string} - */ - tab(text) { - return `${this.container}//span[contains(@class, "x-tab-strip-text") and contains(text(),"${text}")]`; - } - - /** - * Retrieve an XPath for a control, identified by the name parameter, within - * the 'User Information' section of the 'General' tab. `names` are provided - * by `this.names.general.user_information`. - * - * @param name {string} - * @returns {string} - */ - generalUserInformation(name) { - switch (name) { - case 'user_profile_most_privileged_role': - return `${this.userInformation}//div[@id="${name}"]`; - default: - return `${this.userInformation}//input[@name="${name}"]`; - } - } - - /** - * Retrieve an XPath for a button, identified by the name parameter, within - * the 'My Profile' window. Values for name provided by `this.names.buttons` - * - * @param name {string} - * @returns {string} - */ - button(name) { - return `${this.general}//button[contains(@class, "${name}")]`; - } -} - -module.exports = new MyProfile(); diff --git a/tests/ui/test/specs/xdmod/reportGenerator.js b/tests/ui/test/specs/xdmod/reportGenerator.js deleted file mode 100644 index 094eec1701..0000000000 --- a/tests/ui/test/specs/xdmod/reportGenerator.js +++ /dev/null @@ -1,988 +0,0 @@ -const loginPage = require('./loginPage.page.js'); -const usagePage = require('./usageTab.page.js'); -const reportGeneratorPage = require('./reportGenerator.page.js'); -const expected = global.testHelpers.artifacts.getArtifact('reportGenerator'); -var XDMOD_REALMS = process.env.XDMOD_REALMS; -describe('Report Generator', function () { - // These dates correspond to the dates of the test job data. - const startDate = '2016-12-20'; - const endDate = '2017-01-01'; - - // Current date used in calculations below. - const currentDate = new Date(); - - // Calculate start and end dates of previous month. - const previousMonth = new Date(); - previousMonth.setDate(1); - previousMonth.setMonth(previousMonth.getMonth() - 1); - const previousMonthStartDate = previousMonth.toISOString().substr(0, 10); - - // Setting the date to day "0" of a month will result in the last - // day of the previous month. - previousMonth.setMonth(previousMonth.getMonth() + 1); - previousMonth.setDate(0); - const previousMonthEndDate = previousMonth.toISOString().substr(0, 10); - - // Calculate start and end dates of previous quarter. - const previousQuarter = new Date(); - previousQuarter.setDate(1); - const currentMonth = previousQuarter.getMonth(); - const monthModThree = currentMonth % 3; - previousQuarter.setMonth(currentMonth - 3 - monthModThree); - const previousQuarterStartDate = previousQuarter.toISOString().substr(0, 10); - previousQuarter.setMonth(previousQuarter.getMonth() + 3); - previousQuarter.setDate(0); - const previousQuarterEndDate = previousQuarter.toISOString().substr(0, 10); - - // Calculate start and end dates of previous year. - const previousYearStartDate = (currentDate.getFullYear() - 1) + '-01-01'; - const previousYearEndDate = (currentDate.getFullYear() - 1) + '-12-31'; - - // Calculate year-to-date start and end dates. - const yearToDateStartDate = currentDate.getFullYear() + '-01-01'; - const yearToDateEndDate = currentDate.toISOString().substr(0, 10); - - // Descriptive text displayed in empty fields of a newly created - // report. - const reportEmptyText = { - title: 'Optional, 50 max', - header: 'Optional, 40 max', - footer: 'Optional, 40 max' - }; - - // Default values expected when a new report is created. - const defaultReport = { - name: 'TAS Report 1', - chartsPerPage: 1, - schedule: 'Once', - deliveryFormat: 'PDF', - derivedFrom: 'Manual', - charts: [] - }; - - // These charts correspond to those that will be added to the - // "Available Charts" list from the usage tab. - const usageTabCharts = [ - { - realm: 'Jobs', - startDate: startDate, - endDate: endDate, - title: 'CPU Hours: Total', - drillDetails: '', - timeframeType: 'User Defined' - }, - { - realm: 'Jobs', - startDate: startDate, - endDate: endDate, - title: 'CPU Hours: Per Job', - drillDetails: '', - timeframeType: 'User Defined' - }, - { - realm: 'Jobs', - startDate: previousMonthStartDate, - endDate: previousMonthEndDate, - title: 'CPU Hours: Total', - drillDetails: '', - timeframeType: 'Previous month' - }, - { - realm: 'Jobs', - startDate: previousYearStartDate, - endDate: previousYearEndDate, - title: 'CPU Hours: Total', - drillDetails: '', - timeframeType: 'Previous year' - } - ]; - - const centerDirectorReportTemplates = [ - { - name: expected.centerdirector.report_templates[0].name, - chartsPerPage: 1, - schedule: 'Quarterly', - deliveryFormat: 'PDF', - charts: [ - { - realm: 'Jobs', - startDate: previousQuarterStartDate, - endDate: previousQuarterEndDate, - title: 'PREVIOUS QUARTER: Total CPU Hours and Jobs', - drillDetails: '', - timeframeType: 'Previous quarter' - }, - { - realm: 'Jobs', - startDate: yearToDateStartDate, - endDate: yearToDateEndDate, - title: 'YEAR TO DATE: Total CPU Hours and Jobs', - drillDetails: '', - timeframeType: 'Year to date' - }, - { - realm: 'Jobs', - startDate: previousQuarterStartDate, - endDate: previousQuarterEndDate, - title: 'PREVIOUS QUARTER: CPU Percent Utilization', - drillDetails: '', - timeframeType: 'Previous quarter' - }, - { - realm: 'Jobs', - startDate: yearToDateStartDate, - endDate: yearToDateEndDate, - title: 'YEAR TO DATE: Percent CPU Utilization', - drillDetails: '', - timeframeType: 'Year to date' - }, - { - realm: 'Jobs', - startDate: previousQuarterStartDate, - endDate: previousQuarterEndDate, - title: 'PREVIOUS QUARTER: CPU Hours and Number of Jobs - Top 20 Users', - drillDetails: '', - timeframeType: 'Previous quarter' - }, - { - realm: 'Jobs', - startDate: yearToDateStartDate, - endDate: yearToDateEndDate, - title: 'YEAR TO DATE: CPU Hours and Number of Jobs - Top 20 Users', - drillDetails: '', - timeframeType: 'Year to date' - }, - { - realm: 'Jobs', - startDate: previousQuarterStartDate, - endDate: previousQuarterEndDate, - title: 'PREVIOUS QUARTER: CPU Hours and Number of Jobs', - drillDetails: 'by Resource', - timeframeType: 'Previous quarter' - }, - { - realm: 'Jobs', - startDate: yearToDateStartDate, - endDate: yearToDateEndDate, - title: 'YEAR TO DATE: CPU Hours and Number of Jobs', - drillDetails: 'by Resource', - timeframeType: 'Year to date' - }, - { - realm: 'Jobs', - startDate: previousQuarterStartDate, - endDate: previousQuarterEndDate, - title: 'PREVIOUS QUARTER: CPU Hours, Number of Jobs, and Wait Time per Job', - drillDetails: 'by Job Size', - timeframeType: 'Previous quarter' - }, - { - realm: 'Jobs', - startDate: yearToDateStartDate, - endDate: yearToDateEndDate, - title: 'YEAR TO DATE: CPU Hours, Number of Jobs, and Wait Time per Job', - drillDetails: 'by Job Size', - timeframeType: 'Year to date' - }, - { - realm: 'Jobs', - startDate: previousQuarterStartDate, - endDate: previousQuarterEndDate, - title: 'PREVIOUS QUARTER: CPU Hours and User Expansion Factor', - drillDetails: 'by Job Size', - timeframeType: 'Previous quarter' - }, - { - realm: 'Jobs', - startDate: yearToDateStartDate, - endDate: yearToDateEndDate, - title: 'YEAR TO DATE: CPU Hours and User Expansion Factor', - drillDetails: 'by Job Size', - timeframeType: 'Year to date' - }, - { - realm: 'Jobs', - startDate: previousQuarterStartDate, - endDate: previousQuarterEndDate, - title: 'PREVIOUS QUARTER: Wait Hours per Job', - drillDetails: 'by Queue', - timeframeType: 'Previous quarter' - }, - { - realm: 'Jobs', - startDate: yearToDateStartDate, - endDate: yearToDateEndDate, - title: 'YEAR TO DATE: Wait Hours per Job', - drillDetails: 'by Queue', - timeframeType: 'Year to date' - }, - { - realm: 'Jobs', - startDate: previousQuarterStartDate, - endDate: previousQuarterEndDate, - title: 'PREVIOUS QUARTER: CPU Hours and Number of Jobs', - drillDetails: 'by Queue', - timeframeType: 'Previous quarter' - }, - { - realm: 'Jobs', - startDate: yearToDateStartDate, - endDate: yearToDateEndDate, - title: 'YEAR TO DATE: CPU Hours and Number of Jobs', - drillDetails: 'by Queue', - timeframeType: 'Year to date' - } - ] - } - ]; - - // Public user - describe('Public user default report generator state', function () { - it('Report Generator is not enabled', function () { - expect(reportGeneratorPage.isEnabled()).to.be.false; - }); - }); - - // User - loginPage.login('user'); - - describe('Normal user default report generator state', function () { - it('Report Generator is enabled', function () { - expect(reportGeneratorPage.isEnabled()).to.be.true; - }); - it('Select Report Generator tab', function () { - reportGeneratorPage.selectTab(); - }); - it('No reports listed', function () { - expect(reportGeneratorPage.getMyReportsRows().length, 'No rows in the list of reports').to.be.equal(0); - }); - it('No available charts listed', function () { - expect(reportGeneratorPage.getAvailableCharts().length, 'No charts in the list of available charts').to.be.equal(0); - }); - it('No report templates available', function () { - expect(reportGeneratorPage.isNewBasedOnEnabled()).to.be.false; - }); - }); - - loginPage.logout(); - - // TODO: Add tests for storage and cloud realms - if (XDMOD_REALMS.includes('jobs')) { - // PI - loginPage.login('principalinvestigator'); - - describe('Principal investigator default report generator state', function () { - it('Report Generator is enabled', function () { - expect(reportGeneratorPage.isEnabled()).to.be.true; - }); - it('Select Report Generator tab', function () { - reportGeneratorPage.selectTab(); - }); - it('No reports listed', function () { - expect(reportGeneratorPage.getMyReportsRows().length, 'No rows in the list of reports').to.be.equal(0); - }); - it('No available charts listed', function () { - expect(reportGeneratorPage.getAvailableCharts().length, 'No charts in the list of available charts').to.be.equal(0); - }); - it('No report templates available', function () { - expect(reportGeneratorPage.isNewBasedOnEnabled()).to.be.false; - }); - }); - - loginPage.logout(); - } - - // Center staff - loginPage.login('centerstaff'); - - describe('Center staff default report generator state', function () { - it('Report Generator is enabled', function () { - expect(reportGeneratorPage.isEnabled()).to.be.true; - }); - it('Select Report Generator tab', function () { - reportGeneratorPage.selectTab(); - }); - it('Reports listed', function () { - expect(reportGeneratorPage.getMyReportsRows().length, 'Rows in the list of reports').to.be.equal(0); - }); - it('No available charts listed', function () { - expect(reportGeneratorPage.getAvailableCharts().length, 'No charts in the list of available charts').to.be.equal(0); - }); - it('No report templates available', function () { - expect(reportGeneratorPage.isNewBasedOnEnabled()).to.equal(expected.centerstaff.report_templates_available); - }); - }); - - loginPage.logout(); - - // TODO: Add tests for storage and cloud realms - if (XDMOD_REALMS.includes('jobs')) { - // Center director - loginPage.login('centerdirector'); - - describe('Center director default report generator state', function () { - it('Report Generator is enabled', function () { - expect(reportGeneratorPage.isEnabled()).to.be.true; - }); - it('Select Report Generator tab', function () { - reportGeneratorPage.selectTab(); - }); - it('No reports listed', function () { - expect(reportGeneratorPage.getMyReportsRows().length, 'No rows in the list of reports').to.be.equal(0); - }); - it('No available charts listed', function () { - expect(reportGeneratorPage.getAvailableCharts().length, 'No charts in the list of available charts').to.be.equal(0); - }); - it('Buttons are disabled', function () { - expect(reportGeneratorPage.isEditSelectedReportsEnabled(), '"Edit" button is disabled').to.be.false; - expect(reportGeneratorPage.isPreviewSelectedReportsEnabled(), '"Preview" button is disabled').to.be.false; - expect(reportGeneratorPage.isSendSelectedReportsEnabled(), '"Send" button is disabled').to.be.false; - expect(reportGeneratorPage.isDownloadSelectedReportsEnabled(), '"Download" button is disabled').to.be.false; - expect(reportGeneratorPage.isDeleteSelectedReportsEnabled(), '"Delete" button is disabled').to.be.false; - }); - }); - describe('Make usage tab charts available in the Report Generator', function () { - usageTabCharts.forEach((testChart, index) => { - it('Select Usage Tab', function () { - usagePage.selectTab(); - }); - it(`Select "${testChart.title}" chart`, function () { - const topNodeName = testChart.realm + ' ' + (testChart.drillDetails === '' ? 'Summary' : testChart.drillDetails); - usagePage.selectChildTreeNode(topNodeName, testChart.title); - }); - it('Set chart timeframe', function () { - if (testChart.timeframeType === 'User Defined') { - usagePage.setStartDate(testChart.startDate); - usagePage.setEndDate(testChart.endDate); - usagePage.refresh(); - } else { - usagePage.selectDuration(testChart.timeframeType); - } - }); - it(`Make "${testChart.title}" chart available in the Report Generator`, function () { - usagePage.makeCurrentChartAvailableForReport(); - }); - it('Check available charts', function () { - reportGeneratorPage.selectTab(); - var charts; - for (let i = 0; i < 100; i++) { - charts = reportGeneratorPage.getAvailableCharts(); - if (charts.length === (index + 1)) { - break; - } - browser.pause(20); - } - expect(charts.length, `${index + 1} chart(s) in the list of available charts`).to.be.equal(index + 1); - - for (let i = 0; i <= index; ++i) { - const chart = charts[i]; - expect(chart.getTitle(), 'Chart title is correct').to.be.equal(usageTabCharts[i].title); - expect(chart.getDrillDetails(), 'Drill details are correct').to.be.equal(usageTabCharts[i].drillDetails); - expect(chart.getDateDescription(), 'Date description is correct').to.be.equal(`${usageTabCharts[i].startDate} to ${usageTabCharts[i].endDate}`); - expect(chart.getTimeframeType(), 'Timeframe type is correct').to.be.equal(usageTabCharts[i].timeframeType); - } - }); - }); - }); - - describe('Create report with default options', function () { - // Copy default report data for the report being tested. - const testReport = Object.assign({}, defaultReport); - - it('Create a new report', function () { - reportGeneratorPage.createNewReport(); - }); - it('Check default values', function () { - expect(reportGeneratorPage.getReportName(), 'Default report name').to.be.equal(defaultReport.name); - expect(reportGeneratorPage.getNumberOfChartsPerPage(), 'Default report number of charts per page').to.be.equal(defaultReport.chartsPerPage); - expect(reportGeneratorPage.getSchedule(), 'Default report schedule').to.be.equal(defaultReport.schedule); - expect(reportGeneratorPage.getDeliveryFormat(), 'Default report delivery format').to.be.equal(defaultReport.deliveryFormat); - expect(reportGeneratorPage.getIncludedCharts().length, 'Default report chart count').to.be.equal(defaultReport.charts.length); - }); - it('Empty text fields', function () { - expect(reportGeneratorPage.getReportTitle(), 'Empty report title').to.be.equal(reportEmptyText.title); - expect(reportGeneratorPage.getHeaderText(), 'Empty report header').to.be.equal(reportEmptyText.header); - expect(reportGeneratorPage.getFooterText(), 'Empty report footer').to.be.equal(reportEmptyText.footer); - }); - it('Add chart to report', function () { - reportGeneratorPage.addChartToReport(0); - - // Add chart to test report data to reflect the change made - // to the report. - testReport.charts.push(usageTabCharts[0]); - }); - it('Save report', function () { - reportGeneratorPage.saveReport(); - }); - it('Return to "My Reports"', function () { - reportGeneratorPage.returnToMyReports(); - }); - it('Check report list', function () { - const reportRows = reportGeneratorPage.getMyReportsRows(); - expect(reportRows.length, '1 report in list').to.be.equal(1); - const reportRow = reportRows[0]; - expect(reportRow.getName(), 'Name is correct').to.be.equal(testReport.name); - expect(reportRow.getDerivedFrom(), '"Derived From" is correct').to.be.equal(testReport.derivedFrom); - expect(reportRow.getSchedule(), 'Schedule is correct').to.be.equal(testReport.schedule); - expect(reportRow.getDeliveryFormat(), 'Delivery format is correct').to.be.equal(testReport.deliveryFormat); - expect(reportRow.getNumberOfCharts(), 'Number of charts of is correct').to.be.equal(testReport.charts.length); - expect(reportRow.getNumberOfChartsPerPage(), 'Number of charts per page is correct').to.be.equal(testReport.chartsPerPage); - }); - }); - - describe('Create report and change options', function () { - const testReport = { - name: 'Test Report 1', - title: 'Test Report', - header: 'Test header', - footer: 'Test footer', - chartsPerPage: 2, - schedule: 'Monthly', - deliveryFormat: 'Word Document', - derivedFrom: 'Manual', - charts: [] - }; - - it('Create a new report', function () { - reportGeneratorPage.createNewReport(); - }); - it('Set report name', function () { - reportGeneratorPage.setReportName(testReport.name); - }); - it('Set report title', function () { - reportGeneratorPage.setReportTitle(testReport.title); - }); - it('Set header text', function () { - reportGeneratorPage.setHeaderText(testReport.header); - }); - it('Set footer text', function () { - reportGeneratorPage.setFooterText(testReport.footer); - }); - it('Set number of charts per page', function () { - reportGeneratorPage.setNumberOfChartsPerPage(testReport.chartsPerPage); - }); - it('Set schedule', function () { - reportGeneratorPage.setSchedule(testReport.schedule); - }); - it('Set delivery format', function () { - reportGeneratorPage.setDeliveryFormat(testReport.deliveryFormat); - }); - it('Add chart to report', function () { - reportGeneratorPage.addChartToReport(0); - - // Add chart to test report data to reflect the change made - // to the report. - testReport.charts.push(usageTabCharts[0]); - }); - it('Edit the timeframe of the chart', function () { - reportGeneratorPage.getIncludedCharts()[0].editTimeframe(); - }); - it('Select "Specific" for the timeframe type', function () { - reportGeneratorPage.selectSpecificChartTimeframe(); - }); - it('Clear start and end date', function () { - reportGeneratorPage.setSpecificChartTimeframeStartDate(''); - reportGeneratorPage.setSpecificChartTimeframeEndDate(''); - }); - it('Click the update button, expect error about start date', function () { - reportGeneratorPage.confirmEditTimeframeOfSelectedCharts(); - expect(reportGeneratorPage.getEditChartTimeframeErrorMessage(), 'Start date error').to.be.equal('Valid start date required'); - }); - it('Set a start date', function () { - reportGeneratorPage.setSpecificChartTimeframeStartDate(startDate); - }); - it('Click the update button, expect error about end date', function () { - reportGeneratorPage.confirmEditTimeframeOfSelectedCharts(); - expect(reportGeneratorPage.getEditChartTimeframeErrorMessage(), 'End date error').to.be.equal('Valid end date required'); - }); - it('Set an end date', function () { - reportGeneratorPage.setSpecificChartTimeframeEndDate(endDate); - }); - it('Click the update button', function () { - reportGeneratorPage.confirmEditTimeframeOfSelectedCharts(); - }); - it('Verify that the date has been changed', function () { - const chart = reportGeneratorPage.getIncludedCharts()[0]; - expect(chart.getTimeframeType(), 'Timeframe type').to.be.equal('User Defined'); - expect(chart.getDateDescription(), 'Date description').to.be.equal(startDate + ' to ' + endDate); - }); - it('Save report', function () { - reportGeneratorPage.saveReport(); - }); - it('Return to "My Reports"', function () { - reportGeneratorPage.returnToMyReports(); - }); - it('Check report list', function () { - const reportRows = reportGeneratorPage.getMyReportsRows(); - expect(reportRows.length, '2 reports in list').to.be.equal(2); - const reportRow = reportRows[1]; - expect(reportRow.getName(), 'Name is correct').to.be.equal(testReport.name); - expect(reportRow.getDerivedFrom(), '"Derived From" is correct').to.be.equal(testReport.derivedFrom); - expect(reportRow.getSchedule(), 'Schedule is correct').to.be.equal(testReport.schedule); - expect(reportRow.getDeliveryFormat(), 'Delivery format is correct').to.be.equal(testReport.deliveryFormat); - expect(reportRow.getNumberOfCharts(), 'Number of charts of is correct').to.be.equal(testReport.charts.length); - expect(reportRow.getNumberOfChartsPerPage(), 'Number of charts per page is correct').to.be.equal(testReport.chartsPerPage); - }); - it('Edit report and compare values', function () { - reportGeneratorPage.getMyReportsRows()[1].doubleClick(); - expect(reportGeneratorPage.getReportName(), 'Report name is correct').to.be.equal(testReport.name); - expect(reportGeneratorPage.getReportTitle(), 'Report title is correct').to.be.equal(testReport.title); - expect(reportGeneratorPage.getHeaderText(), 'Header text is correct').to.be.equal(testReport.header); - expect(reportGeneratorPage.getFooterText(), 'Footer text is correct').to.be.equal(testReport.footer); - expect(reportGeneratorPage.getNumberOfChartsPerPage(), 'Number of charts per page is correct').to.be.equal(testReport.chartsPerPage); - expect(reportGeneratorPage.getSchedule(), 'Schedule is correct').to.be.equal(testReport.schedule); - expect(reportGeneratorPage.getDeliveryFormat(), 'Delivery format is correct').to.be.equal(testReport.deliveryFormat); - reportGeneratorPage.returnToMyReports(); - }); - }); - - describe('Edit report and "Save As"', function () { - const testReport = { - name: 'Copied Report' - }; - - it('Store data for report that will be copied', function () { - const reportRow = reportGeneratorPage.getMyReportsRows()[1]; - testReport.derivedFrom = reportRow.getDerivedFrom(); - testReport.schedule = reportRow.getSchedule(); - testReport.deliveryFormat = reportRow.getDeliveryFormat(); - testReport.numberOfCharts = reportRow.getNumberOfCharts(); - testReport.chartsPerPage = reportRow.getNumberOfChartsPerPage(); - }); - it('Edit report', function () { - reportGeneratorPage.getMyReportsRows()[1].doubleClick(); - }); - it('Click "Save As" and set report name', function () { - reportGeneratorPage.saveReportAs(testReport.name); - }); - it('Click "Save" in "Save As" window', function () { - reportGeneratorPage.confirmSaveReportAs(); - }); - it('Check copied report name', function () { - expect(reportGeneratorPage.getReportName(), 'Report name is correct').to.be.equal(testReport.name); - }); - it('Edit copied report', function () { - testReport.header = 'Header for copied report'; - reportGeneratorPage.setHeaderText(testReport.header); - testReport.chartsPerPage = testReport.chartsPerPage === 1 ? 2 : 1; - reportGeneratorPage.setNumberOfChartsPerPage(testReport.chartsPerPage); - }); - it('Save report', function () { - reportGeneratorPage.saveReport(); - }); - it('Return to "My Reports"', function () { - reportGeneratorPage.returnToMyReports(); - }); - it('Check report list', function () { - const reportRows = reportGeneratorPage.getMyReportsRows(); - expect(reportRows.length, '3 reports in list').to.be.equal(3); - const reportRow = reportRows[2]; - expect(reportRow.getName(), 'Name is correct').to.be.equal(testReport.name); - expect(reportRow.getDerivedFrom(), '"Derived From" is correct').to.be.equal(testReport.derivedFrom); - expect(reportRow.getSchedule(), 'Schedule is correct').to.be.equal(testReport.schedule); - expect(reportRow.getDeliveryFormat(), 'Delivery format is correct').to.be.equal(testReport.deliveryFormat); - expect(reportRow.getNumberOfCharts(), 'Number of charts of is correct').to.be.equal(testReport.numberOfCharts); - expect(reportRow.getNumberOfChartsPerPage(), 'Number of charts per page is correct').to.be.equal(testReport.chartsPerPage); - }); - it('Edit copied report and compare values', function () { - reportGeneratorPage.getMyReportsRows()[2].doubleClick(); - expect(reportGeneratorPage.getHeaderText(), 'Header text is correct').to.be.equal(testReport.header); - expect(reportGeneratorPage.getNumberOfChartsPerPage(), 'Number of charts per page is correct').to.be.equal(testReport.chartsPerPage); - }); - it('Return to "My Reports"', function () { - reportGeneratorPage.returnToMyReports(); - }); - }); - - describe('Edit report and make changes', function () { - // The row index of the report that will be edited. - const reportIndex = 2; - - // The data that will be changed in the report. - const testReport = { - name: 'Edited Test Report 1', - title: 'Edited Test Report', - header: 'Edited header', - footer: 'Edited footer', - chartsPerPage: 1, - schedule: 'Quarterly', - deliveryFormat: 'PDF', - derivedFrom: 'Manual', - // The report being edited contains one chart already, but this - // test does not check it's contents. - charts: [{}] - }; - - it('Open the report', function () { - reportGeneratorPage.getMyReportsRows()[reportIndex].doubleClick(); - }); - it('Set report name', function () { - reportGeneratorPage.setReportName(testReport.name); - }); - it('Set report title', function () { - reportGeneratorPage.setReportTitle(testReport.title); - }); - it('Set header text', function () { - reportGeneratorPage.setHeaderText(testReport.header); - }); - it('Set footer text', function () { - reportGeneratorPage.setFooterText(testReport.footer); - }); - it('Set number of charts per page', function () { - reportGeneratorPage.setNumberOfChartsPerPage(testReport.chartsPerPage); - }); - it('Set schedule', function () { - reportGeneratorPage.setSchedule(testReport.schedule); - }); - it('Set delivery format', function () { - reportGeneratorPage.setDeliveryFormat(testReport.deliveryFormat); - }); - it('Add chart to report', function () { - reportGeneratorPage.addChartToReport(1); - - // Add chart to test report data to reflect the change made - // to the report. - testReport.charts.push(usageTabCharts[1]); - }); - it('Save report', function () { - reportGeneratorPage.saveReport(); - }); - it('Return to "My Reports"', function () { - reportGeneratorPage.returnToMyReports(); - }); - it('Check report list', function () { - const reportRows = reportGeneratorPage.getMyReportsRows(); - expect(reportRows.length, '3 reports in list').to.be.equal(3); - const reportRow = reportRows[reportIndex]; - expect(reportRow.getName(), 'Name is correct').to.be.equal(testReport.name); - expect(reportRow.getDerivedFrom(), '"Derived From" is correct').to.be.equal(testReport.derivedFrom); - expect(reportRow.getSchedule(), 'Schedule is correct').to.be.equal(testReport.schedule); - expect(reportRow.getDeliveryFormat(), 'Delivery format is correct').to.be.equal(testReport.deliveryFormat); - expect(reportRow.getNumberOfCharts(), 'Number of charts of is correct').to.be.equal(testReport.charts.length); - expect(reportRow.getNumberOfChartsPerPage(), 'Number of charts per page is correct').to.be.equal(testReport.chartsPerPage); - }); - it('Edit report and compare values', function () { - reportGeneratorPage.getMyReportsRows()[reportIndex].doubleClick(); - expect(reportGeneratorPage.getReportName(), 'Report name is correct').to.be.equal(testReport.name); - expect(reportGeneratorPage.getReportTitle(), 'Report title is correct').to.be.equal(testReport.title); - expect(reportGeneratorPage.getHeaderText(), 'Header text is correct').to.be.equal(testReport.header); - expect(reportGeneratorPage.getFooterText(), 'Footer text is correct').to.be.equal(testReport.footer); - expect(reportGeneratorPage.getNumberOfChartsPerPage(), 'Number of charts per page is correct').to.be.equal(testReport.chartsPerPage); - expect(reportGeneratorPage.getSchedule(), 'Schedule is correct').to.be.equal(testReport.schedule); - expect(reportGeneratorPage.getDeliveryFormat(), 'Delivery format is correct').to.be.equal(testReport.deliveryFormat); - reportGeneratorPage.returnToMyReports(); - }); - }); - - describe('Create report from template', function () { - it('Click "New Based On"', function () { - reportGeneratorPage.clickNewBasedOn(); - }); - it('Check list of report templates', function () { - reportGeneratorPage.getReportTemplateNames().forEach((reportTemplateName, i) => { - expect(reportTemplateName, 'Report template ' + i).to.be.equal(centerDirectorReportTemplates[i].name); - }); - }); - it('Click "New Based On" to close menu', function () { - // Close the menu so that it can be re-opened in the loop below. - reportGeneratorPage.clickNewBasedOn(); - }); - centerDirectorReportTemplates.forEach((template, report_template_index) => { - let reportIndex; - - it('Click "New Based On"', function () { - reportIndex = reportGeneratorPage.getMyReportsRows().length; - reportGeneratorPage.clickNewBasedOn(); - }); - it(`Select "${template.name}"`, function () { - reportGeneratorPage.selectNewBasedOnTemplate(template.name, expected.centerdirector.center); - }); - it('Check list of reports', function () { - const reportRows = reportGeneratorPage.getMyReportsRows(); - expect(reportRows.length, 'New report added').to.be.equal(reportIndex + expected.centerdirector.report_templates[report_template_index].reports_created); - const reportRow = reportRows[reportIndex]; - expect(reportRow.getName(), 'Name is correct').to.be.equal(expected.centerdirector.report_templates[report_template_index].created_name + ' 1'); - expect(reportRow.getDerivedFrom(), '"Derived From" is correct').to.be.equal(template.name); - expect(reportRow.getSchedule(), 'Schedule is correct').to.be.equal(template.schedule); - expect(reportRow.getDeliveryFormat(), 'Delivery format is correct').to.be.equal(expected.centerdirector.report_templates[report_template_index].delivery_format); - expect(reportRow.getNumberOfCharts(), 'Number of charts of is correct').to.be.equal(expected.centerdirector.report_templates[report_template_index].created_reports_count); - expect(reportRow.getNumberOfChartsPerPage(), 'Number of charts per page is correct').to.be.equal(template.chartsPerPage); - }); - it('Edit report based on template', function () { - reportGeneratorPage.getMyReportsRows()[reportIndex].doubleClick(); - }); - it('Check charts', function () { - const templateCharts = reportGeneratorPage.getCharts( - 'centerdirector', - report_template_index, - { - startDate: startDate, - endDate: endDate, - previousMonthStartDate: previousMonthStartDate, - previousMonthEndDate: previousMonthEndDate, - previousQuarterStartDate: previousQuarterStartDate, - previousQuarterEndDate: previousQuarterEndDate, - previousYearStartDate: previousYearStartDate, - previousYearEndDate: previousYearEndDate, - yearToDateStartDate: yearToDateStartDate, - yearToDateEndDate: yearToDateEndDate - } - ); - reportGeneratorPage.getIncludedCharts().forEach((chart, i) => { - const templateChart = templateCharts[i]; - expect(chart.getTitle(), 'Chart title').to.be.equal(templateChart.title); - expect(chart.getDrillDetails(), 'Drill details').to.be.equal(templateChart.drillDetails); - expect(chart.getTimeframeType(), 'Timeframe type').to.be.equal(templateChart.timeframeType); - expect(chart.getDateDescription(), 'Date description').to.be.equal(templateChart.startDate + ' to ' + templateChart.endDate); - }); - }); - it('Return to "My Reports"', function () { - reportGeneratorPage.returnToMyReports(); - }); - }); - }); - - describe('Preview report', function () { - it('Select a report', function () { - reportGeneratorPage.getMyReportsRows()[0].click(); - }); - it('Preview selected report', function () { - reportGeneratorPage.previewSelectedReports(); - }); - it('Return to reports overview', function () { - reportGeneratorPage.returnToReportsOverview(); - }); - it('Deselect reports', function () { - reportGeneratorPage.deselectAllReports(); - }); - }); - - describe('Download report', function () { - it('Select a report', function () { - reportGeneratorPage.getMyReportsRows()[0].click(); - }); - it('Click "Download" button', function () { - reportGeneratorPage.downloadSelectedReports(); - }); - it('Click "As PDF"', function () { - reportGeneratorPage.downloadSelectedReportsAsPdf(); - }); - it('Close "Report Built" window', function () { - reportGeneratorPage.closeReportBuiltWindow(); - }); - it('Click "Download" button', function () { - reportGeneratorPage.downloadSelectedReports(); - }); - it('Click "As Word Document"', function () { - reportGeneratorPage.downloadSelectedReportsAsWordDocument(); - }); - it('Close "Report Built" window', function () { - reportGeneratorPage.closeReportBuiltWindow(); - }); - it('Deselect reports', function () { - reportGeneratorPage.deselectAllReports(); - }); - }); - - describe('Select reports', function () { - it('Select all', function () { - reportGeneratorPage.selectAllReports(); - reportGeneratorPage.getMyReportsRows().forEach((row, i) => { - expect(row.isSelected(), `Row ${i} is selected`).to.be.true; - }); - }); - it('Select none', function () { - reportGeneratorPage.deselectAllReports(); - reportGeneratorPage.getMyReportsRows().forEach((row, i) => { - expect(row.isSelected(), `Row ${i} is not selected`).to.be.false; - }); - }); - it('Invert selection', function () { - // Select one row then invert selection. - reportGeneratorPage.getMyReportsRows()[1].toggleSelection(); - const selectedStatus = reportGeneratorPage.getMyReportsRows().map(row => row.isSelected()); - reportGeneratorPage.invertReportSelection(); - reportGeneratorPage.getMyReportsRows().forEach((row, i) => { - expect(row.isSelected(), `Row ${i} has been inverted`).to.be.equal(!selectedStatus[i]); - }); - }); - }); - - describe('Attempt to edit multiple reports from "My Reports"', function () { - it('Select reports', function () { - reportGeneratorPage.selectAllReports(); - }); - it('Edit reports (should not be possible)', function () { - expect(reportGeneratorPage.isEditSelectedReportsEnabled(), '"Edit" button is disabled').to.be.false; - }); - it('Deselect reports', function () { - reportGeneratorPage.deselectAllReports(); - }); - }); - - describe('Attempt to preview multiple reports from "My Reports"', function () { - it('Select reports', function () { - reportGeneratorPage.selectAllReports(); - }); - it('Preview reports (should not be possible)', function () { - expect(reportGeneratorPage.isPreviewSelectedReportsEnabled(), '"Preview" button is disabled').to.be.false; - }); - it('Deselect reports', function () { - reportGeneratorPage.deselectAllReports(); - }); - }); - - describe('Attempt to send multiple reports from "My Reports"', function () { - it('Select reports', function () { - reportGeneratorPage.selectAllReports(); - }); - it('Send reports (should not be possible)', function () { - expect(reportGeneratorPage.isSendSelectedReportsEnabled(), '"Send" button is disabled').to.be.false; - }); - it('Deselect reports', function () { - reportGeneratorPage.deselectAllReports(); - }); - }); - - describe('Attempt to download multiple reports from "My Reports"', function () { - it('Select reports', function () { - reportGeneratorPage.selectAllReports(); - }); - it('Download reports (should not be possible)', function () { - expect(reportGeneratorPage.isDownloadSelectedReportsEnabled(), '"Download" button is disabled').to.be.false; - }); - it('Deselect reports', function () { - reportGeneratorPage.deselectAllReports(); - }); - }); - - describe('Delete report from "My Reports"', function () { - let reportCount; - - it('Select report', function () { - const reports = reportGeneratorPage.getMyReportsRows(); - reportCount = reports.length; - reports[0].click(); - expect(reports[0].isSelected(), 'Report is selected').to.be.true; - }); - it('Click delete button', function () { - reportGeneratorPage.deleteSelectedReports(); - }); - it('Cancel deletion', function () { - reportGeneratorPage.cancelDeleteSelectedReports(); - const reports = reportGeneratorPage.getMyReportsRows(); - expect(reports.length, 'Report count has not changed').to.be.equal(reportCount); - expect(reports[0].isSelected(), 'Report is still selected').to.be.true; - }); - it('Click delete button', function () { - reportGeneratorPage.deleteSelectedReports(); - }); - it('Confirm deletion', function () { - reportGeneratorPage.confirmDeleteSelectedReports(); - --reportCount; - }); - it('Check list of reports', function () { - expect(reportGeneratorPage.getMyReportsRows().length).to.be.equal(reportCount); - }); - }); - - describe('Select charts listed in "Available Charts"', function () { - it('Select all', function () { - reportGeneratorPage.selectAllAvailableCharts(); - reportGeneratorPage.getAvailableCharts().forEach((chart, i) => { - expect(chart.isSelected(), `Chart ${i} is selected`).to.be.true; - }); - }); - it('Select none', function () { - reportGeneratorPage.deselectAllAvailableCharts(); - reportGeneratorPage.getAvailableCharts().forEach((chart, i) => { - expect(chart.isSelected(), `Chart ${i} is not selected`).to.be.false; - }); - }); - it('Invert selection', function () { - // Select one chart then invert selection. - reportGeneratorPage.getAvailableCharts()[1].toggleSelection(); - const selectedStatus = reportGeneratorPage.getAvailableCharts().map(chart => chart.isSelected()); - reportGeneratorPage.invertAvailableChartsSelection(); - reportGeneratorPage.getAvailableCharts().forEach((chart, i) => { - expect(chart.isSelected(), `Chart ${i} selection has been inverted`).to.be.equal(!selectedStatus[i]); - }); - }); - }); - - // Removes all but the first chart. - describe('Remove charts from "Available Charts"', function () { - let chartCount; - - it('Select all charts', function () { - chartCount = reportGeneratorPage.getAvailableCharts().length; - reportGeneratorPage.selectAllAvailableCharts(); - }); - it('Deselect first chart', function () { - reportGeneratorPage.getAvailableCharts()[0].toggleSelection(); - }); - it('Click delete button', function () { - reportGeneratorPage.deleteSelectedAvailableCharts(); - }); - it('Cancel deletion', function () { - reportGeneratorPage.cancelDeleteSelectedAvailableCharts(); - }); - it('Confirm that no charts were removed', function () { - expect(reportGeneratorPage.getAvailableCharts().length, 'Chart count not changed').to.be.equal(chartCount); - }); - it('Click delete button again', function () { - reportGeneratorPage.deleteSelectedAvailableCharts(); - }); - it('Confirm deletion', function () { - reportGeneratorPage.confirmDeleteSelectedAvailableCharts(); - }); - it('Confirm that charts were removed', function () { - expect(reportGeneratorPage.getAvailableCharts().length, 'All but one chart removed').to.be.equal(1); - }); - }); - - // Removes the first chart. - describe('Remove chart from Report Generator from the Usage tab', function () { - it('Click on the "Usage" tab', function () { - usagePage.selectTab(); - }); - it('Set date range', function () { - usagePage.setStartDate(usageTabCharts[0].startDate); - usagePage.setEndDate(usageTabCharts[0].endDate); - usagePage.refresh(); - }); - it('Make chart unavailable', function () { - usagePage.makeCurrentChartUnavailableForReport(); - }); - it('Select Report Generator tab', function () { - reportGeneratorPage.selectTab(); - }); - it('No available charts listed', function () { - expect(reportGeneratorPage.getAvailableCharts().length, 'No charts in the list of available charts').to.be.equal(0); - }); - }); - - describe('Delete multiple reports from "My Reports"', function () { - it('Select reports', function () { - reportGeneratorPage.selectAllReports(); - }); - it('Delete reports', function () { - reportGeneratorPage.deleteSelectedReports(); - reportGeneratorPage.confirmDeleteSelectedReports(); - expect(reportGeneratorPage.getMyReportsRows().length).to.be.equal(0); - }); - }); - - // These tests confirm that the report generator state is the same - // at the end of the tests as it was at the beginning. - describe('Confirm that there are no reports or available charts', function () { - it('No reports listed', function () { - expect(reportGeneratorPage.getMyReportsRows().length, 'No rows in the list of reports').to.be.equal(0); - }); - it('No available charts listed', function () { - expect(reportGeneratorPage.getAvailableCharts().length, 'No charts in the list of available charts').to.be.equal(0); - }); - }); - loginPage.logout(); - } -}); diff --git a/tests/ui/test/specs/xdmod/reportGenerator.page.js b/tests/ui/test/specs/xdmod/reportGenerator.page.js deleted file mode 100644 index a79a89c3c5..0000000000 --- a/tests/ui/test/specs/xdmod/reportGenerator.page.js +++ /dev/null @@ -1,1663 +0,0 @@ -/** - * Report generator test classes. - */ -const expected = global.testHelpers.artifacts.getArtifact('reportGenerator'); -/** - * Helper function for creating XPath expression predicates to accurately - * determine if an element has a class. - * - * This prevents incorrect matching of class names where the desired class name - * is a substring of other class names. - * - * @param {String} className CSS class name. - * - * @return {String} XPath expression predicate. - */ -function classContains(className) { - return `contains(concat(" ",normalize-space(@class)," ")," ${className} ")`; -} - -var xdmod = require('./xdmod.page.js'); - -/** - * A single row in the list of reports. - */ -class MyReportsRow { - - /** - * @param {String} selector XPath selector for a "My Reports" row. - */ - constructor(selector) { - this.selector = selector; - - this.selectors = { - name: selector + '//tr/td[position()=2]//div', - derivedFrom: selector + '//tr/td[position()=3]//div', - schedule: selector + '//tr/td[position()=4]//div', - deliveryFormat: selector + '//tr/td[position()=5]//div[position()=2]', - numberOfCharts: selector + '//tr/td[position()=6]//div', - numberOfChartsPerPage: selector + '//tr/td[position()=6]//div/span' - }; - } - - /** - * Get the name of the chart. - * - * @return {String} - */ - getName() { - return browser.getText(this.selectors.name); - } - - /** - * Get the name of the template this report is derived from or "Manual" if - * the report was created manually.. - * - * @return {String} - */ - getDerivedFrom() { - return browser.getText(this.selectors.derivedFrom); - } - - /** - * Get the schedule (frequency) of the report. - * - * @return {String} - */ - getSchedule() { - return browser.getText(this.selectors.schedule); - } - - /** - * Get the delivery format (PDF or Word Document) of the report. - * - * @return {String} - */ - getDeliveryFormat() { - return browser.getText(this.selectors.deliveryFormat); - } - - /** - * Get the number of charts in the report. - * - * @return {Number} - */ - getNumberOfCharts() { - return parseInt(browser.getText(this.selectors.numberOfCharts).trim(), 10); - } - - /** - * Get the number of charts per page in the report. - * - * @return {Number} - */ - getNumberOfChartsPerPage() { - const chartsPerPage = browser.getText(this.selectors.numberOfChartsPerPage); - const matches = chartsPerPage.match(/\((\d+) per page\)/); - if (matches === null) { - throw new Error(`Failed to determine number of charts from text "${chartsPerPage}"`); - } - return parseInt(matches[1], 10); - } - - /** - * Check if the row is selected. - * - * @return {Boolean} - */ - isSelected() { - return browser.getAttribute(this.selector, 'class').match(/(^| )x-grid3-row-selected($| )/) !== null; - } - - /** - * Click the row. - */ - click() { - browser.click(this.selector); - } - - /** - * Double click the row. - */ - doubleClick() { - browser.doubleClick(this.selector); - } - - /** - * Toggle the row selection using Control+Click. - */ - toggleSelection() { - browser.keys('Control'); - browser.click(this.selector); - browser.keys('Control'); - } -} - -/** - * A chart in the "Available Charts" list. - */ -class AvailableChart { - - /** - * @param {String} selector XPath selector for an "Available Chart". - */ - constructor(selector) { - this.selector = selector; - const baseSelector = selector + '//tr/td[position()=2]/div/div'; - this.selectors = { - titleAndDrillDetails: baseSelector + '/div[position()=4]/span', - dateDescription: baseSelector + '/div[position()=5]', - timeframeType: baseSelector + '/div[position()=6]' - }; - } - - /** - * Get the combined title and drill-down details of the chart. - * - * Contains "
" between the title and drill details. - * - * @return {String} - */ - getTitleAndDrillDetails() { - return browser.getHTML(this.selectors.titleAndDrillDetails, false); - } - - /** - * Get the title of the chart. - * - * @return {String} - */ - getTitle() { - return this.getTitleAndDrillDetails().split('
')[0].trim(); - } - - /** - * Get the drill-down details of the chart. - * - * @return {String} - */ - getDrillDetails() { - const drillDetails = this.getTitleAndDrillDetails().split('
')[1].trim(); - return drillDetails === ' ' ? '' : drillDetails; - } - - /** - * Get the date description of the chart. - * - * @return {String} - */ - getDateDescription() { - return browser.getText(this.selectors.dateDescription); - } - - /** - * Get the timeframe of the chart. - * - * @return {String} - */ - getTimeframeType() { - return browser.getText(this.selectors.timeframeType); - } - - /** - * Check if the chart is selected. - * - * @return {Boolean} - */ - isSelected() { - return browser.getAttribute(this.selector, 'class').match(/(^| )x-grid3-row-selected($| )/) !== null; - } - - /** - * Click the chart. - */ - click() { - browser.click(this.selector); - } - - /** - * Toggle the chart selection using Control+Click. - */ - toggleSelection() { - browser.keys('Control'); - browser.click(this.selector); - browser.keys('Control'); - } -} - -/** - * A chart in the "Included Charts" list. - */ -class IncludedChart { - - /** - * @param {String} selector XPath selector for an "Included Chart". - */ - constructor(selector) { - this.selector = selector; - const baseSelector = selector + '//tr/td[position()=2]/div/div'; - this.selectors = { - titleAndDrillDetails: baseSelector + '/div[position()=4]/span', - dateDescription: baseSelector + '/div[position()=6]', - timeframeEditIcon: baseSelector + '/div[position()=5]/a[position()=1]', - timeframeType: baseSelector + '/div[position()=7]/span', - timeframeResetIcon: baseSelector + '/div[position()=5]/a[position()=2]' - }; - } - - /** - * Get the combined title and drill-down details of the chart. - * - * Contains "
" between the title and drill details. - * - * @return {String} - */ - getTitleAndDrillDetails() { - return browser.getHTML(this.selectors.titleAndDrillDetails, false); - } - - /** - * Get the title of the chart. - * - * @return {String} - */ - getTitle() { - return this.getTitleAndDrillDetails().split('
')[0].trim(); - } - - /** - * Get the drill-down details of the chart. - * - * @return {String} - */ - getDrillDetails() { - const drillDetails = this.getTitleAndDrillDetails().split('
')[1].trim(); - return drillDetails === ' ' ? '' : drillDetails; - } - - /** - * Get the date description of the chart. - * - * @return {String} - */ - getDateDescription() { - return browser.getText(this.selectors.dateDescription); - } - - /** - * Get the timeframe of the chart. - * - * @return {String} - */ - getTimeframeType() { - return browser.getText(this.selectors.timeframeType); - } - - /** - * Check if the row is selected. - * - * @return {Boolean} - */ - isSelected() { - return browser.getAttribute(this.selector, 'class').match(/(^| )x-grid3-row-selected($| )/) !== null; - } - - /** - * Click the chart. - */ - click() { - browser.click(this.selector); - } - - /** - * Click the date range to edit timeframe. - */ - editTimeframe() { - browser.click(this.selectors.timeframeEditIcon); - } - - /** - * Click the timeframe reset icon. - */ - resetTimeframe() { - browser.click(this.selectors.timeframeResetIcon); - } - - /** - * Toggle the chart selection using Control+Click. - */ - toggleSelection() { - browser.keys('Control'); - browser.click(this.selector); - browser.keys('Control'); - } -} - -/** - * Report generator page. - */ -class ReportGenerator { - constructor() { - this.tabName = 'Report Generator'; - - this.selectors = { - tab: () => `//div[${classContains('x-tab-panel-header')}]//span[${classContains('x-tab-strip-text')} and text()="${this.tabName}"]`, - panel: () => '//div[@id="report_generator"]', - mask: () => `//div[${classContains('ext-el-mask')}]`, - myReports: { - panel: () => this.selectors.panel() + `//div[${classContains('report_overview')}]`, - toolbar: { - panel: () => this.selectors.myReports.panel() + `//div[${classContains('x-panel-tbar')}]`, - selectButton: () => this.selectors.myReports.toolbar.panel() + '//button[text()="Select"]', - selectMenu: () => `//div[${classContains('x-menu-floating')}]`, - selectAllReportsButton: () => this.selectors.myReports.toolbar.selectMenu() + '//span[text()="All Reports"]/ancestor::a', - selectNoReportsButton: () => this.selectors.myReports.toolbar.selectMenu() + '//span[text()="No Reports"]/ancestor::a', - invertSelectionButton: () => this.selectors.myReports.toolbar.selectMenu() + '//span[text()="Invert Selection"]/ancestor::a', - newButton: () => this.selectors.myReports.toolbar.panel() + '//button[text()="New"]', - newBasedOnButton: () => this.selectors.myReports.toolbar.panel() + '//button[text()="New Based On"]', - newBasedOnMenu: () => `//div[${classContains('x-menu-floating')} and .//img[${classContains('btn_selected_report')} or ${classContains('btn_report_template')}]]`, - newBasedOnRows: () => this.selectors.myReports.toolbar.newBasedOnMenu() + `//li[not(${classContains('x-menu-sep-li')})]`, - newBasedOnTemplateRows: () => this.selectors.myReports.toolbar.newBasedOnMenu() + `//li[.//img[${classContains('btn_report_template')}]]`, - newBasedOnReportRows: () => this.selectors.myReports.toolbar.newBasedOnMenu() + `//li[.//img[${classContains('btn_selected_report')}]]`, - newBasedOnTemplate: name => this.selectors.myReports.toolbar.newBasedOnTemplateRows() + `//a[.//b[text()="${name}"]]`, - newBasedOnTemplateWithCenter: center => `//div[${classContains('x-menu-floating')}]//a[.//img[${classContains('btn_resource_provider')}] and .//span[contains(text(), "${center}")]]`, - newBasedOnReport: name => this.selectors.myReports.toolbar.newBasedOnReportRows() + `//a[./img[.//b[text()="${name}"]]`, - editButton: () => this.selectors.myReports.toolbar.panel() + '//button[text()="Edit"]', - previewButton: () => this.selectors.myReports.toolbar.panel() + '//button[text()="Preview"]', - sendNowButton: () => this.selectors.myReports.toolbar.panel() + '//button[text()="Send Now"]', - sendNowAsPdfButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="As PDF"]/ancestor::a`, - sendNowAsWordDocumentButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="As Word Document"]/ancestor::a`, - downloadButton: () => this.selectors.myReports.toolbar.panel() + '//button[text()="Download"]', - downloadAsPdfButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="As PDF"]/ancestor::a`, - downloadAsWordDocumentButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="As Word Document"]/ancestor::a`, - deleteButton: () => this.selectors.myReports.toolbar.panel() + '//button[text()="Delete"]' - }, - reportList: { - panel: () => this.selectors.myReports.panel() + `//div[${classContains('x-panel-body-noheader')}]`, - rows: () => this.selectors.myReports.reportList.panel() + `//div[${classContains('x-grid3-row')}]`, - rowByIndex: index => this.selectors.myReports.reportList.panel() + `//div[${classContains('x-grid3-row')} and position()=${index}]` - } - }, - reportPreview: { - panel: () => this.selectors.panel() + `//div[${classContains('report_preview')}]`, - toolbar: { - panel: () => this.selectors.reportPreview.panel() + `//div[${classContains('x-panel-tbar')}]`, - sendNowButton: () => this.selectors.reportPreview.toolbar.panel() + '//button[text()="Send Now"]', - downloadButton: () => this.selectors.reportPreview.toolbar.panel() + '//button[text()="Download"]', - returnToReportsOverviewButton: () => this.selectors.reportPreview.toolbar.panel() + `//button[${classContains('btn_return_to_previous')}]` - } - }, - reportEditor: { - panel: () => this.selectors.panel() + `//div[${classContains('report_edit')}]`, - toolbar: { - panel: () => this.selectors.reportEditor.panel() + `//div[${classContains('x-panel-tbar')} and .//button[text()="Save"]]`, - saveButton: () => this.selectors.reportEditor.toolbar.panel() + '//button[text()="Save"]', - saveAsButton: () => this.selectors.reportEditor.toolbar.panel() + '//button[text()="Save As"]', - previewButton: () => this.selectors.reportEditor.toolbar.panel() + '//button[text()="Preview"]', - sendNowButton: () => this.selectors.reportEditor.toolbar.panel() + '//button[text()="Send Now"]', - downloadButton: () => this.selectors.reportEditor.toolbar.panel() + '//button[text()="Download"]', - returnToMyReportsButton: () => this.selectors.reportEditor.toolbar.panel() + `//button[${classContains('btn_return_to_overview')}]` - }, - generalInformation: { - panel: () => this.selectors.reportEditor.panel() + `//div[${classContains('x-panel')} and .//span[text()="General Information"]]`, - reportNameInput: () => this.selectors.reportEditor.generalInformation.panel() + '//input[@name="report_name"]', - reportTitleInput: () => this.selectors.reportEditor.generalInformation.panel() + '//input[@name="report_title"]', - headerTextInput: () => this.selectors.reportEditor.generalInformation.panel() + '//input[@name="report_header"]', - footerTextInput: () => this.selectors.reportEditor.generalInformation.panel() + '//input[@name="report_footer"]' - }, - chartLayout: { - panel: () => this.selectors.reportEditor.panel() + `//div[${classContains('x-panel')} and .//span[text()="Chart Layout"]]`, - oneChartPerPageRadioButton: () => this.selectors.reportEditor.chartLayout.panel() + '//input[@value="1_up"]', - twoChartsPerPageRadioButton: () => this.selectors.reportEditor.chartLayout.panel() + '//input[@value="2_up"]' - }, - scheduling: { - panel: () => this.selectors.reportEditor.panel() + `//div[${classContains('x-panel')} and .//span[text()="Scheduling"]]`, - scheduleInput: () => this.selectors.reportEditor.scheduling.panel() + '//input[@name="report_generator_report_schedule"]', - scheduleOption: name => `//div[${classContains('x-combo-list-item')} and text()="${name}"]`, - deliveryFormatInput: () => this.selectors.reportEditor.scheduling.panel() + '//div[./label[text()="Delivery Format:"]]//input', - deliveryFormatOption: name => `//div[${classContains('x-combo-list-item')} and text()="${name}"]` - }, - includedCharts: { - panel: () => this.selectors.reportEditor.panel() + '//div[@id="ReportCreatorGrid"]', - toolbar: { - panel: () => this.selectors.reportEditor.includedCharts.panel() + `//div[${classContains('x-panel-tbar')}]`, - selectButton: () => this.selectors.reportEditor.includedCharts.toolbar.panel() + '//button[text()="Select"]', - selectAllChartsButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="All Charts"]/ancestor::a`, - selectNoChartsButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="No Charts"]/ancestor::a`, - invertSelectionButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="Invert Selection"]/ancestor::a`, - editTimeframeButton: () => this.selectors.reportEditor.includedCharts.toolbar.panel() + '//button[text()="Edit Timeframe of Selected Charts"]', - removeButton: () => this.selectors.reportEditor.includedCharts.toolbar.panel() + '//button[text()="Remove"]' - }, - chartList: { - panel: () => this.selectors.reportEditor.includedCharts.panel() + '//div[@class="x-panel-body" and .//div[text()="Chart"]]', - rows: () => this.selectors.reportEditor.includedCharts.chartList.panel() + `//div[${classContains('x-grid3-row')}]` - } - } - }, - availableCharts: { - panel: () => this.selectors.panel() + '//div[@id="chart_pool_panel"]', - toolbar: { - panel: () => this.selectors.availableCharts.panel() + '//div[@class="x-panel-tbar"]', - selectButton: () => this.selectors.availableCharts.toolbar.panel() + '//button[text()="Select"]', - selectAllChartsButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="All Charts"]/ancestor::a`, - selectNoChartsButton: () => `//div[${classContains('x-menu-floating')}]//span[text()="No Charts"]/ancestor::a`, - invertSelectionButton: () => `//div[${classContains('x-menu-floating')}]//a[.//span[text()="Invert Selection"]]`, - deleteButton: () => this.selectors.availableCharts.toolbar.panel() + '//button[text()="Delete"]' - }, - chartList: { - panel: () => this.selectors.availableCharts.panel() + '//div[@class="x-panel-body" and .//div[text()="Chart"]]', - rows: () => this.selectors.availableCharts.chartList.panel() + `//div[${classContains('x-grid3-row')}]` - } - }, - message: { - window: () => '//div[@id="report_generator_message"]', - titleElement: () => this.selectors.message.window() + `//span[${classContains('x-window-header-text')}]`, - textElement: () => this.selectors.message.window() + '//b' - }, - deleteSelectedReports: { - window: () => `//div[${classContains('x-window')} and .//span[text()="Delete Selected Report" or text()="Delete Selected Reports"]]`, - yesButton: () => this.selectors.deleteSelectedReports.window() + '//button[text()="Yes"]', - noButton: () => this.selectors.deleteSelectedReports.window() + '//button[text()="No"]' - }, - unsavedChanges: { - window: () => `//div[${classContains('x-window')} and .//span[text()="Unsaved Changes"]]`, - yesButton: () => this.selectors.unsavedChanges.window() + '//button[text()="Yes"]', - noButton: () => this.selectors.unsavedChanges.window() + '//button[text()="No"]', - cancelButton: () => this.selectors.unsavedChanges.window() + '//button[text()="Cancel"]' - }, - deleteSelectedCharts: { - window: () => `//div[${classContains('x-window')} and .//span[text()="Delete Selected Chart" or text()="Delete Selected Charts"]]`, - yesButton: () => this.selectors.deleteSelectedCharts.window() + '//button[text()="Yes"]', - noButton: () => this.selectors.deleteSelectedCharts.window() + '//button[text()="No"]' - }, - removeSelectedCharts: { - window: () => `//div[${classContains('x-window')} and .//span[text()="Remove Selected Chart" or text()="Remove Selected Charts"]]`, - yesButton: () => this.selectors.removeSelectedCharts.window() + '//button[text()="Yes"]', - noButton: () => this.selectors.removeSelectedCharts.window() + '//button[text()="No"]' - }, - saveReportAs: { - window: () => `//div[${classContains('x-window')} and .//span[text()="Save Report As"]]`, - reportNameInput: () => this.selectors.saveReportAs.window() + '//input[@name="report_name"]', - saveButton: () => this.selectors.saveReportAs.window() + '//button[text()="Save"]', - closeButton: () => this.selectors.saveReportAs.window() + '//button[text()="Close"]', - reportNameInputInvalid: () => this.selectors.saveReportAs.window() + `//input[@name="report_name" and ${classContains('x-form-invalid')}]` - }, - reportBuilt: { - window: () => `//div[${classContains('x-window')} and .//span[text()="Report Built"]]`, - viewReportButton: () => this.selectors.reportBuilt.window() + '//button[text()="View Report"]', - closeButton: () => this.selectors.reportBuilt.window() + `//div[${classContains('x-tool-close')}]` - }, - editChartTimeframe: { - window: () => `//div[${classContains('chart_date_editor')}]`, - specificRadioButton: () => this.selectors.editChartTimeframe.window() + '//input[@name="report_creator_chart_entry" and @value="Specific"]', - periodicRadioButton: () => this.selectors.editChartTimeframe.window() + '//input[@name="report_creator_chart_entry" and @value="Periodic"]', - periodicInput: () => this.selectors.editChartTimeframe.window() + '//table[contains(@class,"menu")]//button', - periodicOption: name => `//div[${classContains('x-menu-floating')}]//a[starts-with(text(),"${name}')]`, - startDateInput: () => this.selectors.editChartTimeframe.window() + '//input[@id="report_generator_edit_date_start_date_field"]', - endDateInput: () => this.selectors.editChartTimeframe.window() + '//input[@id="report_generator_edit_date_end_date_field"]', - updateButton: () => this.selectors.editChartTimeframe.window() + `//button[${classContains('chart_date_editor_update_button')}]`, - cancelButton: () => this.selectors.editChartTimeframe.window() + `//button[${classContains('chart_date_editor_cancel_button')}]`, - errorMessage: () => this.selectors.editChartTimeframe.window() + `//div[${classContains('overlay_message')}]` - }, - // The mask with the check mark image that is displayed after a report is ready for download or has been emailed. - checkmarkMask: () => `//div[${classContains('ext-el-mask-msg')} and .//img[@src="gui/images/checkmark.png"]]` - }; - } - - /** - * Determine if the report generator page is enabled. - * - * The report generator is considered to be enabled if the report - * generator tab exists. - * - * @return {Boolean} True if the report generator page is enabled. - */ - isEnabled() { - return browser.isExisting(this.selectors.tab()); - } - - /** - * Check if the "New Based On" button in the "My Reports" toolbar is - * enabled. - * - * There are two separate "New Based On" buttons. Only one should be - * visible at a time. This method returns true if the visible button is - * enabled. - * - * @return {Boolean} True if the button is enabled. - */ - isNewBasedOnEnabled() { - const visibleButtons = $$(this.selectors.myReports.toolbar.newBasedOnButton() + `/ancestor::table[${classContains('x-btn')}]`).filter(button => button.isVisible()); - expect(visibleButtons.length, 'One "New Based On" button is visible').to.be.equal(1); - return visibleButtons[0].getAttribute('class').match(/(^| )x-item-disabled($| )/) === null; - } - - /** - * Check if the "Edit" button in the "My Reports" toolbar is enabled. - * - * @return {Boolean} True if the button is enabled. - */ - isEditSelectedReportsEnabled() { - return browser.getAttribute(this.selectors.myReports.toolbar.editButton() + `/ancestor::table[${classContains('x-btn')}]`, 'class').match(/(^| )x-item-disabled($| )/) === null; - } - - /** - * Check if the "Preview" button in the "My Reports" toolbar is enabled. - * - * @return {Boolean} True if the button is enabled. - */ - isPreviewSelectedReportsEnabled() { - return browser.getAttribute(this.selectors.myReports.toolbar.previewButton() + `/ancestor::table[${classContains('x-btn')}]`, 'class').match(/(^| )x-item-disabled($| )/) === null; - } - - /** - * Check if the "Send Now" button in the "My Reports" toolbar is enabled. - * - * @return {Boolean} True if the button is enabled. - */ - isSendSelectedReportsEnabled() { - // There are two separate "Send Now" buttons in the "My Reports" panel. - // Only one should be visible at a time. - const visibleButtons = $$(this.selectors.myReports.toolbar.sendNowButton() + `/ancestor::table[${classContains('x-btn')}]`).filter(button => button.isVisible()); - expect(visibleButtons.length, 'One "Send Now" button is visible').to.be.equal(1); - return visibleButtons[0].getAttribute('class').match(/(^| )x-item-disabled($| )/) === null; - } - - /** - * Check if the "Download" button in the "My Reports" toolbar is enabled. - * - * @return {Boolean} True if the button is enabled. - */ - isDownloadSelectedReportsEnabled() { - const visibleButtons = $$(this.selectors.myReports.toolbar.downloadButton() + `/ancestor::table[${classContains('x-btn')}]`).filter(button => button.isVisible()); - expect(visibleButtons.length, 'One "New Based On" button is visible').to.be.equal(1); - return visibleButtons[0].getAttribute('class').match(/(^| )x-item-disabled($| )/) === null; - } - - /** - * Check if the "Delete" button in the "My Reports" toolbar is enabled. - * - * @return {Boolean} True if the button is enabled. - */ - isDeleteSelectedReportsEnabled() { - return browser.getAttribute(this.selectors.myReports.toolbar.deleteButton() + `/ancestor::table[${classContains('x-btn')}]`, 'class').match(/(^| )x-item-disabled($| )/) === null; - } - - /** - * Wait for the "My Reports" panel to be visible. - */ - waitForMyReportsPanelVisible() { - browser.waitForVisible(this.selectors.myReports.panel()); - } - - /** - * Wait for the "Report Preview" panel to be visible. - */ - waitForReportPreviewPanelVisible() { - browser.waitForVisible(this.selectors.reportPreview.panel()); - } - - /** - * Wait for the "Report Editor" panel to be visible. - */ - waitForReportEditorPanelVisible() { - browser.waitForVisible(this.selectors.reportEditor.panel()); - } - - /** - * Wait for the "Included Charts" panel to be visible. - */ - waitForIncludedChartsPanelVisible() { - this.waitForReportEditorPanelVisible(); - browser.waitForVisible(this.selectors.reportEditor.includedCharts.panel()); - } - - /** - * Wait for the "Available Charts" panel to be visible. - */ - waitForAvailableChartsPanelVisible() { - browser.waitForVisible(this.selectors.availableCharts.panel()); - } - - /** - * Wait for the "Message" window to be visible. - */ - waitForMessageWindowVisible() { - browser.waitForVisible(this.selectors.message.window()); - } - - /** - * Wait for the "Delete Selected Report" window to be visible. - */ - waitForDeleteSelectedReportsWindowVisible() { - browser.waitForVisible(this.selectors.deleteSelectedReports.window()); - } - - /** - * Wait for the "Delete Selected Report" window to not be visible. - */ - waitForDeleteSelectedReportsWindowNotVisible() { - browser.waitForInvisible(this.selectors.deleteSelectedReports.window(), 500); - } - - /** - * Wait for the "Delete Selected Chart" window to be visible. - */ - waitForDeleteSelectedChartsWindowVisible() { - browser.waitForVisible(this.selectors.deleteSelectedCharts.window()); - } - - /** - * Wait for the "Delete Selected Chart" window to not be visible. - */ - waitForDeleteSelectedChartsWindowNotVisible() { - browser.waitForInvisible(this.selectors.deleteSelectedCharts.window(), 500); - } - - /** - * Wait for the "Remove Selected Chart" window to be visible. - */ - waitForRemoveSelectedChartsWindowVisible() { - browser.waitForVisible(this.selectors.removeSelectedCharts.window()); - } - - /** - * Wait for the "Remove Selected Chart" window to not be visible. - */ - waitForRemoveSelectedChartsWindowNotVisible() { - browser.waitForInvisible(this.selectors.removeSelectedCharts.window(), 500); - } - - /** - * Wait for the "Edit Chart Timeframe" window to be visible. - * - * Also waits for the error message to not be visible. - */ - waitForEditChartTimeframeWindowVisible() { - browser.waitForVisible(this.selectors.editChartTimeframe.window()); - browser.waitForInvisible(this.selectors.editChartTimeframe.errorMessage(), 2500); - } - - /** - * Wait for the "Save Report As" window to be visible. - */ - waitForSaveReportAsWindowVisible() { - browser.waitForVisible(this.selectors.saveReportAs.window()); - } - - /** - * Wait for the "Report Built" window to be visible. - */ - waitForReportBuiltWindowVisible() { - browser.waitForVisible(this.selectors.reportBuilt.window()); - } - - /** - * Wait for the "Report Built" window to not be visible. - */ - waitForReportBuiltWindowNotVisible() { - browser.waitForInvisible(this.selectors.reportBuilt.window(), 500); - } - - /** - * Convenience method to convert the rows in the "My Reports" list into - * objects. - * - * The list of reports must be visible or this method will throw an - * exception. - * - * @return {MyReportsRow[]} - */ - getMyReportsRows() { - this.waitForMyReportsPanelVisible(); - const selector = this.selectors.myReports.reportList.rows(); - return $$(selector).map((element, i) => new MyReportsRow(`${selector}[${i + 1}]`)); - } - - /** - * Convenience method to convert the charts in the "Included Charts" list - * into objects. - * - * The list of included charts must be visible or this method will throw an - * exception. - * - * @return {IncludedChart[]} - */ - getIncludedCharts() { - this.waitForIncludedChartsPanelVisible(); - const selector = this.selectors.reportEditor.includedCharts.chartList.rows(); - return $$(selector).map((element, i) => new IncludedChart(`${selector}[${i + 1}]`)); - } - - /** - * Convenience method to convert the charts in the "Available Charts" list - * into objects. - * - * The list of available charts must be visible or this method will throw an - * exception. - * - * @return {IncludedChart[]} - */ - getAvailableCharts() { - this.waitForAvailableChartsPanelVisible(); - const selector = this.selectors.availableCharts.chartList.rows(); - var elemCount = browser.elements(selector).length; - var lastCount = -1; - while (elemCount !== lastCount) { - lastCount = elemCount; - browser.pause(100); - elemCount = browser.elements(selector).length; - } - return $$(selector).map((element, i) => new AvailableChart(`${selector}[${i + 1}]`)); - } - - /** - * Get the title of the "Message" window. - * - * @return {String} - */ - getMessageWindowTitle() { - this.waitForMessageWindowVisible(); - return browser.getText(this.selectors.message.titleElement()); - } - - /** - * Get the message text of the "Message" window. - * - * @return {String} - */ - getMessage() { - this.waitForMessageWindowVisible(); - return browser.getText(this.selectors.message.textElement()); - } - - /** - * Select the "Report Generator" tab by clicking it. - */ - selectTab() { - xdmod.selectTab('report_generator'); - } - - /** - * Select a report in the "My Reports" panel by clicking the row in the list - * of reports. - * - * @param {String} reportName The name of the report. - */ - selectReportByName(reportName) { - this.waitForMyReportsPanelVisible(); - - let foundReport = false; - - this.getMyReportsRows().forEach(row => { - if (row.getName() === reportName) { - row.click(); - foundReport = true; - } - }); - - if (!foundReport) { - throw new Error(`No report named "${reportName}" found`); - } - } - - /** - * Select all reports in the "My Reports" panel by clicking the "Select" - * button then clicking "All Reports". - */ - selectAllReports() { - this.waitForMyReportsPanelVisible(); - browser.click(this.selectors.myReports.toolbar.selectButton()); - browser.waitForVisible(this.selectors.myReports.toolbar.selectAllReportsButton()); - browser.click(this.selectors.myReports.toolbar.selectAllReportsButton()); - browser.waitForInvisible(this.selectors.myReports.toolbar.selectMenu(), 500); - // Ext.Button ignores clicks for 250ms after the menu is hidden so pause - // in case the menu is used multiple times in a row. - browser.pause(500); - } - - /** - * Deselect all reports in the "My Reports" panel by clicking the "Select" - * button then clicking "No Reports". - */ - deselectAllReports() { - this.waitForMyReportsPanelVisible(); - browser.click(this.selectors.myReports.toolbar.selectButton()); - browser.waitForVisible(this.selectors.myReports.toolbar.selectNoReportsButton()); - browser.click(this.selectors.myReports.toolbar.selectNoReportsButton()); - browser.waitForInvisible(this.selectors.myReports.toolbar.selectMenu(), 500); - // Ext.Button ignores clicks for 250ms after the menu is hidden so pause - // in case the menu is used multiple times in a row. - browser.pause(500); - } - - /** - * Invert the report selection in the "My Reports" panel by clicking the - * "Select" button then clicking "Invert Selection". - */ - invertReportSelection() { - this.waitForMyReportsPanelVisible(); - browser.click(this.selectors.myReports.toolbar.selectButton()); - browser.waitForVisible(this.selectors.myReports.toolbar.invertSelectionButton()); - // Multiple buttons match the "Invert Selection" selector, but only one should be visible. - const visibleButtons = $$(this.selectors.myReports.toolbar.invertSelectionButton()).filter(button => button.isVisible()); - expect(visibleButtons.length, 'One "Invert Selection" button is visible').to.be.equal(1); - visibleButtons[0].click(); - browser.waitForInvisible(this.selectors.myReports.toolbar.selectMenu(), 500); - // Ext.Button ignores clicks for 250ms after the menu is hidden so pause - // in case the menu is used multiple times in a row. - browser.pause(500); - } - - /** - * Create a new report from the "My Reports" panel by clicking the "New" - * button. - */ - createNewReport() { - this.waitForMyReportsPanelVisible(); - browser.click(this.selectors.myReports.toolbar.newButton()); - } - - /** - * Click the "New Based On" button. - * - * Must click the name of the report template separately. - */ - clickNewBasedOn() { - this.waitForMyReportsPanelVisible(); - // There are two separate "New Based On" buttons. Only one should be - // visible at a time. - const visibleButtons = $$(this.selectors.myReports.toolbar.newBasedOnButton() + `/ancestor::table[${classContains('x-btn')}]`).filter(button => button.isVisible()); - expect(visibleButtons.length, 'One "New Based On" button is visible').to.be.equal(1); - visibleButtons[0].click(); - // Ext.Button ignores clicks for 250ms after the menu is hidden so pause - // in case the menu is used multiple times in a row. - // Ext seems to also be "slow" to add events to the button, wait to make sure events get added - browser.pause(750); - } - - /** - * Get the report template names. - * - * The list of report template names must be visible. - * - * @return {String[]} Report template names. - */ - getReportTemplateNames() { - browser.waitForVisible(this.selectors.myReports.toolbar.newBasedOnTemplateRows()); - return $$(this.selectors.myReports.toolbar.newBasedOnTemplateRows()).map(row => row.$(`//a[./img[${classContains('btn_report_template')}]]//b`).getText()); - } - - /** - * Select a template from the "New Based On" menu. - * - * Must click the "New Based On" button before selecting the template. - * - * @param {String} templateName The full name of the template. - * @param {String} center The name of the center to select [optional]. - */ - selectNewBasedOnTemplate(templateName, center) { - browser.waitForVisible(this.selectors.myReports.toolbar.newBasedOnMenu()); - const reportCount = this.getMyReportsRows().length; - if (!center) { - browser.waitAndClick(this.selectors.myReports.toolbar.newBasedOnTemplate(templateName)); - } else { - // move the mouse to the middle of the menu so that the center selection menu appears - browser.moveToObject(this.selectors.myReports.toolbar.newBasedOnTemplate(templateName)); - - // wait for the new menu to be visible - browser.waitForVisible(this.selectors.myReports.toolbar.newBasedOnTemplateWithCenter(center)); - - // Select the option that corresponds with the center argument - browser.click(this.selectors.myReports.toolbar.newBasedOnTemplateWithCenter(center)); - } - // There is no visible indicator that the reports are being - // updated, so wait for the number of rows to increase - // specifically look for there to be at least one more item - // this seems to be do the the fact that elements and selectors get cached - browser.waitForVisible(this.selectors.myReports.reportList.rowByIndex(reportCount + 1)); - } - - /** - * Select a report from the "New Based On" menu. - * - * Must select the same report in the "My Reports list (and no other - * reports) and click the "New Based On" button before selecting the report - * in the "New Based On" menu. - * - * @param {String} reportName The full name of the template. - */ - selectNewBasedOnReport(reportName) { - browser.waitForVisible(this.selectors.myReports.toolbar.newBasedOnMenu()); - browser.click(this.selectors.myReports.toolbar.newBasedOnReport(reportName)); - } - - /** - * Edit the selected report in the "My Reports" panel by clicking the - * "Edit" button. - */ - editSelectedReports() { - this.waitForMyReportsPanelVisible(); - browser.click(this.selectors.myReports.toolbar.editButton()); - } - - /** - * Edit a report in the "My Reports" panel by double clicking the row for - * that report. - * - * @param {String} reportName Name of the report. - */ - editReportByName(reportName) { - this.waitForMyReportsPanelVisible(); - - let foundReport = false; - - this.getMyReportsRows().forEach(row => { - if (row.getName() === reportName) { - row.doubleClick(); - foundReport = true; - } - }); - - if (!foundReport) { - throw new Error(`No report named "${reportName}" found`); - } - } - - /** - * Preview the selected report in the "My Reports" panel by clicking the - * "Preview" button. - */ - previewSelectedReports() { - this.waitForMyReportsPanelVisible(); - browser.click(this.selectors.myReports.toolbar.previewButton()); - } - - /** - * Return to the "My Reports" panel from the "Report Preview" by clicking - * the "Return To Reports Overview" button. - */ - returnToReportsOverview() { - this.waitForReportPreviewPanelVisible(); - browser.click(this.selectors.reportPreview.toolbar.returnToReportsOverviewButton()); - } - - /** - * Click the "Send Now" button in the "My Reports" panel. - * - * It may be necessary to click the "As PDF" or "As Word Document" option - * after clicking this button. - */ - sendSelectedReportsNow() { - this.waitForMyReportsPanelVisible(); - // There are two separate "Send Now" buttons in the "My Reports" panel. - // Only one should be visible at a time. - const visibleButtons = $$(this.selectors.myReports.toolbar.sendNowButton() + `/ancestor::table[${classContains('x-btn')}]`).filter(button => button.isVisible()); - expect(visibleButtons.length, 'One "Send Now" button is visible').to.be.equal(1); - visibleButtons[0].click(); - } - - /** - * Select the "As PDF" option from the "Send Now" menu in the "My Reports" - * panel. - */ - sendSelectedReportsAsPdfNow() { - browser.waitForVisible(this.selectors.myReports.toolbar.sendNowAsPdfButton()); - browser.click(this.selectors.myReports.toolbar.sendNowAsPdfButton()); - } - - /** - * Select the "As Word Document" option from the "Send Now" menu in the "My Reports" - * panel. - */ - sendSelectedReportsAsWordDocumentNow() { - browser.waitForVisible(this.selectors.myReports.toolbar.sendNowAsWordDocumentButton()); - browser.click(this.selectors.myReports.toolbar.sendNowAsWordDocumentButton()); - } - - /** - * Click the "Download" button in the "My Reports" panel. - * - * It may be necessary to click the "As PDF" or "As Word Document" option - * after clicking this button. - */ - downloadSelectedReports() { - this.waitForMyReportsPanelVisible(); - browser.click(this.selectors.myReports.toolbar.downloadButton()); - } - - /** - * Select the "As PDF" option from the "Download" menu in the "My Reports" - * panel. - */ - downloadSelectedReportsAsPdf() { - browser.waitForVisible(this.selectors.myReports.toolbar.downloadAsPdfButton()); - browser.click(this.selectors.myReports.toolbar.downloadAsPdfButton()); - // Wait for check mark image to appear and disappear. - browser.waitForVisible(this.selectors.checkmarkMask(), 60000); - browser.waitForInvisible(this.selectors.checkmarkMask(), 60000); - } - - /** - * Select the "As Word Document" option from the "Download" menu in the - * "My Reports" panel. - */ - downloadSelectedReportsAsWordDocument() { - browser.waitForVisible(this.selectors.myReports.toolbar.downloadAsWordDocumentButton()); - browser.click(this.selectors.myReports.toolbar.downloadAsWordDocumentButton()); - // Wait for check mark image to appear and disappear. - browser.waitForVisible(this.selectors.checkmarkMask()); - browser.waitForInvisible(this.selectors.checkmarkMask(), 3500); - } - - /** - * Close the "Report Built" window. - */ - closeReportBuiltWindow() { - this.waitForReportBuiltWindowVisible(); - browser.click(this.selectors.reportBuilt.closeButton()); - this.waitForReportBuiltWindowNotVisible(); - } - - /** - * Delete selected reports from "My Reports" by clicking the delete button. - * - * Does not confirm deletion of report, that button must be clicked - * separately. - */ - deleteSelectedReports() { - this.waitForMyReportsPanelVisible(); - browser.click(this.selectors.myReports.toolbar.deleteButton()); - } - - /** - * Click the "Yes" button in the "Delete Selected Report" window. - */ - confirmDeleteSelectedReports() { - const reportCount = this.getMyReportsRows().length; - this.waitForDeleteSelectedReportsWindowVisible(); - browser.click(this.selectors.deleteSelectedReports.yesButton()); - this.waitForDeleteSelectedReportsWindowNotVisible(); - // There is no visible indicator that the reports are being - // updated, so wait for the number of rows to change. - browser.waitUntil(() => reportCount !== this.getMyReportsRows().length, 2000, 'Expect number of reports to change'); - } - - /** - * Click the "Yes" button in the "Delete Selected Report" window. - */ - cancelDeleteSelectedReports() { - this.waitForDeleteSelectedReportsWindowVisible(); - browser.click(this.selectors.deleteSelectedReports.noButton()); - this.waitForDeleteSelectedReportsWindowNotVisible(); - } - - /** - * Save the report currently being edited. - */ - saveReport() { - this.waitForReportEditorPanelVisible(); - browser.click(this.selectors.reportEditor.toolbar.saveButton()); - browser.waitForVisible(this.selectors.message.window()); - expect(this.getMessageWindowTitle(), 'Message window title is correct').to.be.equal('Report Editor'); - browser.waitForInvisible(this.selectors.message.window(), 5000); - } - - /** - * Save a report with a different name. - * - * Does not confirm saving the report. - * - * @param {String} reportName The new name to give the report (optional). - */ - saveReportAs(reportName = undefined) { - this.waitForReportEditorPanelVisible(); - browser.click(this.selectors.reportEditor.toolbar.saveAsButton()); - this.waitForSaveReportAsWindowVisible(); - if (reportName !== undefined) { - /* - * There is a "timing" issue when setting the value of the input box - * and then clicking on save to quickly, this forces it to wait for - * the invalid input to not be present - */ - browser.setValue(this.selectors.saveReportAs.reportNameInput(), reportName); - browser.waitUntilNotExist(this.selectors.saveReportAs.reportNameInputInvalid()); - } - } - - /** - * Confirm saving a report by clicking the "Save" button. - * - * @param {Boolean} expectError True, if an error is expected - * (defaults to false). If no error is expected then this method - * does not return until the confirmation message is no longer - * visible. - */ - confirmSaveReportAs(expectError = false) { - this.waitForSaveReportAsWindowVisible(); - browser.click(this.selectors.saveReportAs.saveButton()); - - if (!expectError) { - expect(this.getMessageWindowTitle(), 'Message window title is correct').to.be.equal('Report Editor'); - expect(this.getMessage(), 'Message is correct').to.be.equal('Report successfully saved as a copy'); - browser.waitForInvisible(this.selectors.message.window(), 5000); - } - } - - /** - * Close "Save Report As" window by clicking the "Close" button. - */ - closeSaveReportAs() { - this.waitForSaveReportAsWindowVisible(); - browser.click(this.selectors.saveReportAs.closeButton()); - } - - /** - * Preview the report currently being edited. - */ - previewCurrentReport() { - this.waitForReportEditorPanelVisible(); - browser.click(this.selectors.reportEditor.toolbar.previewButton()); - } - - /** - * Click the "Send Now" button in the "Report Editor" panel. - * - * It may be necessary to click the "As PDF" or "As Word Document" option - * after clicking this button. - */ - sendCurrentlyBeingEditedReportNow() { - this.waitForReportEditorPanelVisible(); - browser.click(this.selectors.reportEditor.toolbar.sendNowButton()); - } - - /** - * Select the "As PDF" option from the "Send Now" menu in the - * "Report Editor" panel. - */ - sendCurrentlyBeingEditedReportAsPdfNow() { - this.waitForReportEditorPanelVisible(); - browser.waitForVisible(this.selectors.reportEditor.toolbar.sendNowAsPdfButton()); - browser.click(this.selectors.reportEditor.toolbar.sendNowAsPdfButton()); - } - - /** - * Select the "As PDF" option from the "Send Now" menu in the - * "Report Editor" panel. - */ - sendCurrentlyBeingEditedReportAsWordDocumentNow() { - this.waitForReportEditorPanelVisible(); - browser.waitForVisible(this.selectors.reportEditor.toolbar.sendNowAsWordDocumentButton()); - browser.click(this.selectors.reportEditor.toolbar.sendNowAsWordDocumentButton()); - } - - /** - * Click the "Download" button in the "Report Editor" panel. - * - * It may be necessary to click the "As PDF" or "As Word Document" option - * after clicking this button. - */ - downloadCurrentlyBeingEditedReport() { - this.waitForReportEditorPanelVisible(); - browser.click(this.selectors.reportEditor.toolbar.downloadButton()); - } - - /** - * Select the "As PDF" option from the "Download" menu in the - * "Report Editor" panel. - */ - downloadCurrentlyBeingEditedReportAsPdf() { - browser.waitForVisible(this.selectors.reportEditor.toolbar.downloadAsPdfButton()); - browser.click(this.selectors.reportEditor.toolbar.downloadAsPdfButton()); - } - - /** - * Select the "As Word Document" option from the "Download" menu in the - * "Report Editor" panel. - */ - downloadCurrentlyBeingEditedReportAsWordDocument() { - browser.waitForVisible(this.selectors.reportEditor.toolbar.downloadAsWordDocumentButton()); - browser.click(this.selectors.reportEditor.toolbar.downloadAsWordDocumentButton()); - } - - /** - * Return to "My Reports" by clicking the "Return to My Reports" button. - */ - returnToMyReports() { - this.waitForReportEditorPanelVisible(); - browser.click(this.selectors.reportEditor.toolbar.returnToMyReportsButton()); - } - - /** - * Get the name of the report currently being edited. - * - * @return {String} - */ - getReportName() { - this.waitForReportEditorPanelVisible(); - return browser.getValue(this.selectors.reportEditor.generalInformation.reportNameInput()); - } - - /** - * Set the name of the report currently being edited. - * - * @param {String} name - */ - setReportName(name) { - this.waitForReportEditorPanelVisible(); - return browser.setValue(this.selectors.reportEditor.generalInformation.reportNameInput(), name); - } - - /** - * Get the title of the report currently being edited. - * - * @return {String} - */ - getReportTitle() { - this.waitForReportEditorPanelVisible(); - return browser.getValue(this.selectors.reportEditor.generalInformation.reportTitleInput()); - } - - /** - * Set the title of the report currently being edited. - * - * @param {String} reportTitle - */ - setReportTitle(reportTitle) { - this.waitForReportEditorPanelVisible(); - return browser.setValue(this.selectors.reportEditor.generalInformation.reportTitleInput(), reportTitle); - } - - /** - * Get the header text of the report currently being edited. - * - * @return {String} - */ - getHeaderText() { - this.waitForReportEditorPanelVisible(); - return browser.getValue(this.selectors.reportEditor.generalInformation.headerTextInput()); - } - - /** - * Set the header text of the report currently being edited. - * - * @param {String} headerText - */ - setHeaderText(headerText) { - this.waitForReportEditorPanelVisible(); - return browser.setValue(this.selectors.reportEditor.generalInformation.headerTextInput(), headerText); - } - - /** - * Get the footer text of the report currently being edited. - * - * @return {String} - */ - getFooterText() { - this.waitForReportEditorPanelVisible(); - return browser.getValue(this.selectors.reportEditor.generalInformation.footerTextInput()); - } - - /** - * Set the footer text of the report currently being edited. - * - * @param {String} footerText - */ - setFooterText(footerText) { - this.waitForReportEditorPanelVisible(); - return browser.setValue(this.selectors.reportEditor.generalInformation.footerTextInput(), footerText); - } - - /** - * Get the number of charts per page for the report currently being edited. - * - * @return {Number} - */ - getNumberOfChartsPerPage() { - this.waitForReportEditorPanelVisible(); - if (browser.isSelected(this.selectors.reportEditor.chartLayout.oneChartPerPageRadioButton())) { - return 1; - } else if (browser.isSelected(this.selectors.reportEditor.chartLayout.twoChartsPerPageRadioButton())) { - return 2; - } - - throw new Error('No charts per page option selected'); - } - - /** - * Set the number of charts per page for the report currently being edited. - * - * @param {Number} chartsPerPage Must be 1 or 2. - */ - setNumberOfChartsPerPage(chartsPerPage) { - this.waitForReportEditorPanelVisible(); - - if (chartsPerPage === 1) { - browser.click(this.selectors.reportEditor.chartLayout.oneChartPerPageRadioButton()); - } else if (chartsPerPage === 2) { - browser.click(this.selectors.reportEditor.chartLayout.twoChartsPerPageRadioButton()); - } else { - throw new Error(`Invalid number of charts per page: "${chartsPerPage}"`); - } - } - - /** - * Get the schedule (delivery frequency) of the report currently being - * edited. - * - * @return {String} - */ - getSchedule() { - this.waitForReportEditorPanelVisible(); - return browser.getValue(this.selectors.reportEditor.scheduling.scheduleInput()); - } - - /** - * Set the schedule (delivery frequency) of the report currently being - * edited. - * - * @param {String} frequency - */ - setSchedule(frequency) { - this.waitForReportEditorPanelVisible(); - browser.click(this.selectors.reportEditor.scheduling.scheduleInput()); - browser.waitForVisible(this.selectors.reportEditor.scheduling.scheduleOption(frequency)); - browser.click(this.selectors.reportEditor.scheduling.scheduleOption(frequency)); - } - - /** - * Get the delivery format ('PDF" or "Word Document') of the report - * currently being edited. - * - * @return {String} - */ - getDeliveryFormat() { - this.waitForReportEditorPanelVisible(); - return browser.getValue(this.selectors.reportEditor.scheduling.deliveryFormatInput()); - } - - /** - * Set the delivery format ('PDF" or "Word Document') of the report - * currently being edited. - * - * @param {String} format - */ - setDeliveryFormat(format) { - this.waitForReportEditorPanelVisible(); - browser.click(this.selectors.reportEditor.scheduling.deliveryFormatInput()); - browser.waitForVisible(this.selectors.reportEditor.scheduling.deliveryFormatOption(format)); - browser.click(this.selectors.reportEditor.scheduling.deliveryFormatOption(format)); - } - - /** - * Select all charts in the "Included Charts" panel by clicking the "Select" - * button then clicking "All Charts". - */ - selectAllIncludedCharts() { - this.waitForIncludedChartsPanelVisible(); - browser.click(this.selectors.reportEditor.includedCharts.toolbar.selectButton()); - browser.waitForVisible(this.selectors.reportEditor.includedCharts.toolbar.selectAllChartsButton()); - browser.click(this.selectors.reportEditor.includedCharts.toolbar.selectAllChartsButton()); - browser.waitForInvisible(this.selectors.reportEditor.includedCharts.toolbar.selectAllChartsButton(), 500); - // Ext.Button ignores clicks for 250ms after the menu is hidden so pause - // in case the menu is used multiple times in a row. - browser.pause(500); - } - - /** - * Deselect all charts in the "Included Charts" panel by clicking the - * "Select" button then clicking "No Charts". - */ - deselectAllIncludedCharts() { - this.waitForIncludedChartsPanelVisible(); - browser.click(this.selectors.reportEditor.includedCharts.toolbar.selectButton()); - browser.waitForVisible(this.selectors.reportEditor.includedCharts.toolbar.selectNoChartsButton()); - browser.click(this.selectors.reportEditor.includedCharts.toolbar.selectNoChartsButton()); - browser.waitForInvisible(this.selectors.reportEditor.includedCharts.toolbar.selectNoChartsButton(), 500); - // Ext.Button ignores clicks for 250ms after the menu is hidden so pause - // in case the menu is used multiple times in a row. - browser.pause(500); - } - - /** - * Invert the chart selection in the "Included Charts" panel by clicking the - * "Select" button then clicking "Invert Selection". - */ - invertIncludedChartsSelection() { - this.waitForIncludedChartsPanelVisible(); - browser.click(this.selectors.reportEditor.includedCharts.toolbar.selectButton()); - browser.waitForVisible(this.selectors.reportEditor.includedCharts.toolbar.invertSelectionButton()); - // Multiple buttons match the "Invert Selection" selector, but only one should be visible. - const visibleButtons = $$(this.selectors.myReports.toolbar.invertSelectionButton()).filter(button => button.isVisible()); - expect(visibleButtons.length, 'One "Invert Selection" button is visible').to.be.equal(1); - visibleButtons[0].click(); - browser.waitForInvisible(this.selectors.reportEditor.includedCharts.toolbar.invertSelectionButton(), 500); - // Ext.Button ignores clicks for 250ms after the menu is hidden so pause - // in case the menu is used multiple times in a row. - browser.pause(500); - } - - /** - * Click the "Edit Timeframe of Selected Charts" button in the "Included - * Charts" panel. - */ - editTimeframeOfSelectedCharts() { - browser.click(this.selectors.reportEditor.includedCharts.toolbar.editTimeframeButton()); - } - - /** - * Click the "Specific" radio button in the "Edit Chart Timeframe" window. - */ - selectSpecificChartTimeframe() { - this.waitForEditChartTimeframeWindowVisible(); - browser.click(this.selectors.editChartTimeframe.specificRadioButton()); - } - - /** - * Set the start date in the "Edit Chart Timeframe" window. - * - * @param {String} startDate - */ - setSpecificChartTimeframeStartDate(startDate) { - this.waitForEditChartTimeframeWindowVisible(); - browser.setValue(this.selectors.editChartTimeframe.startDateInput(), startDate); - } - - /** - * Set the end date in the "Edit Chart Timeframe" window. - * - * @param {String} endDate - */ - setSpecificChartTimeframeEndDate(endDate) { - this.waitForEditChartTimeframeWindowVisible(); - browser.setValue(this.selectors.editChartTimeframe.endDateInput(), endDate); - } - - /** - * Click the "Periodic" radio button in the "Edit Chart Timeframe" window - * and pick a duration. - * - * @param {String} duration Name of the periodic duration. - */ - selectPeriodicChartTimeframe(duration) { - this.waitForEditChartTimeframeWindowVisible(); - browser.click(this.selectors.editChartTimeframe.periodicRadioButton()); - browser.waitForVisible(this.selectors.editChartTimeframe.periodicInput()); - browser.click(this.selectors.editChartTimeframe.periodicInput()); - browser.waitForVisible(this.selectors.editChartTimeframe.periodicOption(duration)); - browser.click(this.selectors.editChartTimeframe.periodicOption(duration)); - browser.waitForInvisible(this.selectors.editChartTimeframe.periodicOption(duration), 500); - } - - /** - * Click the "Cancel" button in the "Edit Chart Timeframe" window. - */ - cancelEditTimeframeOfSelectedCharts() { - this.waitForEditChartTimeframeWindowVisible(); - browser.click(this.selectors.editChartTimeframe.cancelButton()); - } - - /** - * Click the "Update" button in the "Edit Chart Timeframe" window. - */ - confirmEditTimeframeOfSelectedCharts() { - this.waitForEditChartTimeframeWindowVisible(); - browser.click(this.selectors.editChartTimeframe.updateButton()); - } - - /** - * Get the error message displayed in the "Edit Chart Timeframe" window. - * - * This method must be called while the error message is still visible (or - * directly before the message appears) or it will fail. - * - * @return {String} The text of the error message. - */ - getEditChartTimeframeErrorMessage() { - browser.waitForVisible(this.selectors.editChartTimeframe.errorMessage()); - return browser.getText(this.selectors.editChartTimeframe.errorMessage()); - } - - /** - * Remove selected charts from the "Included Charts" panel by clicking the - * "Remove" button. - */ - removeSelectedIncludedCharts() { - this.waitForIncludedChartsPanelVisible(); - browser.click(this.selectors.reportEditor.includedCharts.toolbar.deleteButton()); - } - - /** - * Click the "No" button in the "Remove Selected Chart" window. - */ - cancelRemoveSelectedIncludedCharts() { - this.waitForRemoveSelectedChartsWindowVisible(); - browser.click(this.selectors.removeSelectedCharts.noButton()); - this.waitForRemoveSelectedChartsWindowNotVisible(); - } - - /** - * Click the "Yes" button in the "Remove Selected Chart" window. - */ - confirmRemoveSelectedIncludedCharts() { - this.waitForRemoveSelectedChartsWindowVisible(); - const chartCount = this.getIncludedCharts().length; - browser.click(this.selectors.removeSelectedCharts.yesButton()); - this.waitForRemoveSelectedChartsWindowNotVisible(); - // There is no visible indicator that the charts are being - // updated, so wait for the number of rows to change. - browser.waitUntil(() => chartCount !== this.getIncludedCharts().length, 2000, 'Expect number of charts to change'); - } - - /** - * Select all charts in the "Available Charts" panel by clicking the - * "Select" button then clicking "All Charts". - */ - selectAllAvailableCharts() { - this.waitForAvailableChartsPanelVisible(); - browser.click(this.selectors.availableCharts.toolbar.selectButton()); - browser.waitForVisible(this.selectors.availableCharts.toolbar.selectAllChartsButton()); - browser.click(this.selectors.availableCharts.toolbar.selectAllChartsButton()); - browser.waitForInvisible(this.selectors.availableCharts.toolbar.selectAllChartsButton(), 500); - // Ext.Button ignores clicks for 250ms after the menu is hidden so pause - // in case the menu is used multiple times in a row. - browser.pause(500); - } - - /** - * Deselect all charts in the "Available Charts" panel by clicking the - * "Select" button then clicking "No Charts". - */ - deselectAllAvailableCharts() { - this.waitForAvailableChartsPanelVisible(); - browser.click(this.selectors.availableCharts.toolbar.selectButton()); - browser.waitForVisible(this.selectors.availableCharts.toolbar.selectNoChartsButton()); - browser.click(this.selectors.availableCharts.toolbar.selectNoChartsButton()); - browser.waitForInvisible(this.selectors.availableCharts.toolbar.selectNoChartsButton(), 500); - // Ext.Button ignores clicks for 250ms after the menu is hidden so pause - // in case the menu is used multiple times in a row. - browser.pause(500); - } - - /** - * Invert the chart selection in the "Available Charts" panel by clicking - * the "Select" button then clicking "Invert Selection". - */ - invertAvailableChartsSelection() { - this.waitForAvailableChartsPanelVisible(); - browser.click(this.selectors.availableCharts.toolbar.selectButton()); - browser.waitForVisible(this.selectors.availableCharts.toolbar.invertSelectionButton()); - // Multiple buttons match the "Invert Selection" selector, but only one should be visible. - const visibleButtons = $$(this.selectors.myReports.toolbar.invertSelectionButton()).filter(button => button.isVisible()); - expect(visibleButtons.length, 'One "Invert Selection" button is visible').to.be.equal(1); - visibleButtons[0].click(); - browser.waitForInvisible(this.selectors.availableCharts.toolbar.invertSelectionButton(), 500); - // Ext.Button ignores clicks for 250ms after the menu is hidden so pause - // in case the menu is used multiple times in a row. - browser.pause(500); - } - - /** - * Delete selected charts from "Available Charts" by clicking the - * delete button. - * - * Does not confirm deletion of carts, that button must be clicked - * separately. - * - */ - deleteSelectedAvailableCharts() { - this.waitForAvailableChartsPanelVisible(); - browser.click(this.selectors.availableCharts.toolbar.deleteButton()); - } - - /** - * Click the "Yes" button in the "Delete Selected Charts" window. - */ - confirmDeleteSelectedAvailableCharts() { - this.waitForDeleteSelectedChartsWindowVisible(); - const chartCount = this.getAvailableCharts().length; - browser.click(this.selectors.deleteSelectedCharts.yesButton()); - // There is no visible indicator that the charts are being - // updated, so wait for the number of rows to change. - browser.waitUntil(() => chartCount !== this.getAvailableCharts().length, 2000, 'Expect number of charts to change'); - } - - /** - * Click the "No" button in the "Delete Selected Charts" window. - */ - cancelDeleteSelectedAvailableCharts() { - this.waitForDeleteSelectedChartsWindowVisible(); - browser.click(this.selectors.deleteSelectedCharts.noButton()); - } - - /** - * Add a chart to the report that is currently being edited. - * - * @param {Number} index The 0-based index of the chart in the list of - * available charts. - */ - addChartToReport(index) { - this.waitForAvailableChartsPanelVisible(); - this.waitForIncludedChartsPanelVisible(); - const charts = this.getAvailableCharts(); - const includedChartCountBefore = this.getIncludedCharts().length; - expect(index, 'Index is valid').to.be.below(charts.length); - browser.dragAndDrop(this.selectors.availableCharts.chartList.rows() + `[${index + 1}]`, this.selectors.reportEditor.includedCharts.chartList.panel()); - browser.waitForExist(this.selectors.reportEditor.includedCharts.chartList.rows() + `[${includedChartCountBefore + 1}]`); - } - - getCharts(user, report_template_index, options) { - var charts = expected[user].report_templates[report_template_index].charts; - charts.forEach(function (chart, i) { - if (chart.startDate in options) { - charts[i].startDate = options[chart.startDate]; - } - if (chart.endDate in options) { - charts[i].endDate = options[chart.endDate]; - } - }); - return charts; - } -} - -module.exports = new ReportGenerator(); diff --git a/tests/ui/test/specs/xdmod/usageTab.js b/tests/ui/test/specs/xdmod/usageTab.js deleted file mode 100644 index 2b2425b7be..0000000000 --- a/tests/ui/test/specs/xdmod/usageTab.js +++ /dev/null @@ -1,94 +0,0 @@ -var logIn = require('./loginPage.page.js'); -var usg = require('./usageTab.page.js'); -var expected = global.testHelpers.artifacts.getArtifact('usage'); -var XDMOD_REALMS = process.env.XDMOD_REALMS; -describe('Usage', function () { - logIn.login('centerdirector'); - var baselineDate = { - start: '2016-12-25', - end: '2017-01-02' - }; - // TODO: Add tests for storage and cloud realms - if (XDMOD_REALMS.includes('jobs')) { - describe('(Center Director)', function xdmod() { - it('Select "Usage" tab', function () { - usg.selectTab(); - browser.waitForChart(); - browser.waitForExist(usg.chartByTitle(expected.centerdirector.default_chart_title, true)); - - // by refreshing we ensure that there are not stale legend-item elements - // on the page. - browser.refresh(); - browser.waitForExist(usg.chartByTitle(expected.centerdirector.default_chart_title, true)); - }); - it('Set a known start and end date', function meSetStartEnd() { - usg.setStartDate(baselineDate.start); - usg.setEndDate(baselineDate.end); - usg.refresh(); - browser.waitForExist(usg.chartXAxisLabelByName(baselineDate.start)); - }); - it('Select Job Size Min', function () { - browser.waitForLoadedThenClick(usg.treeNodeByPath('Jobs Summary', 'Job Size: Min')); - browser.waitForExist(usg.chartByTitle('Job Size: Min (Core Count)', true)); - usg.checkLegendText(expected.centerdirector.legend); - - // Check to make sure that the 'Std Err' display menu items are disabled. - browser.waitForLoadedThenClick(usg.toolbarButtonByText('Display')); - ['Std Err Bars', 'Std Err Labels'].forEach(function (menuLabel) { - browser.waitForVisible(usg.displayMenuItemByText(menuLabel)); - expect(usg.toolbarMenuItemIsEnabled(menuLabel)).to.equal(false); - }); - }); - it('View CPU Hours by System Username', function () { - browser.waitForLoadedThenClick(usg.unfoldTreeNodeByName('Jobs Summary')); - browser.waitForLoadedThenClick(usg.unfoldTreeNodeByName('Jobs by System Username')); - browser.waitUntilAnimEndAndClick(usg.treeNodeByPath('Jobs by System Username', 'CPU Hours: Per Job')); - browser.waitForExist(usg.chartByTitle('CPU Hours: Per Job: by System Username', true)); - }); - it('View CPU Hours: Per Job', function () { - browser.waitForLoadedThenClick(usg.unfoldTreeNodeByName('Jobs Summary', 'CPU Hours: Per Job')); - browser.waitForExist(usg.chartByTitle('CPU Hours: Per Job', true)); - - // Check to make sure that the 'Std Err' display menu items are disabled. - browser.waitForLoadedThenClick(usg.toolbarButtonByText('Display')); - ['Std Err Bars', 'Std Err Labels'].forEach(function (menuLabel) { - browser.waitForVisible(usg.displayMenuItemByText(menuLabel)); - expect(usg.toolbarMenuItemIsEnabled(menuLabel)).to.equal(true); - }); - }); - }); - logIn.logout(); - describe('(Public User)', function () { - it('Selected', function () { - usg.selectTab(); - browser.waitForChart(); - browser.waitForExist(usg.chartByTitle(expected.centerdirector.default_chart_title, true)); - - // by refreshing we ensure that there are not stale legend-item elements - // on the page. - browser.refresh(); - browser.waitForExist(usg.chartByTitle(expected.centerdirector.default_chart_title, true)); - }); - it('Set a known start and end date', function meSetStartEnd() { - usg.setStartDate(baselineDate.start); - usg.setEndDate(baselineDate.end); - usg.refresh(); - browser.waitForExist(usg.chartXAxisLabelByName(baselineDate.start)); - }); - it('View Job Size Min', function () { - browser.waitForLoadedThenClick(usg.treeNodeByPath('Jobs Summary', 'Job Size: Min')); - browser.waitForExist(usg.chartByTitle('Job Size: Min (Core Count)', true)); - usg.checkLegendText(expected.centerdirector.legend); - }); - it('Confirm System Username is not selectable', function () { - browser.waitForLoadedThenClick(usg.unfoldTreeNodeByName('Jobs Summary')); - browser.waitUntilAnimEndAndClick(usg.topTreeNodeByName('Jobs by System Username')); - // The click should do nothing and the chart should remain unchanged - // since nothing should happen, there is no action to wait for, so we - // have to pause for a bit - browser.pause(500); - browser.waitForExist(usg.chartByTitle('Job Size: Min (Core Count)', true)); - }); - }); - } -}); diff --git a/tests/ui/test/specs/xdmod/usageTab.page.js b/tests/ui/test/specs/xdmod/usageTab.page.js deleted file mode 100644 index bfb65309ac..0000000000 --- a/tests/ui/test/specs/xdmod/usageTab.page.js +++ /dev/null @@ -1,212 +0,0 @@ -/* eslint-env node, es6 */ -var xdmod = require('./xdmod.page.js'); - -class Usage { - constructor() { - const self = this; - this.tab = '#main_tab_panel__tg_usage'; - this.startField = '#tg_usage input[id^=start_field_ext]'; - this.endField = '#tg_usage input[id^=end_field_ext]'; - this.refreshButton = '//div[@id="tg_usage"]//button[text()="Refresh"]/ancestor::node()[5]'; - this.toolbar = { - exportButton: '//div[@id="tg_usage"]//button[text()="Export"]/ancestor::node()[5]' - }; - this.panel = '//div[@id="tg_usage"]'; - this.availableForReportCheckbox = '//div[@id="tg_usage"]//label[text()="Available For Report"]/parent::node()/input[@type="checkbox"]'; - this.mask = '.ext-el-mask'; - this.topTreeNodeByName = function (name) { - return '//div[@id="tg_usage"]//div[@class="x-tree-root-node"]/li/div[contains(@class,"x-tree-node-el")]//span[text() = "' + name + '"]'; - }; - this.treeNodeByPath = function (topname, childname) { - return module.exports.topTreeNodeByName(topname) + '/ancestor::node()[3]//span[text() = "' + childname + '"]'; - }; - this.unfoldTreeNodeByName = function (name) { - return module.exports.topTreeNodeByName(name) + '/ancestor::node()[2]/img[contains(@class,"x-tree-ec-icon")]'; - }; - this.chart = '//div[@id="tg_usage"]//div[contains(@class, "plot-container")]//*[local-name() = "svg"][1]'; - this.chart0 = '//div[@id="tg_usage"]//div[contains(@class, "plot-container")]//*[local-name() = "svg"]'; - this.chartByTitle = function (title, zero = false) { - const chart = zero ? self.chart0 : self.chart; - return chart + '/*[name()="g" and contains(@class, "infolayer")]//*[name()="g" and contains(@class, "annotation") and @data-index="0"]//*[local-name() = "text" and contains(text(),"' + title + '")]'; - }; - this.chartXAxisLabelByName = function (name) { - return '(' + module.exports.chart + '//*[name()="g" and @class="xaxislayer-below"]/*[name()="g" and @class="xtick"])[1]'; - }; - this.legendText = `${self.chart0}//*[name()="g" and contains(@class, "infolayer")]/*[name()="g" and @class="legend"]`; - this.durationButton = this.panel + '//button[contains(@class,"custom_date")]'; - this.durationMenu = '//div[contains(@class,"x-menu-floating")]'; - this.durationMenuItem = name => `${this.durationMenu}//li/a[./span[text()="${name}"]]`; - this.toolbarButtonByText = function (text) { - return `//div[contains(@class, "x-toolbar")]//button[contains(text(), "${text}")]`; - }; - this.displayMenuItemByText = function (text) { - return `//div[@id='chart_config_menu_chart_toolbar_tg_usage']//span[contains(text(), '${text}')]//ancestor::li[contains(@class, 'x-menu-list-item')]`; - }; - } - - checkLegendText(text) { - browser.waitForChart(); - browser.waitForExist(this.legendText, 50000); - expect(browser.getText(this.legendText)).to.equal(text); - } - - /** - * Select the "Usage" tab by clicking it. - */ - selectTab() { - xdmod.selectTab('tg_usage'); - browser.waitForVisible(this.chart); - browser.waitForAllInvisible(this.mask); - } - - /** - * Select a duration from the list of preset options. - * - * @param {String} name The name of the duration preset. - */ - selectDuration(name) { - browser.click(this.durationButton); - browser.waitForVisible(this.durationMenu); - browser.click(this.durationMenuItem(name)); - browser.waitForAllInvisible(this.mask); - - // The chart automatically refreshes after a new duration is - // selected, but the menu remains open. Clicking the refresh - // button will close the menu. - this.refresh(); - } - - /** - * Set the start date. - * - * @param {String} date Start date. - */ - setStartDate(date) { - browser.setValue(this.startField, date); - } - - /** - * Set the end date. - * - * @param {String} date End date. - */ - setEndDate(date) { - browser.setValue(this.endField, date); - } - - /** - * Refresh current chart by clicking the "Refresh" button. - */ - refresh() { - browser.click(this.refreshButton); - browser.waitForAllInvisible(this.mask); - } - - /** - * Make the current chart available for use in the report generator by - * clicking the "Available for Report" checkbox. - * - * Preconditions: - * - The "Available for Report" checkbox is visible, enabled and not checked. - * - * Postconditions: - * - The "Available for Report" checkbox is checked. - */ - makeCurrentChartAvailableForReport() { - const checkbox = $(this.availableForReportCheckbox); - expect(checkbox.isVisible(), '"Available for Report" checkbox is visible').to.be.true; - expect(checkbox.isEnabled(), '"Available for Report" checkbox is enabled').to.be.true; - expect(checkbox.isSelected(), '"Available for Report" checkbox is not checked').to.be.false; - checkbox.click(); - expect(checkbox.isSelected(), '"Available for Report" checkbox is checked').to.be.true; - } - - /** - * Remove the current chart from the report generator by clicking the - * "Available for Report" checkbox. - * - * Preconditions: - * - The "Available for Report" checkbox is visible, enabled and checked. - * - * Postconditions: - * - The "Available for Report" checkbox is not checked. - */ - makeCurrentChartUnavailableForReport() { - const checkbox = $(this.availableForReportCheckbox); - expect(checkbox.isVisible(), '"Available for Report" checkbox is visible').to.be.true; - expect(checkbox.isEnabled(), '"Available for Report" checkbox is enabled').to.be.true; - expect(checkbox.isSelected(), '"Available for Report" checkbox is checked').to.be.true; - checkbox.click(); - expect(checkbox.isSelected(), '"Available for Report" checkbox is not checked').to.be.false; - } - - /** - * Check if a top-level tree node is expanded. - * - * @param {String} name The name of the tree node. - * - * @return {Boolean} True if the node is expanded. - */ - isTreeNodeExpanded(name) { - return $(this.unfoldTreeNodeByName(name)).getAttribute('class').match(/[$ ]x-tree-node-plus[^ ]/) === null; - } - - /** - * Expand a top-level node in the metrics tree by clicking the - * plus/minus icon. - * - * @param {String} name The name of the tree node. - */ - expandTreeNode(name) { - expect(this.isTreeNodeExpanded(name), 'Tree node is collapsed').to.be.false; - browser.waitForLoadedThenClick(this.unfoldTreeNodeByName(name)); - } - - /** - * Collapse a top-level node in the metrics tree by clicking the - * plus/minus icon. - * - * @param {String} name The name of the tree node. - */ - collapseTreeNode(name) { - expect(this.isTreeNodeExpanded(name), 'Tree node is expanded').to.be.true; - browser.waitForLoadedThenClick(this.unfoldTreeNodeByName(name)); - } - - /** - * Select a top-level tree node. - * - * @param {String} name The name of the tree node. - */ - selectTreeNode(name) { - browser.waitForLoadedThenClick(this.topTreeNodeByName(name)); - browser.waitForAllInvisible(this.mask); - } - - /** - * Select a child node in the metrics tree by clicking. - * - * @param {String} topName The name of the top-level tree node. - * @param {String} childName The name of the child tree node. - */ - selectChildTreeNode(topName, childName) { - if (!this.isTreeNodeExpanded(topName)) { - this.expandTreeNode(topName); - } - browser.waitUntilAnimEndAndClick(this.treeNodeByPath(topName, childName)); - browser.waitForAllInvisible(this.mask); - } - - /** - * Check if the menu item element that contains the text in `display` is enabled. - * - * @param display - * @returns {boolean} - */ - toolbarMenuItemIsEnabled(display) { - var item = this.displayMenuItemByText(display); - browser.waitForVisible(item); - return !($(item).getAttribute('class').includes('x-item-disabled')); - } -} -module.exports = new Usage(); diff --git a/tests/ui/test/specs/xdmod/xdmod.page.js b/tests/ui/test/specs/xdmod/xdmod.page.js deleted file mode 100644 index d5bbbb4048..0000000000 --- a/tests/ui/test/specs/xdmod/xdmod.page.js +++ /dev/null @@ -1,59 +0,0 @@ -class XDMoD { - constructor() { - this.selectors = { - mask: '.ext-el-mask', - exportDialog: { - window: '//body/div[contains(@class,"x-window")]//span[contains(@class, "x-window-header-text") and text() = "Export"]/ancestor::node()[5]', - cancelButton: function () { - return module.exports.selectors.exportDialog.window + '//button[contains(text(), "Cancel")]'; - }, - formElement: function (formElementName) { - return module.exports.selectors.exportDialog.window + '//input[@name="' + formElementName + '"]'; - }, - dropDown: function (formElementName) { - return module.exports.selectors.exportDialog.formElement(formElementName) + '/../img[contains(@class,"x-form-arrow-trigger")]'; - }, - formatDropdown: function () { - return module.exports.selectors.exportDialog.dropDown('format_type'); - }, - showTitleCheckbox: function () { - return module.exports.selectors.exportDialog.formElement('show_title'); - }, - imageSizeDropdown: function () { - return module.exports.selectors.exportDialog.dropDown('image_size'); - }, - widthInput: function () { - return module.exports.selectors.exportDialog.formElement('width_inches'); - }, - heightInput: function () { - return module.exports.selectors.exportDialog.formElement('height_inches'); - }, - fontInput: function () { - return module.exports.selectors.exportDialog.formElement('font_pt'); - }, - comboList: '//div[contains(@class, "x-combo-list")]', - comboListItems: '//div[contains(@style, "visibility: visible")]//div[contains(@class, "x-combo-list-item")]', - comboListItemByName: function (name) { - return '//div[contains(@style, "visibility: visible")]//div[contains(@class, "x-combo-list-item") and contains(text(), "' + name + '")]'; - } - } - }; - } - selectTab(tabId) { - var tab = '#main_tab_panel__' + tabId; - var panel = '//div[@id="' + tabId + '"]'; - - browser.waitForVisible(tab); - for (let i = 0; i < 100; i++) { - try { - browser.click(tab); - break; - } catch (e) { - browser.waitForAllInvisible(this.selectors.mask); - } - } - browser.waitForVisible(panel); - browser.waitForAllInvisible(this.selectors.mask); - } -} -module.exports = new XDMoD(); diff --git a/tests/ui/wdio.conf.js b/tests/ui/wdio.conf.js deleted file mode 100644 index 15722255ea..0000000000 --- a/tests/ui/wdio.conf.js +++ /dev/null @@ -1,268 +0,0 @@ -/** - * If you want to add other platforms use the following: - * https://wiki.saucelabs.com/display/DOCS/Platform+Configurator - * put them into variable and then add them to the proper `capabilities` - * variable - */ -const crypto = require('crypto'); -const fs = require('fs'); - -const screenshotDir = '/tmp/screenshots'; - -var HeadlessChrome = { - acceptInsecureCerts: true, - browserName: 'chrome', - chromeOptions: { - args: [ - '--no-sandbox', - '--headless', - '--no-gpu', - 'incognito', - 'disable-extensions', - 'start-maximized', - 'window-size=2560,1600' - ] - } -}; -var Chrome = { - acceptInsecureCerts: true, - browserName: 'chrome', - version: '87.0', - screenResolution: '2560x1600', - chromeOptions: { - args: [ - 'incognito', - 'disable-extensions', - 'start-maximized', - 'window-size=2560,1600' - ] - } -}; - -var secrets = require('../ci/testing.json'); -secrets.url = process.env.TEST_URL ? process.env.TEST_URL : secrets.url; -var excludes = [ - './test/**/*.page.js' -]; -if (!process.env.SSO) { - excludes.push('./test/specs/xdmod/SSOLogin.js'); -} -var capabilities = [Chrome]; -var services = ['selenium-standalone']; -var port = 4444; -var path = '/wd/hub'; -var reporters = ['spec']; -var reporterOptions = {}; - -var user = ''; -var key = ''; - -if (process.env.WDIO_MODE === 'headless') { - capabilities = [HeadlessChrome]; - services = ['chromedriver']; - port = 9515; - path = '/'; -} else if (process.env.SAUCE_USER && process.env.SAUCE_KEY) { - user = process.env.SAUCE_USER; - key = process.env.SAUCE_KEY; - services = ['sauce']; - capabilities = [Chrome]; -} -if (process.env.JUNIT_OUTDIR) { - reporters.push('junit'); - reporterOptions.junit = { - outputDir: process.env.JUNIT_OUTDIR, - outputFileFormat: { - single: function (config) { - if (process.env.SSO) { - return 'xdmod-ui-sso.xml'; - } - return 'xdmod-ui.xml'; - } - } - }; -} - -exports.config = { - deprecationWarnings: false, - user: user, - key: key, - // ================== - // Specify Test Files - // ================== - // Define which test specs should run. The pattern is relative to the directory - // from which `wdio` was called. Notice that, if you are calling `wdio` from an - // NPM script (see https://docs.npmjs.com/cli/run-script) then the current working - // directory is where your package.json resides, so `wdio` will be called from there. - // - specs: [ - './test/specs/**/*.js', - '../../open_xdmod/modules/*/tests/automated_tests/test/specs/*.js' - ], - - // Patterns to exclude. - exclude: excludes, - // - // ============ - // Capabilities - // ============ - // Define your capabilities here. WebdriverIO can run multiple capabilties at the same - // time. Depending on the number of capabilities, WebdriverIO launches several test - // sessions. Within your capabilities you can overwrite the spec and exclude option in - // order to group specific specs to a specific capability. - // - // If you have trouble getting all important capabilities together, check out the - // Sauce Labs platform configurator - a great tool to configure your capabilities: - // https://docs.saucelabs.com/reference/platforms-configurator - // - // **NOTE** if no browserName is specified webdriverio defaults to firefox - // Webdriver.io has the ability to run multiple browsers at once - // uncomment the different objects to enable each browser - // uncomment multiple objects to enable multiple at the same time - // Please bear in mind that if the tests are manipulating saved state on the server - // then running multiple logins concurrently could cause problems. - - // Define how many instances should be started at the same time. Let's - // say you have 3 different capabilities (Chrome, Firefox, and Safari) - // and you have set maxInstances to 1, wdio will spawn 3 processes. - // Therefore, if you have 10 spec files and you set maxInstances to 10; - // all spec files will get tested at the same time and 30 processes will - // get spawned. The property handles how many capabilities from the same - // test should run tests. - maxInstances: 1, - - capabilities: capabilities, - // - // =================== - // Test Configurations - // =================== - // Define all options that are relevant for the WebdriverIO instance here - // - - // By default WebdriverIO commands are executed in a synchronous way using - // the wdio-sync package. If you still want to run your tests in an async way - // e.g. using promises you can set the sync option to false. - sync: true, - // Level of logging verbosity. - logLevel: 'silent', - // - // Enables colors for log output. - coloredLogs: true, - // - // Saves a screenshot to a given path if a command fails. - // Comment out to disable feature. - // screenshotPath: './errorShots/', - // - // Set a base URL in order to shorten url command calls. If your url parameter starts - // with '/', the base url gets prepended. - baseUrl: secrets.url, - // - // Default timeout for all waitForXXX commands. - waitforTimeout: 10000, - - port: port, - - path: path, - - // - // Initialize the browser instance with a WebdriverIO plugin. The object should have the - // plugin name as key and the desired plugin options as property. Make sure you have - // the plugin installed before running any tests. The following plugins are currently - // available: - // WebdriverCSS: https://github.com/webdriverio/webdrivercss - // WebdriverRTC: https://github.com/webdriverio/webdriverrtc - // Browserevent: https://github.com/webdriverio/browserevent - // plugins: { - // webdrivercss: { - // screenshotRoot: 'my-shots', - // failedComparisonsRoot: 'diffs', - // misMatchTolerance: 0.05, - // screenWidth: [320,480,640,1024] - // }, - // webdriverrtc: {}, - // browserevent: {} - // }, - // - services: services, - // Framework you want to run your specs with. - // The following are supported: mocha, jasmine and cucumber - // see also: http://webdriver.io/guide/testrunner/frameworks.html - // - // Make sure you have the node package for the specific framework installed before running - // any tests. If not please install the following package: - // Mocha: `$ npm install mocha` - // Jasmine: `$ npm install jasmine` - // Cucumber: `$ npm install cucumber` - framework: 'mocha', - // - // Test reporter for stdout. - // The following are supported: dot (default), spec and xunit - // see also: http://webdriver.io/guide/testrunner/reporters.html - reporters: reporters, - - reporterOptions: reporterOptions, - - // - // Options to be passed to Mocha. - // See the full list at http://mochajs.org/ - mochaOpts: { - ui: 'bdd', - timeout: 100000, - bail: true - }, - // - // ===== - // Hooks - // ===== - // Run functions before or after the test. If one of them returns with a promise, WebdriverIO - // will wait until that promise got resolved to continue. - // - // Gets executed before all workers get launched. - onPrepare: function onPrepare() { - // do something - }, - // - // Gets executed before test execution begins. At this point you will have access to all global - // variables like `browser`. It is the perfect place to define custom commands. - before: function before() { - // eslint-disable-next-line global-require - global.testHelpers = require('./test/helpers'); - // eslint-disable-next-line global-require - var webdriverHelpers = require('./webdriverHelpers'); - for (var extension in webdriverHelpers) { - if (webdriverHelpers.hasOwnProperty(extension) && typeof webdriverHelpers[extension] === 'function') { - browser.addCommand(extension, webdriverHelpers[extension].bind(browser)); - } else { - process.stderr.write('Not Adding: ' + extension + typeof webdriverHelpers[extension]); - } - } - // eslint-disable-next-line global-require - var chai = require('chai'); - // eslint-disable-next-line no-global-assign - expect = chai.expect; - }, - // - // Gets executed after all tests are done. You still have access to all global variables from - // the test. - after: function after(/* failures, pid */) { - // do something - }, - - /** - * Get's executed after each test. - */ - afterTest: function afterTest(test, context) { - if (!test.passed) { - let hash = crypto.createHash('md5').update(test.fullTitle).digest('hex'); - let filename = `${screenshotDir}/${hash}`; - browser.saveScreenshot(`${filename}.png`); - fs.writeFileSync(`${filename}.json`, JSON.stringify(test)); - } - }, - // - // Gets executed after all workers got shut down and the process is about to exit. It is not - // possible to defer the end of the process using a promise. - onComplete: function onComplete() { - // do something - } -}; diff --git a/tests/ui/webdriverHelpers/getChartSeriesPointLocation.js b/tests/ui/webdriverHelpers/getChartSeriesPointLocation.js deleted file mode 100644 index 9af80b4c21..0000000000 --- a/tests/ui/webdriverHelpers/getChartSeriesPointLocation.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Attempt to retrieve the location ( top, left ) relative to the provided - * containerId of the point in the series ( pointIndex, seriesIndex ). A top - * and left offset can also be supplied. - * - * @param {String} containerId - the 'id' property of the container that - * contains the Highcharts instance. - * NOTE: this is an Ext id so no '#'. - * @param {Number} [seriesIndex=0] - the index of the series that contains - * the point to be located. - * @param {Number} [pointIndex=0] - the index of the point to be located. - * - * @return {Object} in the form: - * { - * top: , - * left: - * } - **/ -module.exports = function getChartSeriesPointLocation(containerId, seriesIndex, pointIndex) { - var thisSeriesIndex = (seriesIndex !== undefined) ? seriesIndex : 0; - var thisPointIndex = (pointIndex !== undefined) ? pointIndex : 0; - return this.execute(function (innerContainerId, innerSeriesIndex, innerPointIndex) { - // TODO: Fix this withOut having to use EXT if Possible - // eslint-disable-next-line no-undef - var cmp = Ext.getCmp(innerContainerId); - var axes = cmp.chart.axes; - var xAxis = axes[0]; - var yAxis = axes[1]; - var series = cmp.chart.series[innerSeriesIndex]; - var point = series.data[innerPointIndex]; - var top = yAxis.toPixels(point.options.y); - var left = xAxis.toPixels(point.options.x); - - return { - top: Number(top.toFixed(0)), - left: Number(left.toFixed(0)) - }; - }, containerId, thisSeriesIndex, thisPointIndex); -}; diff --git a/tests/ui/webdriverHelpers/index.js b/tests/ui/webdriverHelpers/index.js deleted file mode 100644 index f7c749d520..0000000000 --- a/tests/ui/webdriverHelpers/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('require-dir')(); diff --git a/tests/ui/webdriverHelpers/waitAndClick.js b/tests/ui/webdriverHelpers/waitAndClick.js deleted file mode 100644 index 978e5b2af0..0000000000 --- a/tests/ui/webdriverHelpers/waitAndClick.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Wait for selector to be visible then click on it - * - * @param {string} selector - element to click on - * @param {Number} [ms=5000] - Milliseconds to wait for element to be visible - * - * @uses commands/waitForVisible, commands/click - */ -module.exports = function waitAndClick(selector, ms) { - var thisMs = ms || 5000; - - this.waitForVisible(selector, thisMs); - return this.click(selector); -}; diff --git a/tests/ui/webdriverHelpers/waitAndSet.js b/tests/ui/webdriverHelpers/waitAndSet.js deleted file mode 100644 index e111730a36..0000000000 --- a/tests/ui/webdriverHelpers/waitAndSet.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Wait for the element identified by the provided selector to be visible and - * then set it's value to the provided value. To more accurately simulate a user - * setting a value the click argument defaults to true. Also, as a simple sanity - * check, validate is also defaulted to true with a default validate function - * that just checks that the value that the selector was set to is what is - * retrieved via a getValue. - * - * @param {String} selector - will be used to identify the element. - * @param {*} value - the value to use while performing a setValue - * on the now visible element. - * @param {Number} [ms=5000] - the amount of time to wait for the selector - * to become visible. - * @param {Boolean} [click=true] - A value of true indicates that a click - * event will be executed before the setValue - * for the provided selector. A value of false - * indicates that the click event should be - * skipped. - * @param {Boolean} [validate=true] - validate that the value the selector - * is set to matches the value that is - * retrieved after the setValue is - * performed. - * @param {Function} [validateFunc=function(expected, actual){ - * expect(actual).to.be.equal(expected); - * } - * ] - The function that will be used to validate the value retrieved - * from the selector if the validate argument is true. - **/ -// eslint-disable-next-line consistent-return -module.exports = function waitAndSet(selector, value, ms, click, validate) { - var timeOut = ms || 5000; - var thisClick = click !== undefined ? click : true; - var thisValidate = validate !== undefined ? validate : true; - - if (thisClick === true && thisValidate === true) { - this.waitForVisible(selector, timeOut); - this.click(selector); - this.setValue(selector, value); - return this.getValue(selector); - } else if (thisClick === true && thisValidate === false) { - this.waitForVisible(selector, timeOut); - this.click(selector); - return this.setValue(selector, value); - } else if (thisClick === false && thisValidate === true) { - this.waitForVisible(selector, timeOut); - this.setValue(selector, value); - return this.getValue(selector); - } else if (thisClick === false && thisValidate === false) { - this.waitForVisible(selector, timeOut); - return this.setValue(selector, value); - } -}; diff --git a/tests/ui/webdriverHelpers/waitForAllInvisible.js b/tests/ui/webdriverHelpers/waitForAllInvisible.js deleted file mode 100644 index 70e7e1b9c3..0000000000 --- a/tests/ui/webdriverHelpers/waitForAllInvisible.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Wait for all elements that match a selector to be invisible. - * - * @param {string} selector - elements to check - * @param {Number} [ms=18000] - Milliseconds to wait for elements to be invisible - * - * @uses commands/waitForVisible - */ - -module.exports = function waitForAllInvisible(selector, ms) { - var timeOut = ms || 18000; - browser.waitUntil(() => $$(selector).filter(el => el.isVisible()).length === 0, timeOut); -}; diff --git a/tests/ui/webdriverHelpers/waitForChart.js b/tests/ui/webdriverHelpers/waitForChart.js deleted file mode 100644 index ea911858eb..0000000000 --- a/tests/ui/webdriverHelpers/waitForChart.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Wait for chart to load - * TODO: Find a better way to determine if chart is rendered - * - * @param {Number} [ms=9000] - Milliseconds to wait for chat to be visible - * - * @uses commands/waitForVisible, protocol/pause - */ - -module.exports = function waitForChart(ms) { - var timeOut = ms || 9000; - - return this.waitForVisible('.ext-el-mask-msg', timeOut, true); -}; diff --git a/tests/ui/webdriverHelpers/waitForInvisible.js b/tests/ui/webdriverHelpers/waitForInvisible.js deleted file mode 100644 index ef7e84dc1d..0000000000 --- a/tests/ui/webdriverHelpers/waitForInvisible.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Wait for selector to be invisible - * - * @param {string} selector - element to check - * @param {Number} [ms=5000] - Milliseconds to wait for element to be invisible - * - * @uses commands/waitForVisible - */ - -module.exports = function waitForInvisible(selector, ms) { - var timeOut = ms || 5000; - return this.waitForVisible(selector, timeOut, true); -}; diff --git a/tests/ui/webdriverHelpers/waitForLoadedThenClick.js b/tests/ui/webdriverHelpers/waitForLoadedThenClick.js deleted file mode 100644 index 9e800da9be..0000000000 --- a/tests/ui/webdriverHelpers/waitForLoadedThenClick.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Wait for loadingMask to not exist and selector to exist then click on selector - * - * @param {string} selector - element to click on - * @param {Number} [maskMs=9000] - Milliseconds to wait for mask to not exist - * - * @uses commands/waitForVisible, helpers/waitAndClick - */ -module.exports = function waitForLoadedThenClick(selector, maskMs) { - var maskTimeOut = maskMs || 9000; - browser.waitForVisible('.ext-el-mask-msg', maskTimeOut, true); - return browser.click(selector); -}; diff --git a/tests/ui/webdriverHelpers/waitUntilAnimEnd.js b/tests/ui/webdriverHelpers/waitUntilAnimEnd.js deleted file mode 100644 index 4603d65646..0000000000 --- a/tests/ui/webdriverHelpers/waitUntilAnimEnd.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Wait for selector to stop animating, where animation is defined - * as moving in the view. - * - * @param {string} selector - element to click on - * @param {Number} [ms=9000] - Milliseconds to wait for element to be visible - * @param {Number} [interval=250] - interval between element location checks - * - * @uses commands/waitUntil commands/getLocationInView - */ - -module.exports = function waitUntilAnimEnd(selector, ms, interval) { - var timeOut = ms || 9000; - var checkInterval = interval || 250; - - var initialLoc = { x: -1, y: -1 }; - - this.waitForVisible(selector, timeOut); - return this.waitUntil(function () { - var loc = this.getLocationInView(selector); - if (loc.x === initialLoc.x && loc.y === initialLoc.y) { - return true; - } - - initialLoc = loc; - - return false; - }, timeOut, checkInterval); -}; diff --git a/tests/ui/webdriverHelpers/waitUntilAnimEndAndClick.js b/tests/ui/webdriverHelpers/waitUntilAnimEndAndClick.js deleted file mode 100644 index 0e38270a39..0000000000 --- a/tests/ui/webdriverHelpers/waitUntilAnimEndAndClick.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Wait for selector to stop animating then click on it - * - * @param {string} selector - element to click on - * @param {string} [button=left] which mouse key to use ["left", "middle", "right"] - * @param {Number} [ms=9000] - Milliseconds to wait for element to stop moving - * @param {Number} [interval=250] - interval between element location checks - * - * @uses waitUntilAnimEnd commands/buttonPress - */ - -module.exports = function waitUntilAnimEndAndClick(selector, button, ms, interval) { - var clickButton = button || 'left'; - var timeOut = ms || 9000; - var checkInterval = interval || 250; - this.waitUntilAnimEnd(selector, timeOut, checkInterval); - this.moveToObject(selector); - return this.buttonPress(clickButton); -}; diff --git a/tests/ui/webdriverHelpers/waitUntilChartLoadsBySeries.js b/tests/ui/webdriverHelpers/waitUntilChartLoadsBySeries.js deleted file mode 100644 index 2971b606c9..0000000000 --- a/tests/ui/webdriverHelpers/waitUntilChartLoadsBySeries.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Wait for the Highchart that contains the provided series to load. - * - * @param {String} series - name of the series to be used to further - * identify the Highcharts instance. - * @param {Number} [ms=9000] - Milliseconds to wait for the element to - **/ -module.exports = function waitUntilChartLoadsBySeries(series, ms) { - var timeOut = ms || 9000; - return this.waitForVisible( - "//*[local-name() = 'svg']//*[name() = 'g' and contains(@class, 'g-ytitle')]//*[local-name() = 'tspan' and text()[contains(., '" + series + "')]]", - timeOut - ); -}; diff --git a/tests/ui/webdriverHelpers/waitUntilNotExist.js b/tests/ui/webdriverHelpers/waitUntilNotExist.js deleted file mode 100644 index 1ecb98629c..0000000000 --- a/tests/ui/webdriverHelpers/waitUntilNotExist.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Wait for selector to be visible then click on it - * - * @param {string} selector - element to click on - * @param {Number} [ms=9000] - Milliseconds to wait for element to be visible - * - * @uses commands/waitForExist - */ - -module.exports = function waitUntilNotExist(selector, ms) { - var timeOut = ms || 9000; - return this.waitForExist(selector, timeOut, true); -}; diff --git a/user_manual_builder/setup.sh b/user_manual_builder/setup.sh index 5bdcc9b9ac..80428b30c6 100755 --- a/user_manual_builder/setup.sh +++ b/user_manual_builder/setup.sh @@ -3,6 +3,8 @@ BUILDDIR=user_manual_builder BUILDENV=$BUILDDIR/sphinx_venv +rm -rf $BUILDENV + python3 -m venv $BUILDENV source $BUILDENV/bin/activate