├── .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 $@
--------------------------------------------------------------------------------