Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ DATABASE_URL=postgres://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:$
USE_UV=1
PROCONNECT_ACTIVATED=True
HOST_PROTO = "http"

# Demo site environment
SITE_NAME="Sites Conformes"
MEDIA_ROOT=medias
18 changes: 18 additions & 0 deletions .github/actions/setup-e2e/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Setup E2E Tools
description: Install Node.js, npm dependencies, and Playwright Chromium

runs:
using: composite
steps:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "24"

- name: Install Node dependencies
shell: bash
run: npm ci

- name: Install Playwright browsers
shell: bash
run: npx playwright install chromium --with-deps
24 changes: 24 additions & 0 deletions .github/actions/start-django-server/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Start Django Server
description: Start Django dev server and wait for it to be ready

inputs:
base-url:
description: "URL to poll for readiness"
required: false
default: "http://127.0.0.1:8000"
timeout:
description: "Milliseconds to wait (passed to wait-on)"
required: false
default: "30000"

runs:
using: composite
steps:
- name: Run server
shell: bash
# Debug is required to get media working
run: DEBUG=True uv run python manage.py runserver 127.0.0.1:8000 &

- name: Wait for server to be ready
shell: bash
run: npx wait-on ${{ inputs.base-url }} --timeout ${{ inputs.timeout }}
136 changes: 136 additions & 0 deletions .github/workflows/_e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
name: End-to-end test job

on:
workflow_call:
inputs:
ref:
required: true
type: string
compare:
required: true
type: boolean

env:
DJANGO_SETTINGS_MODULE: config.settings_test
SCREENSHOTS_BASE_PATH: __screenshots__

jobs:
end-to-end:
name: Playwright (${{ inputs.ref }})
runs-on: ubuntu-latest
timeout-minutes: 30
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_USER: dju
POSTGRES_PASSWORD: djpwd
POSTGRES_DB: djdb
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 1s
--health-timeout 5s
--health-retries 30

steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}

# When running against main, overlay current-branch test files so both
# baseline and comparison jobs use the same test definitions.
- name: Checkout E2E files from PR branch
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
sparse-checkout: |
playwright.config.ts
pyproject.toml
e2e/
.github/
.env.test
justfile
scripts/
path: __e2e_overlay__

- name: Overlay E2E files onto working tree
shell: bash
run: |
shopt -s dotglob
cp -r __e2e_overlay__/* .
rm -rf __e2e_overlay__

- name: Install just
uses: extractions/setup-just@v2

- name: Set up Python
uses: actions/setup-python@v4

- name: Set up uv
uses: astral-sh/setup-uv@v5

- name: Install Python dependencies
run: uv sync --no-group dev

- name: Copy .env.test to .env
run: cp .env.test .env

- name: Collect static files
run: just collectstatic

- name: Deploy starter content + demo pages
run: |
just init
uv run python manage.py create_demo_pages

- name: Setup E2E tools
uses: ./.github/actions/setup-e2e

- name: Start Django server
uses: ./.github/actions/start-django-server

- name: Download baseline screenshots
if: inputs.compare
uses: actions/download-artifact@v4
with:
name: regression-screenshots
path: ${{ env.SCREENSHOTS_BASE_PATH }}

- name: Run Playwright tests (full suite + generate snapshots)
if: ${{ !inputs.compare }}
run: npx playwright test --update-snapshots

- name: Run Playwright tests (regression comparison only)
if: inputs.compare
run: npx playwright test --grep=@regression

- name: Upload baseline screenshots
if: ${{ !inputs.compare }}
uses: actions/upload-artifact@v4
with:
name: regression-screenshots
path: ${{ env.SCREENSHOTS_BASE_PATH }}

- name: Upload Playwright report
if: ${{ !cancelled() && !inputs.compare }}
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 7

- name: Upload diff images on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-diffs
path: |
**/*-expected.png
**/*-actual.png
**/*-diff.png

permissions:
contents: read
95 changes: 95 additions & 0 deletions .github/workflows/ci-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: 🎭 CI - E2E tests

on: [push, pull_request]

jobs:
e2e:
name: Baseline (current branch)
uses: ./.github/workflows/_e2e.yml
with:
ref: ${{ github.ref }}
compare: false
permissions:
contents: read

e2e-compare:
name: Compare (main)
needs: e2e
uses: ./.github/workflows/_e2e.yml
with:
ref: main
compare: true
permissions:
contents: read
continue-on-error: true

comment-visual-diffs:
name: Comment visual regression diffs
needs: e2e-compare
if: |
needs.e2e-compare.result == 'failure' &&
github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write

steps:
- uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0

- name: Download Playwright diffs
uses: actions/download-artifact@v4
with:
name: playwright-diffs
path: diffs

- name: Push diff images to branch
id: push-images
env:
GH_TOKEN: ${{ github.token }}
run: |
BRANCH="pr-${{ github.event.pull_request.number }}-screenshots"
git checkout --orphan "$BRANCH"
git rm -rf --quiet . 2>/dev/null || true
git clean -fdx -e diffs
find diffs -name '*.png' -exec git add {} +
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git commit -m "Visual regression diffs for PR #${{ github.event.pull_request.number }}"
git push --force origin "$BRANCH"
echo "branch=$BRANCH" >> $GITHUB_OUTPUT

# - name: Build PR comment body
# env:
# BRANCH: ${{ steps.push-images.outputs.branch }}
# REPO: ${{ github.repository }}
# run: |
# RAW_BASE="https://raw.githubusercontent.com/${REPO}/${BRANCH}"
# {
# echo "## Régression visuelle détectée"
# echo ""
# echo "| Test | Avant (main) | Après (PR) | Diff |"
# echo "|------|--------------|------------|------|"
# for expected in $(find diffs -name '*-expected.png' | sort); do
# actual="${expected/-expected/-actual}"
# diff_img="${expected/-expected/-diff}"
# test_name=$(basename "$(dirname "$expected")" | sed 's/-chromium.*//;s/-/ /g')
# echo "| ${test_name} | ![avant](${RAW_BASE}/${actual}) | ![après](${RAW_BASE}/${expected}) | ![diff](${RAW_BASE}/${diff_img}) |"
# done
# echo ""
# echo "_Generated by Playwright visual regression tests._"
# } > comment_body.md

# - name: Post PR comment
# env:
# GH_TOKEN: ${{ github.token }}
# run: |
# gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments \
# -F body=@comment_body.md

permissions:
pull-requests: write
contents: read
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,7 @@ dmypy.json
config.json
cron.json
temp.json

# Playwright artifacts
test-results
__screenshots__
7 changes: 1 addition & 6 deletions blog/templates/blog/blocks/contact_card.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,7 @@ <h3 class="fr-card__title">{{ value.name }}</h3>
</div>
<div class="cmsfr-author_card__header fr-card__header">
<div class="fr-card__img">
{% image value.image fill-200x200 as contact_image %}
<img class="fr-responsive-img cmsfr-author-img"
src="{{ contact_image.url }}"
width="4.5em"
height="4.5em"
alt="" />
{% picture value.image fill-200x200 format-{avif,webp,jpeg} preserve-svg class="fr-responsive-img cmsfr-author-img" alt="" width="4.5em" height="4.5em" %}
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ def show_toolbar(request):
("mega_menu_section_16", "Catégorie de méga-menu 16"),
)

WAGTAILIMAGES_EXTENSIONS = ["gif", "jpg", "jpeg", "png", "webp", "svg"]
WAGTAILIMAGES_EXTENSIONS = ["gif", "jpg", "jpeg", "png", "webp", "svg", "avif"]
SF_SCHEME_DEPENDENT_SVGS = True if os.getenv("SF_SCHEME_DEPENDENT_SVGS", False) in ["1", "True"] else False

# Allows for complex Streamfields without completely removing checks
Expand Down
53 changes: 52 additions & 1 deletion content_manager/management/commands/create_demo_pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,15 @@
from content_manager.utils import get_default_site, import_image
from forms.models import FormField, FormPage

ALL_ALLOWED_SLUGS = ["blog_index", "publications", "menu_page", "form", "common_blocks", "hero_blocks"]
ALL_ALLOWED_SLUGS = [
"blog_index",
"publications",
"menu_page",
"form",
"common_blocks",
"hero_blocks",
"image_examples",
]

fake = Faker("fr_FR")

Expand Down Expand Up @@ -83,6 +91,8 @@ def handle(self, *args, **kwargs):
menu_page = ContentPage.objects.get(slug="menu_page", locale=locale)
self.create_hero_blocks_page(home_page=home_page, parent_page=menu_page)

elif slug == "image_examples":
self.create_image_examples_page(parent_page=home_page)
else:
raise ValueError(f"Valeur inconnue : {slug}")

Expand Down Expand Up @@ -313,6 +323,47 @@ def create_form_page(self, slug: str, parent_page: ContentPage) -> None:

self.stdout.write(self.style.SUCCESS(f"Page {slug} created with id {form_page.id}"))

def create_image_examples_page(self, parent_page: ContentPage) -> None:
"""
Creates a page showcasing the available illustration images.
"""
slug = "image_examples"
already_exists = ContentPage.objects.filter(slug=slug).first()
if already_exists:
self.stdout.write(f"The page seem to already exist with id {already_exists.id}")
return

illustrations_dir = os.path.join(settings.BASE_DIR, "static", "illustration")
image_files = [
("Placeholder-Sites-Faciles.png", "Placeholder Sites Faciles"),
("illustration-sites-faciles-homme-nuages.png", "Illustration Sites Faciles - Homme et nuages"),
("illustration-sites-faciles-femme-ordinateur.png", "Illustration Sites Faciles - Femme à l'ordinateur"),
]

body = []
body.append(("paragraph", RichText("<p>Exemples d'images disponibles dans le projet.</p>")))

for filename, title in image_files:
full_path = os.path.join(illustrations_dir, filename)
if not os.path.exists(full_path):
self.stdout.write(self.style.WARNING(f"Image not found, skipping: {full_path}"))
continue

image = import_image(full_path, title)
body.append(
(
"image",
{
"image": image,
"alt": title,
"width": "",
},
)
)

get_or_create_content_page(slug, title="Exemples d'images", body=body, parent_page=parent_page)
self.stdout.write(self.style.SUCCESS("Image examples page created"))

def create_common_blocks_page(self, parent_page: ContentPage) -> None:
"""
Creates a page showcasing all blocks in STREAMFIELD_COMMON_BLOCKS.
Expand Down
Loading