Skip to content

Update README to change chunk file suffix #108

Update README to change chunk file suffix

Update README to change chunk file suffix #108

Workflow file for this run

name: Playwright tests 🎭
# The only browser-driven suite in CI — it replaced the Behat workflow, whose
# environment provisioning steps (up through MySQL) this one inherits.
#
# There is deliberately no Chrome/Chromedriver/Selenium/Java/Xvfb stack here:
# tests/playwright/features/actionInstall.feature installs Chamilo by clicking
# through the legacy web installer at /main/install/index.php, so Playwright's
# own browser handles both installing the app and testing it — one
# browser-automation stack for the whole job, not two.
on: [ push, pull_request ]
jobs:
build:
name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }}
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
operating-system: [ ubuntu-latest ]
# 8.4/8.5 temporarily disabled: focusing on getting the full suite green on
# 8.3 first, then re-enabling the rest — that split also makes any future
# failure easier to read as "version-specific" instead of muddying it with
# whatever's still being worked out on 8.3 alone. Re-enable by uncommenting.
php-versions: [ '8.3' ] # , '8.4', '8.5'
steps:
- name: Checkout
uses: actions/checkout@v7
# As of PHP 8.5, opcache is compiled into core (php-src RFC "Make OPcache a
# non-optional part of PHP") and no longer ships as its own loadable
# extension/package — there's no php8.5-opcache in ondrej/php, and
# requesting the "opcache" extension by name would fail. Both spots below
# that reference it (this apt package list, and the extensions: list two
# steps down) skip it for 8.5 accordingly. Computed here in plain bash
# rather than a GitHub Actions `${{ cond && a || b }}` expression — a first
# attempt at that pattern broke on the classic JS-ternary-via-&&/||
# pitfall (the "true" branch value was '', which is falsy, so `||` fell
# through to the fallback branch instead — i.e. it did the opposite of
# what was intended, and 8.5 tried to install php8.5-opcache anyway,
# failing the whole apt-get install command). Extend the version check if
# a future PHP version keeps this behavior, since it isn't range-proofed.
- name: Setup apache
run: |
PHP_VERSION="${{ matrix.php-versions }}"
OPCACHE_PACKAGE="php${PHP_VERSION}-opcache"
if [ "$PHP_VERSION" = "8.5" ]; then
OPCACHE_PACKAGE=""
fi
sudo apt-get install software-properties-common
sudo add-apt-repository ppa:ondrej/php
sudo apt-get update
sudo apt-get install -y nodejs npm apache2 \
php${PHP_VERSION} \
php${PHP_VERSION}-common \
php${PHP_VERSION}-cli \
php${PHP_VERSION}-mysql \
php${PHP_VERSION}-bcmath \
php${PHP_VERSION}-curl \
php${PHP_VERSION}-gd \
php${PHP_VERSION}-intl \
php${PHP_VERSION}-ldap \
php${PHP_VERSION}-mbstring \
$OPCACHE_PACKAGE \
php${PHP_VERSION}-soap \
php${PHP_VERSION}-xml \
php${PHP_VERSION}-zip \
libapache2-mod-php${PHP_VERSION}
sudo a2enmod rewrite actions
sudo chmod 777 -R $HOME
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: bcmath, ctype, curl, dom, gd, iconv, intl, json, ldap, mbstring, pdo_mysql, mysqli, soap, xml, zip${{ matrix.php-versions != '8.5' && ', opcache' || '' }}
ini-values: post_max_size=256M, max_execution_time=600, memory_limit=4096M, date.timezone=Europe/Paris, opcache.enable=1, opcache.memory_consumption=64M
- name: Get composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Install dependencies with composer
run: composer install --no-progress
- name: Install assets
run: php bin/console assets:install
- name: Install Yarn
run: |
sudo npm i -g npm
node --version
npm --version
sudo corepack enable
yarn set version stable
yarn --version
- name: Install JS dependencies
run: yarn install
- name: Yarn run encore
run: yarn run encore production
- name: Start mysql service
run: sudo /etc/init.d/mysql start
- name: Install Playwright browsers
run: yarn playwright install --with-deps chromium
# Deliberately last, right before Apache is actually needed, not
# right after `apt-get install apache2` back in "Setup apache".
# A previous run showed 000-default.conf was still the stock
# /var/www/html vhost by this point in the job, despite "Setup
# apache" copying gh-apache into it and re-enabling the site
# without error — something in between (most likely
# shivammathur/setup-php@v2, which manages the Apache/PHP
# integration) was resetting it. Applying the vhost swap here
# instead, as the last write before first use, sidesteps that
# regardless of which exact step was clobbering it.
- name: Configure apache vhost
run: |
sudo a2dissite 000-default.conf
sudo cp -f ${{ github.workspace }}/.github/gh-apache /etc/apache2/sites-available/000-default.conf
sudo a2ensite 000-default.conf
sudo systemctl restart apache2
- name: Check apache is up
run: |
sudo chmod 777 -R ${{ github.workspace }}
curl http://localhost/main/install/index.php
- name: Install chamilo
env:
BASE_URL: http://localhost
run: |
yarn test:playwright:install
sed -i "s/APP_ENV='dev'/APP_ENV='prod'/g" .env
sed -i "s/APP_DEBUG='1'/APP_DEBUG='0'/g" .env
if grep -q "APP_ENV='prod'" .env; then
printf 'framework:\n http_cache: true\n' > config/packages/prod/framework.yaml
fi
sudo php bin/console cache:clear
sudo chmod -R 777 var
# Seeds the fixed test accounts (mmosquera, acostea, ptook, etc.)
# that the rest of the suite logs in as via "I am a teacher" /
# "I am a student" / etc. — kept as its own step, run once,
# separate from "Playwright tests" below, same reasoning as
# keeping "Install chamilo" separate.
- name: Seed test users
env:
BASE_URL: http://localhost
run: yarn test:playwright:seed
# Creates the "TEMP" course (which becomes cid=3 on this fresh
# install) as a one-time setup fixture, same reasoning as "Seed
# test users" above: course_user_registration.feature and every
# remaining course-tool feature file (toolAgenda, toolForum,
# toolLp, etc. — 14 in total as of this writing) hardcode cid=3,
# assuming "TEMP" already exists. Left as its own step, run once,
# BEFORE the main parallel "Playwright tests" step below, so
# every other feature file can rely on it regardless of which
# worker picks them up or what order they run in — a real CI
# failure showed course_user_registration.feature's first
# scenario reaching that course before course.feature's own
# course-creation scenario (in a different file, different worker)
# had even run.
#
# WHY cid=3 AND NOT cid=1 (changed 2026-08-19): the installer's
# own DemoCoursesFixtures (fixture group "install", loaded by the
# plain `doctrine:fixtures:load` this workflow's "Install chamilo"
# step runs) now creates TWO demo courses before any test-suite
# seeding happens at all — "AI Act" (cid=1) and "Using Chamilo"
# (cid=2), added to the product on 2026-08-08/2026-08-11, i.e.
# AFTER this Playwright suite was originally written against the
# older "TEMP is the first course, so it gets id 1" assumption.
# That assumption is now permanently false, so the whole suite was
# migrated cid=1 -> cid=3 in one pass. Note this failed QUIETLY
# before the migration: cid=1 is a real, working course, so
# cid=1-based scenarios kept passing while silently exercising the
# AI Act demo course instead of TEMP.
#
# This step must create EXACTLY ONE course for cid=3 to be
# deterministic. `--grep "Create a course before testing"` used to
# match TWO scenarios — course.feature's ("TEMP") and
# toolExerciseTeacher.feature's ("EXTEACH") — so on a multi-worker
# pool whichever finished first took id 3 and the other took id 4,
# a genuine coin flip (observed both ways: TEMP won on real CI,
# EXTEACH won locally at 43.7s vs 54.6s). Fixed at the source by
# renaming that other scenario to "Create the exercise teacher
# course before testing", so this grep no longer matches it and
# EXTEACH is created inline as its own file's first scenario
# instead (it is referenced only by course CODE, never by cid, so
# its id is irrelevant — see that file's comment). The
# --workers=1 in package.json's test:playwright:seed-course is
# kept purely as a guard: if a future feature file ever adds a
# scenario matching this grep again, single-worker + the config's
# `fullyParallel: false` keeps creation order deterministic
# (sorted by file path, so "course.feature" still goes first)
# instead of silently reshuffling every cid=3 URL in the suite.
- name: Seed test course
env:
BASE_URL: http://localhost
run: yarn test:playwright:seed-course
# Same reasoning as "Seed test course" above, for the OTHER course
# fixture: sessionAccess.feature (new) selects this course by its
# code, "TEMPPRIVATE", to attach to the sessions it creates.
# course.feature's own "Create a private course before testing"
# scenario is what creates it, but wasn't part of the dedicated
# seed step (only "Create a course before testing"/TEMP was) — the
# exact same cross-file race gotcha already fixed once for TEMP/
# cid=1 would otherwise apply here too, just for a course "TEMPPRIVATE"
# is looked up by code instead of by an assumed id.
- name: Seed private course
env:
BASE_URL: http://localhost
run: yarn test:playwright:seed-private-course
# Real, confirmed root cause: GroupSettingsSchema.php's schema
# default for 'allow_group_categories' is 'false'. toolGroup.
# feature's entire category/group listing depends on it being
# 'true' for the whole file's run. adminSettings.feature's own
# "Update 'allow_group_categories' setting" scenario sets it to
# 'Yes', but that Feature is tagged @settings and its BeforeAll/
# AfterAll pair only restores it back to whatever was snapshotted
# BEFORE that scenario ran — 'false' on a fresh install — once
# its own scenarios finish. Since different feature files still
# run concurrently across workers, toolGroup.feature only saw
# 'true' during the narrow window while adminSettings.feature's
# own scenario had it temporarily flipped — a real CI run showed
# toolGroup.feature's very first scenario seeing genuinely zero
# categories rendered, no server error, right after creating one.
# Setting it here, once, before the main batch starts, closes
# that race for the whole run (it also makes adminSettings.
# feature's own snapshot see 'true' as the "current" value, so
# its restore-at-the-end keeps it 'true' too, not 'false').
- name: Seed platform settings
env:
BASE_URL: http://localhost
run: yarn test:playwright:seed-settings
# NOTE (2026-08-23): a fifth seed step ("Seed course
# subscriptions", subscribing acostea/fapple to TEMP) briefly lived
# here and was REMOVED again on purpose. It is redundant now that
# playwright.config.ts pins `workers: 1`: file order is fixed, and
# course_user_registration.feature sorts before toolGroup.feature,
# so the subscriptions exist by the time anything needs them.
#
# It was also actively harmful. Hoisting those scenarios out of
# their own file broke that file's internal narrative (it
# subscribes amann, unsubscribes him, then re-subscribes him as a
# fixture): with amann pre-subscribed, its first scenario had
# nobody left to select, because subscribe_user.php's picker
# excludes already-subscribed users. And it could never have fixed
# the failures it targeted anyway — a seed guarantees a fixture
# exists at the START, but course TEMP's user list is written by
# four different feature files, so under parallel execution another
# file could still unsubscribe fapple mid-run (measured: it did).
# Ordering, not more seeding, is the actual fix. See
# playwright.config.ts's `workers: 1` comment for the full history.
# The step result is decided by check-results.mjs, NOT by
# `playwright test`'s exit code. That is deliberate: the exit code is
# non-zero for anything that went wrong anywhere in the process,
# including work that is not a test, so it cannot distinguish "a test
# regressed" from "a cleanup hook was slow". Twice in two days this
# job went red on runs that were 410/410 green, because playwright-bdd
# defers every tagged AfterAll into one worker-teardown phase that
# blew its 90s budget AFTER the last test had already passed.
#
# A cleanup step running once every test has finished cannot
# invalidate those tests, and two attempts at making that teardown fit
# its budget both failed — so the durable fix is to stop asking the
# exit code a question it cannot answer. The checker still fails the
# build on any failed test, and on a TRUNCATED run (--min-tests),
# which is the loophole a naive `|| true` would leave open. Non-test
# errors are printed as warnings so they stay visible and fixable.
- name: Playwright tests
env:
BASE_URL: http://localhost
run: |
yarn test:playwright || echo "::warning::playwright exited non-zero — deferring to the result check below"
node tests/playwright/scripts/check-results.mjs \
--report var/test-results/playwright/results.json \
--min-tests 439 \
--label "main batch"
# MUST come before the SpecialCase1 step below, not at the end of
# the job with the other uploads: `playwright test` wipes its own
# outputDir at the START of every invocation, so the SpecialCase1
# run would delete this batch's HTML report, traces and
# screenshots before any end-of-job upload step could collect
# them. Two separately-named artifacts is the only way to keep
# both batches' evidence.
- name: Upload main-batch Playwright report on failure
if: failure()
uses: actions/upload-artifact@v7
with:
name: playwright-report-main-php${{ matrix.php-versions }}
path: |
var/test-results/playwright/report
var/test-results/playwright/results
var/test-results/playwright/results.json
retention-days: 7
if-no-files-found: ignore
# SpecialCase1 runs in its own SEQUENTIAL step, excluded from the
# parallel batch above via the @specialcase1 tag (see
# package.json's --grep/--grep-invert pair). Three separate
# reasons, any one of which would justify it:
#
# 1. ORDERING. specialCase1Sessions.feature hard-depends on
# specialCase1PlatformSettings.feature having run first — the
# session extra fields (extra_domaine/extra_theme_fr/
# extra_theme_de/extra_ecouter) its 4 session scenarios fill in
# only exist because "Add minimal session extra fields" creates
# them, and its teacher-creation scenario needs
# admins_can_set_users_pass, which that same file enables. In
# the parallel batch there was NO ordering guarantee between
# two different files at all. With --workers=1 and the config's
# `fullyParallel: false`, files run in sorted order, and
# "specialCase1PlatformSettings" sorts before
# "specialCase1Sessions" — so the dependency is satisfied by
# construction.
# 2. COLLATERAL DAMAGE. specialCase1PlatformSettings mutates ~100
# platform settings, several of which are global and UI-visible
# on EVERY page (cookie_warning renders a fixed bottom banner
# that intercepts pointer events; allow_registration/
# allow_terms_conditions change the header on every page). Its
# own "Tear down" scenario restores them, but only at the END —
# so while it ran, every concurrently-executing file saw the
# mutated platform. Running it alone closes that window
# entirely.
# 3. WORKER STARVATION. Its scenarios are tagged @long-scenario
# (15-minute budget) and genuinely take minutes each. Dropping
# several of those into the shared worker pool measurably
# starved unrelated files: the CI run that first enabled them
# produced fresh 90s timeouts in companyReports.feature and
# accessCompanyReports.feature, which had passed immediately
# before, plus a session-bleed failure in
# admin/fileIntegrity.feature.
#
# `if: always()` so this still runs when the batch above fails —
# SpecialCase1 derives from a real customer project, so its signal
# must not be hidden behind an unrelated failure elsewhere. Placed
# AFTER the batch (not before) so the batch sees pristine,
# never-yet-mutated platform settings.
# Same exit-code-vs-results reasoning as the main batch above.
# --min-tests is this batch's own floor (11 scenarios), not the
# suite-wide one — a shared floor would let a truncated SpecialCase1
# run slip through.
#
# NOTE: test:playwright:specialcase1 deliberately passes
# `--retries=0 --trace=retain-on-failure`, overriding the config's
# `retries: 1` / `trace: "on-first-retry"`. Retries are WRONG for this
# batch, and measured to be: a retry gets a fresh browser but the SAME
# database, and these scenarios are heavily non-idempotent (they create
# sessions, courses and extra fields). Observed on 2026-08-26 — "Add
# minimal session extra fields" failed attempt 0 on the terms-and-
# conditions interstitial, then attempt 1 failed with a COMPLETELY
# different error ("Add courses to this session" not found) because
# attempt 0 had already created the session. One clear failure became
# two confusing ones.
# `--trace=retain-on-failure` is the necessary partner: with retries
# off, "on-first-retry" would never capture a trace at all, so a
# SpecialCase1 failure would arrive with no evidence.
# The main batch keeps retries — its scenarios largely clean up after
# themselves, and there the retry is what absorbs the roving
# `browserContext._wrapApiCall ... traceName` Playwright-internal error.
- name: Playwright SpecialCase1 tests
if: always()
env:
BASE_URL: http://localhost
run: |
yarn test:playwright:specialcase1 || echo "::warning::playwright exited non-zero — deferring to the result check below"
node tests/playwright/scripts/check-results.mjs \
--report var/test-results/playwright/results.json \
--min-tests 11 \
--label "SpecialCase1"
# Whatever is left in outputDir at the end of the job — i.e. the
# SpecialCase1 step's own output, since it runs last and wiped the
# main batch's (uploaded separately above under
# playwright-report-main-*). Kept on the original artifact name so
# existing habits/tooling still find something here.
- name: Upload Playwright report on failure
if: failure()
uses: actions/upload-artifact@v7
with:
name: playwright-report-php${{ matrix.php-versions }}
path: |
var/test-results/playwright/report
var/test-results/playwright/results
var/test-results/playwright/results.json
retention-days: 7
if-no-files-found: ignore
- name: Upload provisioning logs on failure
if: failure()
uses: actions/upload-artifact@v7
with:
name: provisioning-logs-php${{ matrix.php-versions }}
path: /var/log/apache2/error.log
retention-days: 7
if-no-files-found: ignore