diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml new file mode 100644 index 0000000..4dd8351 --- /dev/null +++ b/.github/workflows/e2e-tests.yml @@ -0,0 +1,141 @@ +# Reusable Playwright e2e workflow, kept separate from `continuous-integration.yml` +# because it needs a dedicated e2e_testing database/environment and browser +# install, which the CI workflow does not provide. +name: "End-to-end tests" + +on: + workflow_call: + inputs: + plugin-key: + required: true + type: string + glpi-version: + required: true + type: string + php-version: + required: true + type: string + db-image: + required: true + type: string + +jobs: + e2e: + name: "Playwright" + runs-on: "ubuntu-latest" + container: + image: "ghcr.io/glpi-project/githubactions-glpi-apache:php-${{ inputs.php-version }}-glpi-${{ inputs.glpi-version }}" + # `--user github-actions-runner` is mandatory to prevent rights issues on mounted volume during checkout operation + options: >- + --volume ${{ github.workspace }}:/var/www/glpi/plugins:rw + --user github-actions-runner + services: + db: + image: "ghcr.io/glpi-project/githubactions-${{ inputs.db-image }}" + env: + MYSQL_ALLOW_EMPTY_PASSWORD: "yes" + MYSQL_DATABASE: "glpi_e2e" + options: >- + --shm-size=1g + defaults: + run: + # By default, execute commands using the `www-data` user to prevent rights issues on GLPI generated files. + shell: "sudo --set-home --user=www-data --preserve-env bash --noprofile --norc -eo pipefail {0}" + working-directory: "/var/www/glpi" + env: + CACHE_DIR: "/home/www-data/.cache/e2e-tests-workflow" + steps: + - name: "Validate plugin-key" + shell: "bash" + run: | + [[ "${{ inputs.plugin-key }}" =~ ^[a-z][a-z0-9_-]*$ ]] || { echo "Invalid plugin-key"; exit 1; } + + - name: "Checkout" + uses: "actions/checkout@v7" + with: + path: "${{ inputs.plugin-key }}" + + - name: "Configure cache directories" + # sudo needs the `github-actions-runner` user (only one with passwordless sudo). + # working-directory is the plugin dir so `composer config` writes to its local + # composer.json, readable regardless of which user runs later composer commands. + shell: "bash" + working-directory: "/var/www/glpi/plugins/${{ inputs.plugin-key }}" + run: | + sudo mkdir --parents "${{ env.CACHE_DIR }}/composer" + sudo chown --recursive github-actions-runner "${{ env.CACHE_DIR }}" + composer config cache-dir "${{ env.CACHE_DIR }}/composer" + + - name: "Restore cache" + uses: "actions/cache@v6" + with: + path: | + ${{ env.CACHE_DIR }} + key: "${{ inputs.plugin-key }}-dependencies-${{ inputs.php-version }}-${{ github.job }}-${{ hashFiles(format('{0}/composer.lock', inputs.plugin-key)) }}" + restore-keys: | + ${{ inputs.plugin-key }}-dependencies-${{ inputs.php-version }}-${{ github.job }}- + ${{ inputs.plugin-key }}-dependencies-${{ inputs.php-version }}- + ${{ inputs.plugin-key }}-dependencies- + + - name: "Fix directories ACL" + # Runs after cache restore so cache-extracted files (owned by github-actions-runner) get the ACL too. + shell: "bash" + run: | + sudo setfacl --recursive --modify u:www-data:rwx "${{ env.CACHE_DIR }}" + sudo setfacl --recursive --modify u:www-data:rwx "/var/www/glpi/plugins/${{ inputs.plugin-key }}" + + - name: "Mark plugin directory as safe for git" + run: | + git config --global --add safe.directory "/var/www/glpi/plugins/${{ inputs.plugin-key }}" + + - name: "Install composer dependencies" + if: ${{ hashFiles(format('{0}/composer.json', inputs.plugin-key)) != '' }} + working-directory: "/var/www/glpi/plugins/${{ inputs.plugin-key }}" + run: | + composer install --ansi --no-interaction --no-progress --prefer-dist + + - name: "Install e2e database" + # Port 8090 is the vhost bound to GLPI_ENVIRONMENT_TYPE=e2e_testing in + # this image; port 80 serves a different (unconfigured) environment. + run: | + bin/console database:install --ansi --no-interaction --force --reconfigure --no-telemetry \ + --db-host=db --db-name=glpi_e2e --db-user=root --db-password="" \ + --env=e2e_testing + bin/console config:set url_base "http://localhost:8090" --env=e2e_testing + bin/console config:set url_base_api "http://localhost:8090/apirest.php" --env=e2e_testing + + - name: "Install and activate plugin" + run: | + bin/console plugin:install --ansi --no-interaction --username=glpi "${{ inputs.plugin-key }}" --env=e2e_testing + bin/console plugin:activate --ansi --no-interaction "${{ inputs.plugin-key }}" --env=e2e_testing + + - name: "Run apache" + shell: "bash" + run: | + sudo service apache2 start + + - name: "Install Playwright browser" + # Not cached: Playwright recommends against it (restore time ≈ download time), + # and OS deps below are reinstalled on every run regardless. + run: | + npx playwright install chromium + + - name: "Install Playwright system dependencies" + # Needs passwordless sudo to install system packages as root. + shell: "bash" + run: | + npx playwright install-deps chromium + + - name: "Playwright" + env: + E2E_BASE_URL: "http://localhost:8090" + run: | + npx playwright test --project="plugin:${{ inputs.plugin-key }}" --reporter=html + + - name: "Upload Playwright report" + if: ${{ !cancelled() }} + uses: "actions/upload-artifact@v7" + with: + name: "playwright-report-${{ inputs.glpi-version }}-${{ inputs.php-version }}" + path: "/var/www/glpi/playwright-report" + retention-days: 7 diff --git a/README.md b/README.md index d9951ba..bce6e8a 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,48 @@ On pull requests, the workflow checks that the `CHANGELOG` file has been updated On pull requests that modify `plugin.xml` or `.xml`, the workflow also validates that all URLs declared in the file are reachable. URLs inside `` tags that are newly introduced by the PR only produce a warning (the release archive may not be published yet), while all other invalid URLs fail the check. +## End-to-end tests workflow + +This workflow runs the plugin's Playwright specs (`tests/e2e/specs`) against a dedicated `e2e_testing` GLPI environment. +It is kept separate from the `continuous-integration.yml` workflow because it needs its own database and a browser install, which the CI workflow does not provide. + +```yaml +name: "End-to-end tests" + +on: + push: + branches: + - "main" + tags: + - "*" + pull_request: + workflow_dispatch: + +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + e2e: + uses: "glpi-project/plugin-ci-workflows/.github/workflows/e2e-tests.yml@v1" + with: + # The plugin key (system name). + plugin-key: "myplugin" + + # The version of GLPI on which to run the tests. + glpi-version: "12.0.x" + + # The version of PHP on which to run the tests. + php-version: "8.3" + + # The database docker image on which to run the tests. + db-image: "mariadb:10.6" +``` + +See the [Continuous integration workflow](#continuous-integration-workflow) section above for the available `glpi-version`/`php-version`/`db-image` combinations. + +The plugin specs are auto-discovered by GLPI's own `playwright.config.ts` as the `plugin:` project, so no plugin-side Playwright configuration is required beyond the `tests/e2e/specs` directory. + ## Code coverage Code coverage is automatically enabled when a `.glpi-coverage.json` configuration file is present at the root of the plugin directory. @@ -178,6 +220,19 @@ jobs: php-version: "${{ matrix.php-version }}" db-image: "${{ matrix.db-image }}" + e2e: + name: "Playwright - GLPI ${{ matrix.glpi-version }} - php:${{ matrix.php-version }} - ${{ matrix.db-image }}" + needs: "generate-ci-matrix" + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.generate-ci-matrix.outputs.matrix) }} + uses: "glpi-project/plugin-ci-workflows/.github/workflows/e2e-tests.yml@v1" + with: + plugin-key: "myplugin" + glpi-version: "${{ matrix.glpi-version }}" + php-version: "${{ matrix.php-version }}" + db-image: "${{ matrix.db-image }}" + coverage-report: if: github.event_name == 'pull_request' needs: "ci"