diff --git a/cypress.json b/cypress.json index bf98fd8..92a94a5 100644 --- a/cypress.json +++ b/cypress.json @@ -8,5 +8,6 @@ "pluginsFile": "plugins", "screenshotsFolder": "screenshots", "supportFile": "commands", - "defaultCommandTimeout": 10000 + "defaultCommandTimeout": 10000, + "requestTimeout": 10000 } diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..4100158 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,153 @@ +# Running tao-e2e-runner from a Docker container + +> In some situations (for example CI), it is desirable to run Cypress tests in a predictable, dependency-free way, against a separate application instance that you want to test. Loading the Cypress test runner inside a Docker container is a good way to achieve this: in fact, Cypress.io provide their own Docker images and guides for doing this: + +This guide makes use of the `cypress/included` public Docker image, which contains Node, npm, and the Cypress package installed globally. + +The E2E tests will be run headlessly, although screenshots of test failures will still be saved into `screenshots/` + +The TAO instance you test against can theoretically be online, local or Dockerised. The most reliable results will come from a Dockerised instance, so that is what this guide focuses on. + +## 1. docker-compose approach + +### Prerequisites + +- A Dockerised instance of TAO + +In this example we're using the basic stack of which has containers for nginx, phpfpm, and mariadb. You need to bring up your stack and install TAO within the phpfpm container to get started. + +If your existing stack is different, see the CLI approach below. + +Intended project structure: + +``` +package-tao/ <- root of your instance +├- tao/ +| └- views/ +| └- build/ +| └- node_modules/ +| └- @oat-sa/ +| └- tao-e2e-runner/ <- npm package or git repo +| ├- commands/ +| ├- cypress.json <- important config values +| ├- cypress.env.json <- optional overrides +| ├- data/ +| ├- docker/ +| | └- docker-compose.yml <- container definitions +| ├- exampleTests/ +| ├- plugins/ +| └- screenshots/ <- test output saved here +| +└-taoQtiTest/ + └- views/ + └- js/ + └- e2e/ + └- *.spec.js <- location of e2e tests for taoQtiTest +``` + +### Configuration values + +When Cypress runs, it looks for the following values: + +- `baseUrl`: (*required*) the URL where the TAO instance can be accessed +- `integrationFolder`: where the E2E tests reside. If not provided, will fall back to `exampleTests` +- `testFiles`: glob pattern for finding tests within the `integrationFolder` + +Don't forget to set the TAO-specific values, some tests are likely to need them: + +- `adminUser` +- `adminPass` + +All these values can be passed to Cypress in several ways: + +1. as part of the main `cypress.json` configuration +2. (*recommended*) as part of a `cypress.env.json` file which overrides the default values +3. as config parameters after the cypress command: `cypress run --config baseUrl=http://localhost:8888` +4. as environment variables before running the command: `CYPRESS_baseUrl=http://localhost:8888 npx cypress run` +5. as environment variable passthrough parameters in a Docker command: `-e CYPRESS_baseUrl=http://localhost:8888` +6. (*recommended*) as environment variables in your `docker-compose.yml` container definition + +### Volumes + +The `cypress/included` container needs 2 volumes mapped into it: + +1. the `tao-e2e-runner` folder, for the testing configuration, custom commands and fixtures +2. the TAO filesystem, because E2E tests are stored in the `views/js/e2e` folder of each extension. This is satisfied in the provided configuration by reusing the volume from `tao_phpfpm`. + +The `integrationFolder` path should be relative to where the TAO filesystem is mapped inside the `cypress/included` container. + +### Finally, run some tests + +Run the following command to build the defined containers, bring them up, stop them when `cypress run` finishes and return with the exit code from Cypress (equal to the number of failed tests). + +`docker-compose up -d --exit-code-from tao_e2e` + +## 2. Command line approach + +If the stack defined in `docker-compose.yml` doesn't meet your needs, and you just want to run the Cypress container on its own, that can be done using a single command: + +Assuming that your TAO stack is running on the network `docker_tao_network`, and you are in `/tao/views/build/node_modules/@oat-sa/tao-e2e-runner`: + +```sh +docker run -it \ +-v $PWD/../../../../../../:/var/www/html \ +-v $PWD:/cypress \ +-w /cypress \ +-e CYPRESS_baseUrl='http://tao_nginx' \ +-e CYPRESS_integrationFolder='../var/www/html/tao/views/js' \ +-e CYPRESS_testFiles='**/*.spec.js' \ +--network docker_tao_network \ +cypress/included:3.4.0 +``` + +Breakdown of the above: + +``` +-v $PWD/../../../../../../:/var/www/html # maps your local project root to the webserver's folder in the container +-v $PWD:/cypress # maps the current folder to an arbitrary /cypress folder in the container +-w /cypress # the working directory in the container, where commands are run +-e CYPRESS_baseUrl='http://tao_nginx' # pass this environment variable through +-e CYPRESS_integrationFolder='../var/www/html/tao/views/js' # pass this environment variable through +-e CYPRESS_testFiles='**/*.spec.js' # pass this environment variable through +--network docker_tao_network # make this container join the network the webserver is on +cypress/included:3.4.0 # dockerhub image name & tag +``` + +These parameters obviously depend on the setup of the Dockerised TAO you are testing against. + +## Troubleshooting + +### Connection refused or `ECONNREFUSED 127.0.0.1:80` + +Your URLs or hostnames might be misconfigured. + +The Cypress `baseUrl` should be equal to the web server's `container_name`, which should also match the `ROOT_URL` defined in `/config/generis.conf.php` of your TAO instance. + +For example, in the provided `docker-compose.yml` all three values are set to `tao_nginx`. Other approaches such as `localhost`, `127.0.0.1` or fixed IP addresses are not recommended as they will be more problematic. + +### No tests found + +Your `integrationFolder` + `testFiles` path is not resolving where you expected. + +Make sure you have the `tao-e2e-runner` folder mapped into the `cypress/included` container and the working directory set. Unset any overrides for the above config values, and they should fall back to `exampleTests` + `**/e2e/*.spec.js`. If these local tests are able to be run, it's just a case of working out the correct relative path for the other tests. + +### Timeouts + +If a test is failing due to timed out requests, it could be that the test is not well-written, or it could be due to the slower networking between containers. + +Fortunately, the Cypress timeouts can also be configured by environment variables: + +Defaults: + +- `-e CYPRESS_defaultCommandTimeout=10000` +- `-e CYPRESS_requestTimeout=5000` (suggested: try 10000 or 15000) + +More info on [timeouts](https://docs.cypress.io/guides/references/configuration.html#Timeouts). + +### Cypress DEBUG output + +Add the following environment variable to see some useful logging output which can help to debug your setup: + +- `-e DEBUG=cypress:server:project` + +More info on [debugging](https://docs.cypress.io/guides/guides/debugging.html#Print-DEBUG-logs). diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100755 index 0000000..ffb71dd --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,73 @@ +version: '2' + +services: + tao_nginx: + container_name: tao_nginx + image: nginx:stable + networks: + - tao_network + ports: + - "80:80" + volumes_from: + - tao_phpfpm + volumes: + - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:cached + working_dir: /etc/nginx/conf.d + logging: + driver: "none" + + tao_phpfpm: + container_name: tao_phpfpm + build: + context: ./phpfpm + expose: + - "9000" + networks: + - tao_network + volumes: + - ../../../../../../../:/var/www/html:cached + working_dir: /var/www/html + + tao_mariadb: + container_name: tao_mariadb + image: mariadb:latest + networks: + - tao_network + ports: + - "3306:3306" + environment: + MYSQL_ROOT_PASSWORD: r00t + MYSQL_USER: tao + MYSQL_PASSWORD: tao + MYSQL_DATABASE: tao + volumes: + - tao_mariadb_data:/var/lib/mysql:cached + logging: + driver: "none" + + tao_e2e: + container_name: tao_e2e + image: "cypress/included:3.4.0" + depends_on: + - tao_nginx + - tao_phpfpm + - tao_mariadb + environment: + - CYPRESS_baseUrl=http://tao_nginx # ROOT_URL in TAO generis.conf.php has to match this + - CYPRESS_integrationFolder=../var/www/html/taoQtiTest/views/js/e2e + - CYPRESS_testFiles=**/*.spec.js + networks: + - tao_network + working_dir: /cypress + volumes: + - ../:/cypress + volumes_from: + - tao_phpfpm + +volumes: + tao_mariadb_data: + driver: local + +networks: + tao_network: + driver: bridge diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf new file mode 100755 index 0000000..23df06c --- /dev/null +++ b/docker/nginx/nginx.conf @@ -0,0 +1,46 @@ +server { + index index.php index.html; + + server_name tao.docker; + + error_log /var/log/nginx/error.log; + access_log /var/log/nginx/access.log; + + root /var/www/html; + + location ~ ^/([^//]*)/(views|locales)/. {} + + location /tao/install {} + + location /tao/getFileFlysystem.php { + rewrite ^(.*)$ /tao/getFileFlysystem.php last; + } + + location / { + rewrite ^(.*)$ /index.php; + } + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + sendfile off; + + client_max_body_size 100m; + + location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass tao_phpfpm:9000; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + + fastcgi_intercept_errors off; + fastcgi_buffer_size 16k; + fastcgi_buffers 4 16k; + fastcgi_connect_timeout 300; + fastcgi_send_timeout 300; + fastcgi_read_timeout 300; + } +} \ No newline at end of file diff --git a/docker/phpfpm/Dockerfile b/docker/phpfpm/Dockerfile new file mode 100755 index 0000000..8df7c86 --- /dev/null +++ b/docker/phpfpm/Dockerfile @@ -0,0 +1,50 @@ +FROM php:7.1-fpm + +RUN usermod -u 1000 www-data +RUN usermod -G staff www-data + +RUN apt-get update && apt-get install -y libpng-dev libjpeg-dev libpq-dev zip unzip sudo wget sqlite3 libsqlite3-dev && rm -rf /var/lib/apt/lists/* + +RUN docker-php-ext-configure gd --with-png-dir=/usr --with-jpeg-dir=/usr +RUN docker-php-ext-configure pdo_mysql --with-pdo-mysql=mysqlnd +RUN docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql +RUN docker-php-ext-configure mysqli --with-mysqli=mysqlnd + +RUN yes | pecl install igbinary redis + +RUN docker-php-ext-install pdo && \ + docker-php-ext-install pdo_mysql && \ + docker-php-ext-install mysqli && \ + docker-php-ext-install pgsql && \ + docker-php-ext-install pdo_pgsql && \ + docker-php-ext-install pdo_sqlite && \ + docker-php-ext-install gd && \ + docker-php-ext-install mbstring && \ + docker-php-ext-install opcache && \ + docker-php-ext-install zip && \ + docker-php-ext-install calendar && \ + docker-php-ext-install sockets && \ + docker-php-ext-install pcntl && \ + docker-php-ext-enable igbinary && \ + docker-php-ext-enable redis + +RUN { \ + echo 'opcache.memory_consumption=128'; \ + echo 'opcache.interned_strings_buffer=8'; \ + echo 'opcache.max_accelerated_files=4000'; \ + echo 'opcache.revalidate_freq=2'; \ + echo 'opcache.fast_shutdown=1'; \ + echo 'opcache.enable_cli=1'; \ + echo 'opcache.load_comments=1'; \ +} >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini + +RUN version=$(php -r "echo PHP_MAJOR_VERSION.PHP_MINOR_VERSION;") \ + && curl -A "Docker" -o /tmp/blackfire-probe.tar.gz -D - -L -s https://blackfire.io/api/v1/releases/probe/php/linux/amd64/$version \ + && mkdir -p /tmp/blackfire \ + && tar zxpf /tmp/blackfire-probe.tar.gz -C /tmp/blackfire \ + && mv /tmp/blackfire/blackfire-*.so $(php -r "echo ini_get('extension_dir');")/blackfire.so \ + && printf "extension=blackfire.so\nblackfire.agent_socket=tcp://tao_blackfire:8707\n" > $PHP_INI_DIR/conf.d/blackfire.ini \ + && rm -rf /tmp/blackfire /tmp/blackfire-probe.tar.gz + +RUN rm -rf /var/www/html \ + && chmod 0777 /tmp/ diff --git a/package-lock.json b/package-lock.json index e699450..45859c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@oat-sa/tao-e2e-runner", - "version": "0.3.0", + "version": "0.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -48,9 +48,9 @@ } }, "ajv": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.1.tgz", - "integrity": "sha512-w1YQaVGNC6t2UCPjEawK/vo/dG8OOrVtUmhBT1uJJYxbl5kU2Tj3v6LGqBcsysN1yhuCStJCCA3GqdvKY8sqXQ==", + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1", @@ -322,9 +322,9 @@ } }, "cypress": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-3.4.0.tgz", - "integrity": "sha512-vUE+sK3l23fhs5qTN3dKpveyP0fGr37VmK3FSYaTEjbqC/qh4DbA1Ych/3bLStUpHP4rpE5KAx7i1s/tpdD9vQ==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-3.4.1.tgz", + "integrity": "sha512-1HBS7t9XXzkt6QHbwfirWYty8vzxNMawGj1yI+Fu6C3/VZJ8UtUngMW6layqwYZzLTZV8tiDpdCNBypn78V4Dg==", "dev": true, "requires": { "@cypress/listr-verbose-renderer": "0.4.1", @@ -342,12 +342,11 @@ "extract-zip": "1.6.7", "fs-extra": "5.0.0", "getos": "3.1.1", - "glob": "7.1.3", "is-ci": "1.2.1", "is-installed-globally": "0.1.0", "lazy-ass": "1.6.0", "listr": "0.12.0", - "lodash": "4.17.11", + "lodash": "4.17.15", "log-symbols": "2.2.0", "minimist": "1.2.0", "moment": "2.24.0", @@ -588,9 +587,9 @@ } }, "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -611,9 +610,9 @@ } }, "graceful-fs": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", - "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", "dev": true }, "har-schema": { @@ -968,9 +967,9 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, "lodash.once": { @@ -1199,9 +1198,9 @@ "dev": true }, "psl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", - "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz", + "integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==", "dev": true }, "punycode": { @@ -1300,9 +1299,9 @@ } }, "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { "glob": "^7.1.3" @@ -1330,9 +1329,9 @@ "dev": true }, "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, "shebang-command": { @@ -1529,9 +1528,9 @@ "dev": true }, "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", "dev": true }, "verror": { diff --git a/package.json b/package.json index 3e30c93..e77082e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@oat-sa/tao-e2e-runner", - "version": "0.3.0", + "version": "0.4.0", "description": "End to end test runner", "main": "index.js", "scripts": { @@ -22,7 +22,7 @@ }, "homepage": "https://github.com/oat-sa/tao-e2e-runner#readme", "devDependencies": { - "cypress": "^3.2.0", + "cypress": "^3.4.1", "cypress-file-upload": "^3.2.1" }, "peerDependencies": {