├── .github └── workflows │ └── docker-compose-ci.yml ├── .gitignore ├── README.md ├── chamilo-php7 ├── Dockerfile └── files │ ├── chamilo.conf │ └── security.conf ├── docker-compose-selenium.yml ├── docker-compose.yml └── selenium-with-jest ├── .gitignore ├── __tests__ └── grid.tests.js ├── babel.config.js ├── helpers └── helpers.js ├── package-lock.json ├── package.json └── wait-for-selenium-grid.sh /.github/workflows/docker-compose-ci.yml: -------------------------------------------------------------------------------- 1 | name: Docker-compose CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [12.x, 14.x] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v1 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - name: Build and run docker-compose with selenium grid 26 | run: docker-compose -f docker-compose.yml -f docker-compose-selenium.yml up -d 27 | - run: npm install 28 | working-directory: ./selenium-with-jest 29 | - name: Run tests 30 | run: ./wait-for-selenium-grid.sh npm test 31 | working-directory: ./selenium-with-jest 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dccache 2 | 3 | # Created by https://www.toptal.com/developers/gitignore/api/macos,windows 4 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,windows 5 | 6 | ### macOS ### 7 | # General 8 | .DS_Store 9 | .AppleDouble 10 | .LSOverride 11 | 12 | # Icon must end with two \r 13 | Icon 14 | 15 | # Thumbnails 16 | ._* 17 | 18 | # Files that might appear in the root of a volume 19 | .DocumentRevisions-V100 20 | .fseventsd 21 | .Spotlight-V100 22 | .TemporaryItems 23 | .Trashes 24 | .VolumeIcon.icns 25 | .com.apple.timemachine.donotpresent 26 | 27 | # Directories potentially created on remote AFP share 28 | .AppleDB 29 | .AppleDesktop 30 | Network Trash Folder 31 | Temporary Items 32 | .apdisk 33 | 34 | ### Windows ### 35 | # Windows thumbnail cache files 36 | Thumbs.db 37 | Thumbs.db:encryptable 38 | ehthumbs.db 39 | ehthumbs_vista.db 40 | 41 | # Dump file 42 | *.stackdump 43 | 44 | # Folder config file 45 | [Dd]esktop.ini 46 | 47 | # Recycle Bin used on file shares 48 | $RECYCLE.BIN/ 49 | 50 | # Windows Installer files 51 | *.cab 52 | *.msi 53 | *.msix 54 | *.msm 55 | *.msp 56 | 57 | # Windows shortcuts 58 | *.lnk 59 | 60 | # End of https://www.toptal.com/developers/gitignore/api/macos,windows 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chamilo LMS docker-compose 2 | 3 | A simple docker-compose setup for [Chamilo](https://chamilo.org/) LMS. 4 | 5 | Official Docker Hub images used: 6 | * [php](https://hub.docker.com/_/php/) (7.x-apache) 7 | * [mariadb](https://hub.docker.com/_/mariadb/) (latest) 8 | 9 | Chamilo LMS on Github: https://github.com/chamilo/chamilo-lms 10 | 11 | This setup doesn't directly install/configure Chamilo. You have to use the Chamilo installation wizard once the docker containers are up. 12 | 13 | *This setup works with Chamilo version 1.11.x 14 | 15 | ### Optional: Host system config 16 | 17 | On the Host system you need to add a `/etc/hosts` entry for the domain name configured in the apache vhost: 18 | 19 | ```bash 20 | sudo echo "127.0.0.1 docker.chamilo.net" >> /etc/hosts 21 | ``` 22 | 23 | ##### `.../etc/hosts` on Windows: 24 | * Open a text editor as administrator (with administrator privileges) 25 | * Open hosts file: `C:\Windows\System32\Drivers\etc\hosts` 26 | * Add entry `127.0.0.1 docker.chamilo.net` at the end of the file and **save**. 27 | 28 | # ENV Variables 29 | 30 | ### `environment` variables for MYSQL 31 | You can define mysql username, password and database name in the docker-compose config: 32 | 33 | ```yaml 34 | ... 35 | environment: 36 | - MYSQL_ROOT_PASSWORD=pass 37 | - MYSQL_USER=chamilo 38 | - MYSQL_PASSWORD=chamilo 39 | - MYSQL_DATABASE=chamilo 40 | ... 41 | ``` 42 | 43 | ### `args` variables for building Chamilo 44 | You can define the Version for Chamilo with `CHAMILO_VERSION`. 45 | And you have to set the `CHAMILO_TAR` filename due to incosistent naming. 46 | Check for `.tar.gz` filenames here: https://github.com/chamilo/chamilo-lms/releases 47 | 48 | Example in `docker-compose.yml`: 49 | ```yaml 50 | args: 51 | - CHAMILO_VERSION=1.11.10 52 | - CHAMILO_TAR=chamilo-1.11.10-php7.3.tar.gz 53 | ``` 54 | 55 | The `args` settings in `docker-compose.yml` will override the `ARG` settings in the `Dockerfile`. 56 | If you remove the `args` in `docker-compose.yml`, the "fallback" values from the `Dockerfile` will be used. 57 | 58 | ### Build & Run 59 | 60 | ##### Build: 61 | Build chamilo docker image 62 | ```bash 63 | docker-compose -f docker-compose.yml build 64 | ``` 65 | 66 | ##### Run: 67 | ```bash 68 | docker-compose -f docker-compose.yml up 69 | ``` 70 | 71 | ## Database connection step in web installation wizard 72 | The "Database Host" in step 4 of the mysql connections settings has to be the name of the docker image defined in the appropriate `docker-compose.yml`. 73 | 74 | Database Host: `mariadb` 75 | 76 | ## Access Chamilo Website 77 | Access Chamilo URL with `/etc/hosts` entry: 78 | 79 | ``` 80 | http://docker.chamilo.net:8080/ 81 | ``` 82 | 83 | Without `/etc/hosts` entry: 84 | ``` 85 | http://localhost:8080 86 | ``` 87 | 88 | # Selenium grid tests with jest 89 | 90 | [Selenium grid](https://www.selenium.dev/documentation/en/grid/) tests using [jest](https://jestjs.io/) (javascript) and Docker e.g. docker-compose 91 | 92 | Selenium provides docker images ([docker-selenium on github](https://github.com/SeleniumHQ/docker-selenium)) to run the Selenium grid with docker-compose 93 | 94 | 95 | ### Install packages 96 | 97 | Install needed node packages: 98 | ```bash 99 | cd "selenium-with-jest" 100 | npm install 101 | ``` 102 | 103 | 104 | ### Run tests 105 | 106 | Start chamilo lms and selenium grid: 107 | ```bash 108 | docker-compose -f docker-compose.yml -f docker-compose-selenium.yml up -d 109 | ``` 110 | 111 | Run tests with jest: 112 | ```bash 113 | cd "selenium-with-jest" 114 | ./wait-for-selenium-grid.sh npm test 115 | ``` 116 | 117 | ## Github action with Selenium grid, jest and docker-compose 118 | 119 | A Github Action to run **jest** tests on a Selenium grid with docker-compose when creating a pull request or pushing into the `master` branch: 120 | [docker-compose-ci.yml](.github/workflows/docker-compose-ci.yml) -------------------------------------------------------------------------------- /chamilo-php7/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.3-apache as php7_apache_base 2 | 3 | # install packages 4 | RUN apt update && apt install -yq --no-install-recommends \ 5 | msmtp \ 6 | curl \ 7 | git \ 8 | wget \ 9 | parallel \ 10 | libzip-dev \ 11 | zlib1g-dev \ 12 | libpng-dev \ 13 | libicu-dev \ 14 | libxslt-dev \ 15 | libjpeg-dev \ 16 | libgd3 \ 17 | libgd-dev \ 18 | libfreetype6-dev \ 19 | libmemcached-dev \ 20 | libwebp-dev \ 21 | libxpm-dev \ 22 | libjpeg62-turbo-dev \ 23 | g++ \ 24 | && apt autoclean \ 25 | && apt autoremove -y \ 26 | && rm -rf /var/lib/apt/lists/* 27 | 28 | # Install APCu 29 | RUN pecl install apcu && \ 30 | pecl install memcached 31 | 32 | # install and configure extensions 33 | RUN docker-php-ext-install -j$(nproc) opcache pdo_mysql zip xsl 34 | RUN docker-php-ext-configure intl && \ 35 | docker-php-ext-install -j$(nproc) intl 36 | RUN docker-php-ext-configure gd \ 37 | --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include/ \ 38 | --with-freetype-dir=/usr/include/ --with-gd && \ 39 | docker-php-ext-install -j$(nproc) gd 40 | RUN docker-php-ext-enable memcached apcu 41 | 42 | WORKDIR /var/www 43 | 44 | FROM php7_apache_base as php7_apache_chamilo_lms 45 | 46 | ARG CHAMILO_VERSION="1.11.12" 47 | ARG CHAMILO_TAR="chamilo-${CHAMILO_VERSION}-php7.2.tar.gz" 48 | 49 | # download and install chamilo 50 | ADD "https://github.com/chamilo/chamilo-lms/releases/download/v${CHAMILO_VERSION}/${CHAMILO_TAR}" "/var/www/chamilo.tar.gz" 51 | # unpack, delete archive, rename folders 52 | RUN tar -zxf chamilo.tar.gz \ 53 | && rm chamilo.tar.gz \ 54 | && mv chamilo* chamilo 55 | # delete unneeded files 56 | RUN find . -iname ".git*" -exec rm -rf {} + \ 57 | && rm -rf chamilo/tests chamilo/.htac* 58 | 59 | WORKDIR /var/www/chamilo 60 | 61 | RUN bash -c 'set -o pipefail; ls -d app main/default_course_document/images main/lang vendor web | parallel chown -R www-data:www-data "{}"' 62 | 63 | # Configure and start Apache 64 | RUN a2dissite 000-default; rm -rf /etc/apache2/sites-enabled/000-default.conf 65 | COPY files/security.conf /etc/apache2/conf-available/ 66 | COPY files/chamilo.conf /etc/apache2/sites-available/chamilo.conf 67 | RUN a2ensite chamilo; a2enmod remoteip rewrite headers 68 | 69 | # configure /etc/hosts 70 | RUN echo "127.0.0.1 docker.chamilo.net" >> /etc/hosts 71 | 72 | EXPOSE 80 -------------------------------------------------------------------------------- /chamilo-php7/files/chamilo.conf: -------------------------------------------------------------------------------- 1 | 2 | ServerAdmin webmaster@localhost 3 | ServerName docker.chamilo.net 4 | 5 | DocumentRoot /var/www/chamilo 6 | 7 | 8 | AllowOverride All 9 | Require all granted 10 | 11 | 12 | 13 | AllowOverride None 14 | Options -Indexes 15 | 16 | 17 | 18 | AllowOverride All 19 | Require all granted 20 | 21 | 22 | 23 | # Rewrites 24 | RewriteEngine On 25 | 26 | # Prevent execution of PHP from directories used for different types of uploads 27 | RedirectMatch 403 ^/app/(cache|courses|home|logs|upload|Resources/public/css)/.*\.ph(p[3457]?|t|tml|ar)$ 28 | RedirectMatch 403 ^/main/default_course_document/images/.*\.ph(p[3457]?|t|tml|ar)$ 29 | RedirectMatch 403 ^/main/lang/.*\.ph(p[3457]?|t|tml|ar)$ 30 | RedirectMatch 403 ^/web/css/.*\.ph(p[3457]?|t|tml|ar)$ 31 | 32 | # http://my.chamilo.net/certificates/?id=123 to http://my.chamilo.net/certificates/index.php?id=123 33 | RewriteCond %{QUERY_STRING} ^id=(.*)$ 34 | RewriteRule ^certificates/$ certificates/index.php?id=%1 [L] 35 | 36 | # Course redirection 37 | RewriteRule ^courses/([^/]+)/?$ main/course_home/course_home.php?cDir=$1 [QSA,L] 38 | RewriteRule ^courses/([^/]+)/index.php$ main/course_home/course_home.php?cDir=$1 [QSA,L] 39 | 40 | # Rewrite everything in the scorm folder of a course to the download script 41 | RewriteRule ^courses/([^/]+)/scorm/(.*)$ main/document/download_scorm.php?doc_url=/$2&cDir=$1 [QSA,L] 42 | 43 | # Rewrite everything in the document folder of a course to the download script 44 | # Except certificate resources, which might need to be accessible publicly to all 45 | RewriteRule ^courses/([^/]+)/document/certificates/(.*)$ app/courses/$1/document/certificates/$2 [QSA,L] 46 | RewriteRule ^courses/([^/]+)/document/(.*)$ main/document/download.php?doc_url=/$2&cDir=$1 [QSA,L] 47 | 48 | # Course upload files 49 | RewriteRule ^courses/([^/]+)/upload/([^/]+)/(.*)$ main/document/download_uploaded_files.php?code=$1&type=$2&file=$3 [QSA,L] 50 | 51 | # Rewrite everything in the work folder 52 | RewriteRule ^courses/([^/]+)/work/(.*)$ main/work/download.php?file=work/$2&cDir=$1 [QSA,L] 53 | 54 | RewriteRule ^courses/([^/]+)/course-pic85x85.png$ main/inc/ajax/course.ajax.php?a=get_course_image&code=$1&image=course_image_source [QSA,L] 55 | RewriteRule ^courses/([^/]+)/course-pic.png$ main/inc/ajax/course.ajax.php?a=get_course_image&code=$1&image=course_image_large_source [QSA,L] 56 | 57 | # Redirect all courses/ to app/courses/ 58 | RewriteRule ^courses/([^/]+)/(.*)$ app/courses/$1/$2 [QSA,L] 59 | 60 | # About session 61 | RewriteRule ^session/(\d{1,})/about/?$ main/session/about.php?session_id=$1 [L] 62 | 63 | # About course 64 | RewriteRule ^course/(\d{1,})/about/?$ main/course_info/about.php?course_id=$1 [L] 65 | 66 | # Issued individual badge friendly URL 67 | RewriteRule ^badge/(\d{1,}) main/badge/issued.php?issue=$1 [L] 68 | 69 | # Issued badges friendly URL 70 | RewriteRule ^skill/(\d{1,})/user/(\d{1,}) main/badge/issued_all.php?skill=$1&user=$2 [L] 71 | # Support deprecated URL (avoid 404) 72 | RewriteRule ^badge/(\d{1,})/user/(\d{1,}) main/badge/issued_all.php?skill=$1&user=$2 [L] 73 | 74 | # Support old URLs using the exercice (with a c) folder rather than exercise 75 | RewriteRule ^main/exercice/(.*)$ main/exercise/$1 [QSA,L] 76 | # Support old URLs using the newscorm folder rather than lp 77 | RewriteRule ^main/newscorm/(.*)$ main/lp/$1 [QSA,L] 78 | 79 | # service Information 80 | RewriteRule ^service/(\d{1,})$ plugin/buycourses/src/service_information.php?service_id=$1 [L] 81 | 82 | # This rule is very generic and should always remain at the bottom of .htaccess 83 | # http://my.chamilo.net/jdoe to http://my.chamilo.net/user.php?jdoe 84 | RewriteRule ^([^/.]+)/?$ user.php?$1 [L] 85 | 86 | # Deny access 87 | RewriteRule ^(tests|.git|.env|.env.dist|config) - [F,L,NC] 88 | 89 | 90 | AddType application/font-woff .woff .woff2 91 | 92 | ExpiresActive On 93 | ExpiresByType application/font-woff "access plus 1 month" 94 | 95 | 96 | # php values 97 | php_value expose_php Off 98 | php_value error_logging On 99 | php_value error_reporting 6143 100 | php_value display_errors On 101 | php_value xdebug.enable On 102 | 103 | #php_value memory_limit 256 104 | php_value upload_max_filesize 100M 105 | php_value post_max_size 100M 106 | php_value max_execution_time 300 107 | php_value max_input_time 600 108 | php_value session.cookie_httponly 1 109 | 110 | php_value safe_mode Off 111 | php_value short_open_tag Off 112 | php_value magic_quotes_gpc Off 113 | php_value magic_quotes_runtime Off 114 | php_value allow_url_fopen Off 115 | php_value date.timezone Europe/Zurich 116 | php_value variables_order GPCS 117 | php_value output_buffering On 118 | 119 | php_value sendmail_path "/usr/sbin/sendmail -t -i" 120 | php_value mail.add_x_header On 121 | 122 | php_value opcache.interned_strings_buffer 16 123 | php_value opcache.max_accelerated_files 100000 124 | php_value opcache.validate_timestamps 0 125 | php_value apc.enabled 1 126 | php_value apc.enable_cli 1 127 | php_value request_terminate_timeout 50 128 | 129 | php_value session.gc_maxlifetime 4320 130 | 131 | # logging 132 | LogLevel warn 133 | #ErrorLog ${APACHE_LOG_DIR}/docker.chamilo-error.log 134 | #CustomLog ${APACHE_LOG_DIR}/docker.chamilo-access.log combined 135 | 136 | -------------------------------------------------------------------------------- /chamilo-php7/files/security.conf: -------------------------------------------------------------------------------- 1 | # Disable access to the entire file system except for the directories that 2 | # are explicitly allowed later. 3 | # 4 | # This currently breaks the configurations that come with some web application 5 | # Debian packages. 6 | # 7 | # 8 | # AllowOverride None 9 | # Require all denied 10 | # 11 | 12 | 13 | # Changing the following options will not really affect the security of the 14 | # server, but might make attacks slightly more difficult in some cases. 15 | 16 | # 17 | # ServerTokens 18 | # This directive configures what you return as the Server HTTP response 19 | # Header. The default is 'Full' which sends information about the OS-Type 20 | # and compiled in modules. 21 | # Set to one of: Full | OS | Minimal | Minor | Major | Prod 22 | # where Full conveys the most information, and Prod the least. 23 | #ServerTokens Minimal 24 | ServerTokens Prod 25 | #ServerTokens Full 26 | 27 | # 28 | # Optionally add a line containing the server version and virtual host 29 | # name to server-generated pages (internal error documents, FTP directory 30 | # listings, mod_status and mod_info output etc., but not CGI generated 31 | # documents or custom error documents). 32 | # Set to "EMail" to also include a mailto: link to the ServerAdmin. 33 | # Set to one of: On | Off | EMail 34 | #ServerSignature Off 35 | ServerSignature Off 36 | 37 | # 38 | # Allow TRACE method 39 | # 40 | # Set to "extended" to also reflect the request body (only for testing and 41 | # diagnostic purposes). 42 | # 43 | # Set to one of: On | Off | extended 44 | TraceEnable Off 45 | #TraceEnable On 46 | 47 | # 48 | # Forbid access to version control directories 49 | # 50 | # If you use version control systems in your document root, you should 51 | # probably deny access to their directories. For example, for subversion: 52 | # 53 | # 54 | # Require all denied 55 | # 56 | 57 | # 58 | # Setting this header will prevent MSIE from interpreting files as something 59 | # else than declared by the content type in the HTTP headers. 60 | # Requires mod_headers to be enabled. 61 | # 62 | #Header set X-Content-Type-Options: "nosniff" 63 | 64 | # 65 | # Setting this header will prevent other sites from embedding pages from this 66 | # site as frames. This defends against clickjacking attacks. 67 | # Requires mod_headers to be enabled. 68 | # 69 | #Header set X-Frame-Options: "sameorigin" 70 | 71 | # X-XSS-Protection 72 | #Header set X-XSS-Protection "1; mode=block" 73 | 74 | # X-Powered-By 75 | Header always unset "X-Powered-By" 76 | Header unset "X-Powered-By" 77 | 78 | 79 | # vim: syntax=apache ts=4 sw=4 sts=4 sr noet -------------------------------------------------------------------------------- /docker-compose-selenium.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | 4 | mariadb: 5 | networks: 6 | - selenium-grid 7 | 8 | chamilo_php7: 9 | networks: 10 | - selenium-grid 11 | 12 | chrome: 13 | image: selenium/node-chrome:4.1.2-20220208 14 | shm_size: 3g 15 | depends_on: 16 | - selenium-hub 17 | environment: 18 | - SE_EVENT_BUS_HOST=selenium-hub 19 | - SE_EVENT_BUS_PUBLISH_PORT=4442 20 | - SE_EVENT_BUS_SUBSCRIBE_PORT=4443 21 | - START_XVFB=false 22 | ports: 23 | - "6900:5900" 24 | networks: 25 | - selenium-grid 26 | 27 | firefox: 28 | image: selenium/node-firefox:4.1.2-20220208 29 | shm_size: 3g 30 | depends_on: 31 | - selenium-hub 32 | environment: 33 | - SE_EVENT_BUS_HOST=selenium-hub 34 | - SE_EVENT_BUS_PUBLISH_PORT=4442 35 | - SE_EVENT_BUS_SUBSCRIBE_PORT=4443 36 | - START_XVFB=false 37 | ports: 38 | - "6901:5900" 39 | networks: 40 | - selenium-grid 41 | 42 | selenium-hub: 43 | image: selenium/hub:4.1.2-20220208 44 | container_name: selenium-hub 45 | environment: 46 | - GRID_MAX_SESSION=16 47 | - GRID_BROWSER_TIMEOUT=3000 48 | - GRID_TIMEOUT=3000 49 | ports: 50 | - "4442:4442" 51 | - "4443:4443" 52 | - "4444:4444" 53 | healthcheck: 54 | test: ["CMD", "/opt/bin/check-grid.sh"] 55 | interval: 15s 56 | timeout: 30s 57 | retries: 5 58 | start_period: 40s 59 | networks: 60 | - selenium-grid 61 | 62 | networks: 63 | selenium-grid: -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.5" 2 | services: 3 | mariadb: 4 | image: mariadb 5 | environment: 6 | - MYSQL_ROOT_PASSWORD=pass 7 | - MYSQL_USER=chamilo 8 | - MYSQL_PASSWORD=chamilo 9 | - MYSQL_DATABASE=chamilo 10 | labels: 11 | "project.id": "docker.compose.mariadb" 12 | "project.github.url": "https://github.com/22phuber/docker-compose-chamilo-lms" 13 | chamilo_php7: 14 | build: 15 | context: ./chamilo-php7 16 | target: php7_apache_chamilo_lms 17 | args: 18 | - CHAMILO_VERSION=1.11.12 19 | - CHAMILO_TAR=chamilo-1.11.12-php7.2.tar.gz 20 | links: 21 | - mariadb 22 | ports: 23 | - "8080:80" 24 | extra_hosts: 25 | - "docker.chamilo.net:127.0.0.1" 26 | labels: 27 | "project.id": "docker.compose.chamilo_lms" 28 | "project.github.url": "https://github.com/22phuber/docker-compose-chamilo-lms" 29 | -------------------------------------------------------------------------------- /selenium-with-jest/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .idea/ 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /selenium-with-jest/__tests__/grid.tests.js: -------------------------------------------------------------------------------- 1 | const { Builder, By, Key, until, Capabilities } = require("selenium-webdriver"); 2 | import { 3 | findElementByTagName, 4 | findVisibleElementByCssSelector, 5 | getVisibleElementByName, 6 | getVisibleElementByXpath, 7 | getVisibleElementById, 8 | } from "../helpers/helpers"; 9 | 10 | const grid_url = "http://localhost:4444/wd/hub"; 11 | const chamilo_url = "http://chamilo_php7/"; 12 | 13 | // Google Chrome Capabilities 14 | const chromeCapabilities = Capabilities.chrome(); 15 | chromeCapabilities.set("goog:chromeOptions", { args: ["--headless"] }); 16 | // Mozilla Firefox Capabilities 17 | const firefoxCapabilities = Capabilities.firefox(); 18 | firefoxCapabilities.set("moz:firefoxOptions", { args: ["--headless"] }); 19 | 20 | 21 | /***********/ 22 | /* Firefox */ 23 | /***********/ 24 | // Tests wizard until Step6 (no installation) 25 | describe("Firefox", () => { 26 | let firefoxDriver; 27 | 28 | beforeAll(async () => { 29 | firefoxDriver = await new Builder() 30 | .usingServer(grid_url) 31 | .forBrowser("firefox") 32 | .withCapabilities(firefoxCapabilities) 33 | .build(); 34 | 35 | await firefoxDriver.manage().setTimeouts({ implicit: 5000 }); 36 | await firefoxDriver.manage().window().maximize(); 37 | 38 | // eslint-disable-next-line no-undef 39 | await firefoxDriver.get(chamilo_url); 40 | }, 10000); 41 | 42 | afterAll(async () => { 43 | await firefoxDriver.quit(); 44 | }, 10000); 45 | 46 | test("Welcome page", async () => { 47 | // Browser title has correct content 48 | const title = await ( 49 | await findElementByTagName(firefoxDriver, "title") 50 | ).getAttribute("innerText"); 51 | expect(title).toContain("Chamilo has not been installed"); 52 | 53 | // Wizard welcome title has correct content 54 | const WelcomeH2XPath = 55 | "/html/body/div/div/div/div/div[2]/div/div/form/div/div/div/h2"; 56 | const WelcomeText = await ( 57 | await getVisibleElementByXpath(firefoxDriver, WelcomeH2XPath) 58 | ).getText(); 59 | expect(WelcomeText).toContain( 60 | "Welcome to the Chamilo 1.11.12 stable installation wizard" 61 | ); 62 | }, 3000); 63 | 64 | test("Setup wizard, test database connection", async () => { 65 | // Find and click install button 66 | const installButton = await findVisibleElementByCssSelector( 67 | firefoxDriver, 68 | "button[type=submit]" 69 | ); 70 | await installButton.click(); 71 | 72 | /**************/ 73 | /* Step1 Page */ 74 | /**************/ 75 | 76 | // Step1: Browser title has correct content 77 | const wizardTitle = await ( 78 | await findElementByTagName(firefoxDriver, "title") 79 | ).getAttribute("innerText"); 80 | expect(wizardTitle).toContain("— Chamilo installation — Version 1.11.12"); 81 | 82 | // Step1: Wizard page h2 title 83 | const wizardH2XPath = 84 | "/html/body/div/div/div/div[1]/div/div[1]/form/div/h2"; 85 | const wizardStep1H2Text = await ( 86 | await getVisibleElementByXpath(firefoxDriver, wizardH2XPath) 87 | ).getText(); 88 | expect(wizardStep1H2Text).toContain("Step 1 – Installation Language"); 89 | 90 | // Step1: Next Button 91 | const step1NextButton = await getVisibleElementByName(firefoxDriver, "step1"); 92 | const step1NextButtonValue = await step1NextButton.getAttribute("value"); 93 | expect(step1NextButtonValue).toContain("Next"); 94 | await step1NextButton.click(); 95 | 96 | /**************/ 97 | /* Step2 Page */ 98 | /**************/ 99 | 100 | // Step2: Wizard page h2 title 101 | const wizardStep2H2Text = await ( 102 | await getVisibleElementByXpath(firefoxDriver, wizardH2XPath) 103 | ).getText(); 104 | expect(wizardStep2H2Text).toContain("Step 2 – Requirements"); 105 | 106 | // Step2: Install Button 107 | const step2NextButton = await getVisibleElementByName( 108 | firefoxDriver, 109 | "step2_install" 110 | ); 111 | const step2NextButtonValue = await step2NextButton.getAttribute("value"); 112 | expect(step2NextButtonValue).toContain("New installation"); 113 | await step2NextButton.click(); 114 | 115 | /**************/ 116 | /* Step3 Page */ 117 | /**************/ 118 | 119 | // Step3: Wizard page header h2 title 120 | const headerPageHeaderTitleXPath = 121 | "/html/body/div/div/div/div[1]/div/div[1]/form/div[1]"; 122 | const step3PageHeaderTitleText = await ( 123 | await getVisibleElementByXpath(firefoxDriver, headerPageHeaderTitleXPath) 124 | ).getText(); 125 | expect(step3PageHeaderTitleText).toContain("New installation"); 126 | 127 | // Step3: Wizard page h2 title 128 | const wizardNewInstallH2XPath = 129 | "/html/body/div/div/div/div[1]/div/div[1]/form/div[2]/h2"; 130 | const wizardStep3H2Text = await ( 131 | await getVisibleElementByXpath(firefoxDriver, wizardNewInstallH2XPath) 132 | ).getText(); 133 | expect(wizardStep3H2Text).toContain("Step 3 – Licence"); 134 | 135 | // Step3: Accept license checkbox 136 | const step3AcceptLicence = await getVisibleElementById( 137 | firefoxDriver, 138 | "accept_licence" 139 | ); 140 | if (!(await step3AcceptLicence.isSelected())) { 141 | await step3AcceptLicence.click(); 142 | } 143 | expect(await step3AcceptLicence.isSelected()).toBeTruthy(); 144 | 145 | // Step3: Next Button 146 | const step3NextButton = await getVisibleElementByName(firefoxDriver, "step3"); 147 | const step3NextButtonValue = await step3NextButton.getAttribute("value"); 148 | expect(step3NextButtonValue).toContain("Next"); 149 | await step3NextButton.click(); 150 | 151 | /**************/ 152 | /* Step4 Page */ 153 | /**************/ 154 | 155 | // Step4: Wizard page header h2 title 156 | const step4PageHeaderTitleText = await ( 157 | await getVisibleElementByXpath(firefoxDriver, headerPageHeaderTitleXPath) 158 | ).getText(); 159 | expect(step4PageHeaderTitleText).toContain("New installation"); 160 | 161 | // Step4: Wizard page h2 title 162 | const wizardStep4H2Text = await ( 163 | await getVisibleElementByXpath(firefoxDriver, wizardNewInstallH2XPath) 164 | ).getText(); 165 | expect(wizardStep4H2Text).toContain("Step 4 – MySQL database settings"); 166 | 167 | // Step4: Enter form field values 168 | const dbHostFormField = await findVisibleElementByCssSelector( 169 | firefoxDriver, 170 | "input[name=dbHostForm][type=text]" 171 | ); 172 | await dbHostFormField.clear(); 173 | await dbHostFormField.sendKeys("mariadb"); 174 | 175 | const dbUsernameFormField = await findVisibleElementByCssSelector( 176 | firefoxDriver, 177 | "input[name=dbUsernameForm][type=text]" 178 | ); 179 | await dbUsernameFormField.clear(); 180 | await dbUsernameFormField.sendKeys("chamilo"); 181 | 182 | const dbPassFormFormField = await findVisibleElementByCssSelector( 183 | firefoxDriver, 184 | "input[name=dbPassForm][type=password]" 185 | ); 186 | await dbPassFormFormField.clear(); 187 | await dbPassFormFormField.sendKeys("chamilo"); 188 | 189 | // Step4: Database connection test Button 190 | const step4TestDatabaseButton = await getVisibleElementByName( 191 | firefoxDriver, 192 | "step3" 193 | ); 194 | const step4TestDatabaseButtonValue = await step4TestDatabaseButton.getAttribute( 195 | "value" 196 | ); 197 | expect(step4TestDatabaseButtonValue).toContain("step3"); 198 | await step4TestDatabaseButton.click(); 199 | 200 | // Step4: Test database connection 201 | const dbConnectionStatus = await getVisibleElementById(firefoxDriver, "db_status"); 202 | const dbConnectionStatusClasses = await dbConnectionStatus.getAttribute( 203 | "class" 204 | ); 205 | expect(dbConnectionStatusClasses).toContain("alert-success"); 206 | 207 | // Step4: Next Button 208 | const step4NextButton = await getVisibleElementByName(firefoxDriver, "step4"); 209 | const step4NextButtonValue = await step4NextButton.getAttribute("value"); 210 | expect(step4NextButtonValue).toContain("Next"); 211 | await step4NextButton.click(); 212 | 213 | /**************/ 214 | /* Step5 Page */ 215 | /**************/ 216 | 217 | // Step5: Wizard page header h2 title 218 | const step5PageHeaderTitleText = await ( 219 | await getVisibleElementByXpath(firefoxDriver, headerPageHeaderTitleXPath) 220 | ).getText(); 221 | expect(step5PageHeaderTitleText).toContain("New installation"); 222 | 223 | // Step5: Wizard page h2 title 224 | const wizardStep5H2Text = await ( 225 | await getVisibleElementByXpath(firefoxDriver, wizardNewInstallH2XPath) 226 | ).getText(); 227 | expect(wizardStep5H2Text).toContain("Step 5 – Config settings"); 228 | 229 | // Step5: Enter form field values 230 | const passFormField = await findVisibleElementByCssSelector( 231 | firefoxDriver, 232 | "input[name=passForm][type=password]" 233 | ); 234 | await passFormField.clear(); 235 | await passFormField.sendKeys("admin"); 236 | 237 | // Step4: Next Button 238 | const step5NextButton = await getVisibleElementByName(firefoxDriver, "step5"); 239 | const step5NextButtonValue = await step5NextButton.getAttribute("value"); 240 | expect(step5NextButtonValue).toContain("Next"); 241 | await step5NextButton.click(); 242 | 243 | /**************/ 244 | /* Step6 Page */ 245 | /**************/ 246 | 247 | // Step6: Wizard page header h2 title 248 | const step6PageHeaderTitleXPath = 249 | "/html/body/div/div/div/div[1]/div/div[1]/form/div[1]/h2"; 250 | const step6PageHeaderTitleText = await ( 251 | await getVisibleElementByXpath(firefoxDriver, step6PageHeaderTitleXPath) 252 | ).getText(); 253 | expect(step6PageHeaderTitleText).toContain("New installation"); 254 | 255 | // Step6: Wizard page h2 title 256 | const wizardNewInstallH3XPath = 257 | "/html/body/div/div/div/div[1]/div/div[1]/form/div[2]/h3"; 258 | const wizardStep6H2Text = await ( 259 | await getVisibleElementByXpath(firefoxDriver, wizardNewInstallH3XPath) 260 | ).getText(); 261 | expect(wizardStep6H2Text).toContain("Step 6 – Last check before install"); 262 | }, 30000); 263 | }); 264 | 265 | 266 | /**********/ 267 | /* Chrome */ 268 | /**********/ 269 | // Tests wizard and installs chamilo 270 | describe("Chrome", () => { 271 | let chromeDriver; 272 | 273 | beforeAll(async () => { 274 | chromeDriver = await new Builder() 275 | .usingServer(grid_url) 276 | .forBrowser("chrome") 277 | .withCapabilities(chromeCapabilities) 278 | .build(); 279 | 280 | await chromeDriver.manage().setTimeouts({ implicit: 10000 }); 281 | await chromeDriver.manage().window().maximize(); 282 | 283 | // eslint-disable-next-line no-undef 284 | await chromeDriver.get(chamilo_url); 285 | }, 10000); 286 | 287 | afterAll(async () => { 288 | await chromeDriver.quit(); 289 | }, 10000); 290 | 291 | test("Welcome page", async () => { 292 | // Browser title has correct content 293 | const title = await ( 294 | await findElementByTagName(chromeDriver, "title") 295 | ).getAttribute("innerText"); 296 | expect(title).toContain("Chamilo has not been installed"); 297 | 298 | // Wizard welcome title has correct content 299 | const WelcomeH2XPath = 300 | "/html/body/div/div/div/div/div[2]/div/div/form/div/div/div/h2"; 301 | const WelcomeText = await ( 302 | await getVisibleElementByXpath(chromeDriver, WelcomeH2XPath) 303 | ).getText(); 304 | expect(WelcomeText).toContain( 305 | "Welcome to the Chamilo 1.11.12 stable installation wizard" 306 | ); 307 | }, 3000); 308 | 309 | test("Setup wizard, test database connection, install chamilo, login admin user", async () => { 310 | // Find and click install button 311 | const installButton = await findVisibleElementByCssSelector( 312 | chromeDriver, 313 | "button[type=submit]" 314 | ); 315 | await installButton.click(); 316 | 317 | /**************/ 318 | /* Step1 Page */ 319 | /**************/ 320 | 321 | // Step1: Browser title has correct content 322 | const wizardTitle = await ( 323 | await findElementByTagName(chromeDriver, "title") 324 | ).getAttribute("innerText"); 325 | expect(wizardTitle).toContain("— Chamilo installation — Version 1.11.12"); 326 | 327 | // Step1: Wizard page h2 title 328 | const wizardH2XPath = 329 | "/html/body/div/div/div/div[1]/div/div[1]/form/div/h2"; 330 | const wizardStep1H2Text = await ( 331 | await getVisibleElementByXpath(chromeDriver, wizardH2XPath) 332 | ).getText(); 333 | expect(wizardStep1H2Text).toContain("Step 1 – Installation Language"); 334 | 335 | // Step1: Next Button 336 | const step1NextButton = await getVisibleElementByName(chromeDriver, "step1"); 337 | const step1NextButtonValue = await step1NextButton.getAttribute("value"); 338 | expect(step1NextButtonValue).toContain("Next"); 339 | await step1NextButton.click(); 340 | 341 | /**************/ 342 | /* Step2 Page */ 343 | /**************/ 344 | 345 | // Step2: Wizard page h2 title 346 | const wizardStep2H2Text = await ( 347 | await getVisibleElementByXpath(chromeDriver, wizardH2XPath) 348 | ).getText(); 349 | expect(wizardStep2H2Text).toContain("Step 2 – Requirements"); 350 | 351 | // Step2: Install Button 352 | const step2NextButton = await getVisibleElementByName( 353 | chromeDriver, 354 | "step2_install" 355 | ); 356 | const step2NextButtonValue = await step2NextButton.getAttribute("value"); 357 | expect(step2NextButtonValue).toContain("New installation"); 358 | await step2NextButton.click(); 359 | 360 | /**************/ 361 | /* Step3 Page */ 362 | /**************/ 363 | 364 | // Step3: Wizard page header h2 title 365 | const headerPageHeaderTitleXPath = 366 | "/html/body/div/div/div/div[1]/div/div[1]/form/div[1]"; 367 | const step3PageHeaderTitleText = await ( 368 | await getVisibleElementByXpath(chromeDriver, headerPageHeaderTitleXPath) 369 | ).getText(); 370 | expect(step3PageHeaderTitleText).toContain("New installation"); 371 | 372 | // Step3: Wizard page h2 title 373 | const wizardNewInstallH2XPath = 374 | "/html/body/div/div/div/div[1]/div/div[1]/form/div[2]/h2"; 375 | const wizardStep3H2Text = await ( 376 | await getVisibleElementByXpath(chromeDriver, wizardNewInstallH2XPath) 377 | ).getText(); 378 | expect(wizardStep3H2Text).toContain("Step 3 – Licence"); 379 | 380 | // Step3: Accept license checkbox 381 | const step3AcceptLicence = await getVisibleElementById( 382 | chromeDriver, 383 | "accept_licence" 384 | ); 385 | if (!(await step3AcceptLicence.isSelected())) { 386 | await step3AcceptLicence.click(); 387 | } 388 | expect(await step3AcceptLicence.isSelected()).toBeTruthy(); 389 | 390 | // Step3: Next Button 391 | const step3NextButton = await getVisibleElementByName(chromeDriver, "step3"); 392 | const step3NextButtonValue = await step3NextButton.getAttribute("value"); 393 | expect(step3NextButtonValue).toContain("Next"); 394 | await step3NextButton.click(); 395 | 396 | /**************/ 397 | /* Step4 Page */ 398 | /**************/ 399 | 400 | // Step4: Wizard page header h2 title 401 | const step4PageHeaderTitleText = await ( 402 | await getVisibleElementByXpath(chromeDriver, headerPageHeaderTitleXPath) 403 | ).getText(); 404 | expect(step4PageHeaderTitleText).toContain("New installation"); 405 | 406 | // Step4: Wizard page h2 title 407 | const wizardStep4H2Text = await ( 408 | await getVisibleElementByXpath(chromeDriver, wizardNewInstallH2XPath) 409 | ).getText(); 410 | expect(wizardStep4H2Text).toContain("Step 4 – MySQL database settings"); 411 | 412 | // Step4: Enter form field values 413 | const dbHostFormField = await findVisibleElementByCssSelector( 414 | chromeDriver, 415 | "input[name=dbHostForm][type=text]" 416 | ); 417 | await dbHostFormField.clear(); 418 | await dbHostFormField.sendKeys("mariadb"); 419 | 420 | const dbUsernameFormField = await findVisibleElementByCssSelector( 421 | chromeDriver, 422 | "input[name=dbUsernameForm][type=text]" 423 | ); 424 | await dbUsernameFormField.clear(); 425 | await dbUsernameFormField.sendKeys("chamilo"); 426 | 427 | const dbPassFormFormField = await findVisibleElementByCssSelector( 428 | chromeDriver, 429 | "input[name=dbPassForm][type=password]" 430 | ); 431 | await dbPassFormFormField.clear(); 432 | await dbPassFormFormField.sendKeys("chamilo"); 433 | 434 | // Step4: Database connection test Button 435 | const step4TestDatabaseButton = await getVisibleElementByName( 436 | chromeDriver, 437 | "step3" 438 | ); 439 | const step4TestDatabaseButtonValue = await step4TestDatabaseButton.getAttribute( 440 | "value" 441 | ); 442 | expect(step4TestDatabaseButtonValue).toContain("step3"); 443 | await step4TestDatabaseButton.click(); 444 | 445 | // Step4: Test database connection 446 | const dbConnectionStatus = await getVisibleElementById(chromeDriver, "db_status"); 447 | const dbConnectionStatusClasses = await dbConnectionStatus.getAttribute( 448 | "class" 449 | ); 450 | expect(dbConnectionStatusClasses).toContain("alert-success"); 451 | 452 | // Step4: Next Button 453 | const step4NextButton = await getVisibleElementByName(chromeDriver, "step4"); 454 | const step4NextButtonValue = await step4NextButton.getAttribute("value"); 455 | expect(step4NextButtonValue).toContain("Next"); 456 | await step4NextButton.click(); 457 | 458 | /**************/ 459 | /* Step5 Page */ 460 | /**************/ 461 | 462 | // Step5: Wizard page header h2 title 463 | const step5PageHeaderTitleText = await ( 464 | await getVisibleElementByXpath(chromeDriver, headerPageHeaderTitleXPath) 465 | ).getText(); 466 | expect(step5PageHeaderTitleText).toContain("New installation"); 467 | 468 | // Step5: Wizard page h2 title 469 | const wizardStep5H2Text = await ( 470 | await getVisibleElementByXpath(chromeDriver, wizardNewInstallH2XPath) 471 | ).getText(); 472 | expect(wizardStep5H2Text).toContain("Step 5 – Config settings"); 473 | 474 | // Step5: Enter form field values 475 | const passFormField = await findVisibleElementByCssSelector( 476 | chromeDriver, 477 | "input[name=passForm][type=password]" 478 | ); 479 | await passFormField.clear(); 480 | await passFormField.sendKeys("admin"); 481 | 482 | // Step4: Next Button 483 | const step5NextButton = await getVisibleElementByName(chromeDriver, "step5"); 484 | const step5NextButtonValue = await step5NextButton.getAttribute("value"); 485 | expect(step5NextButtonValue).toContain("Next"); 486 | await step5NextButton.click(); 487 | 488 | /**************/ 489 | /* Step6 Page */ 490 | /**************/ 491 | 492 | // Step6: Wizard page header h2 title 493 | const step6PageHeaderTitleXPath = 494 | "/html/body/div/div/div/div[1]/div/div[1]/form/div[1]/h2"; 495 | const step6PageHeaderTitleText = await ( 496 | await getVisibleElementByXpath(chromeDriver, step6PageHeaderTitleXPath) 497 | ).getText(); 498 | expect(step6PageHeaderTitleText).toContain("New installation"); 499 | 500 | // Step6: Wizard page h3 title 501 | const wizardNewInstallH3XPath = 502 | "/html/body/div/div/div/div[1]/div/div[1]/form/div[2]/h3"; 503 | const wizardStep6H3Text = await ( 504 | await getVisibleElementByXpath(chromeDriver, wizardNewInstallH3XPath) 505 | ).getText(); 506 | expect(wizardStep6H3Text).toContain("Step 6 – Last check before install"); 507 | 508 | // Step6: Find and click install button 509 | const finalInstallButton = await findVisibleElementByCssSelector( 510 | chromeDriver, 511 | "button[id=button_step6][type=submit]" 512 | ); 513 | const finalInstallButtonClasses = await finalInstallButton.getAttribute( 514 | "class" 515 | ); 516 | expect(finalInstallButtonClasses).toContain("btn-success"); 517 | await finalInstallButton.click(); 518 | 519 | /**************/ 520 | /* Step7 Page */ 521 | /**************/ 522 | // Step7: Wizard page h3 title 523 | const wizardFinalInstallH3XPath = 524 | "/html/body/div/div/div/div[1]/div/div[1]/form/div[1]/h3" 525 | const wizardStep7H3Text = await ( 526 | await getVisibleElementByXpath(chromeDriver, wizardFinalInstallH3XPath) 527 | ).getText(); 528 | expect(wizardStep7H3Text).toContain("Step 7 – Installation process execution"); 529 | 530 | // Step7: Portal button 531 | const portalLink = await findVisibleElementByCssSelector( 532 | chromeDriver, 533 | "a[href='../../index.php']" 534 | ); 535 | const portalLinkClasses = await portalLink.getAttribute( 536 | "class" 537 | ); 538 | expect(portalLinkClasses).toContain("btn-success"); 539 | const portalLinkText = await portalLink.getText(); 540 | expect(portalLinkText).toContain("Go to your newly created portal."); 541 | await portalLink.click(); 542 | 543 | /*********************/ 544 | /* Portal Login Page */ 545 | /*********************/ 546 | // Portal Login: Navbar Link 547 | const navBarLink = await findVisibleElementByCssSelector( 548 | chromeDriver, 549 | "a[href='" + chamilo_url + "index.php'][title=Homepage]" 550 | ); 551 | const navBarLinkText = await navBarLink.getText(); 552 | expect(navBarLinkText).toContain("Homepage"); 553 | 554 | // Portal Login: Enter form field values 555 | const loginUsernameField = await findVisibleElementByCssSelector( 556 | chromeDriver, 557 | "input[id=login][placeholder=Username][name=login][type=text]" 558 | ); 559 | await loginUsernameField.clear(); 560 | await loginUsernameField.sendKeys("admin"); 561 | 562 | const loginPasswordField = await findVisibleElementByCssSelector( 563 | chromeDriver, 564 | "input[id=password][placeholder=Pass][name=password][type=password]" 565 | ); 566 | await loginPasswordField.clear(); 567 | await loginPasswordField.sendKeys("admin"); 568 | 569 | // Portal Login: Login Button 570 | const portalLoginButton = await findVisibleElementByCssSelector( 571 | chromeDriver, 572 | "button[id=formLogin_submitAuth][name=submitAuth][type=submit]" 573 | ); 574 | const portalLoginButtonText = await portalLoginButton.getText(); 575 | expect(portalLoginButtonText).toContain("Login"); 576 | await portalLoginButton.click(); 577 | 578 | /*****************************/ 579 | /* Portal Administrator Page */ 580 | /*****************************/ 581 | // Portal Administrator Page: Navbar Link 582 | const navBarAdministrationLink = await findVisibleElementByCssSelector( 583 | chromeDriver, 584 | "a[href='" + chamilo_url + "main/admin/'][title=Administration]" 585 | ); 586 | const navBarAdministrationLinkText = await navBarAdministrationLink.getText(); 587 | expect(navBarAdministrationLinkText).toContain("Administration"); 588 | 589 | // Portal Administrator Page: User list Link 590 | const userListLink = await findVisibleElementByCssSelector( 591 | chromeDriver, 592 | "a[href='user_list.php']" 593 | ); 594 | const userListLinkText = await userListLink.getText(); 595 | expect(userListLinkText).toContain("User list"); 596 | 597 | }, 60000); 598 | }); 599 | -------------------------------------------------------------------------------- /selenium-with-jest/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current', 8 | }, 9 | }, 10 | ], 11 | ], 12 | }; -------------------------------------------------------------------------------- /selenium-with-jest/helpers/helpers.js: -------------------------------------------------------------------------------- 1 | const { By, Key, until } = require("selenium-webdriver"); 2 | 3 | const findElementByTagName = async (driver, name, timeout = 2000) => { 4 | const element = await driver.wait( 5 | until.elementLocated(By.css(name)), 6 | timeout 7 | ); 8 | return element; 9 | }; 10 | 11 | /** 12 | * Get a visible element by css selector. Waits for element to be displayed in the rendered page 13 | * @param {*} driver 14 | * @param {*} name 15 | * @param {*} timeout 16 | */ 17 | const findVisibleElementByCssSelector = async (driver, name, timeout = 2000) => { 18 | const element = await driver.wait( 19 | until.elementLocated(By.css(name)), 20 | timeout 21 | ); 22 | return await driver.wait(until.elementIsVisible(element), timeout); 23 | }; 24 | 25 | const getVisibleElementByName = async (driver, name, timeout = 2000) => { 26 | const element = await driver.wait( 27 | until.elementLocated(By.name(name)), 28 | timeout 29 | ); 30 | return await driver.wait(until.elementIsVisible(element), timeout); 31 | }; 32 | 33 | const getVisibleElementById = async (driver, id, timeout = 2000) => { 34 | const element = await driver.wait(until.elementLocated(By.id(id)), timeout); 35 | return await driver.wait(until.elementIsVisible(element), timeout); 36 | }; 37 | 38 | const getVisibleElementByXpath = async (driver, xpath, timeout = 2000) => { 39 | const element = await driver.wait( 40 | until.elementLocated(By.xpath(xpath)), 41 | timeout 42 | ); 43 | return await driver.wait(until.elementIsVisible(element), timeout); 44 | }; 45 | 46 | export { 47 | findElementByTagName, 48 | findVisibleElementByCssSelector, 49 | getVisibleElementByName, 50 | getVisibleElementByXpath, 51 | getVisibleElementById, 52 | }; 53 | -------------------------------------------------------------------------------- /selenium-with-jest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "selenium-with-jest", 3 | "version": "1.0.0", 4 | "description": "Basic Selenium tests for chamilo lms", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "selenium-webdriver": "^4.4.0" 13 | }, 14 | "devDependencies": { 15 | "@babel/preset-env": "^7.12.1", 16 | "babel-jest": "^26.6.3", 17 | "jest": "^26.6.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /selenium-with-jest/wait-for-selenium-grid.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # wait-for-selenium-grid.sh 3 | # usage: $ ./wait-for-selenium-grid.sh npm test 4 | 5 | set -e 6 | set -o pipefail 7 | 8 | sleepTime=2 9 | loopCount=0 10 | stopLoopCount=30 # 30 * 2s => 60s 11 | 12 | while ! curl -sSL "http://localhost:4444/wd/hub/status" 2>&1 \ 13 | | grep -i -A1 value | tr -d '\n| |,' | awk -F':' '{print $3}' 2>&1 | grep "true" >/dev/null; do 14 | 15 | printf "Waiting for the Selenium Grid ... %ss\n" "$(((stopLoopCount-loopCount)*sleepTime))" 16 | sleep ${sleepTime} 17 | loopCount=$((loopCount+1)) 18 | [[ "${loopCount}" -ge "${stopLoopCount}" ]] \ 19 | && { 20 | printf "ERROR: Selenium Grid was not able to start in %s seconds\n" "$((stopLoopCount*sleepTime))"; 21 | exit 1; 22 | } 23 | 24 | done 25 | 26 | >&2 echo "Selenium Grid is up" 27 | 28 | # shellcheck disable=SC2068 29 | exec $@ --------------------------------------------------------------------------------