diff --git a/.ddev/.env.playwright b/.ddev/.env.playwright new file mode 100644 index 0000000..72ff1a8 --- /dev/null +++ b/.ddev/.env.playwright @@ -0,0 +1 @@ +PLAYWRIGHT_DOCKER_IMAGE="mcr.microsoft.com/playwright:v1.64.1-noble" diff --git a/.ddev/addon-metadata/playwright/manifest.yaml b/.ddev/addon-metadata/playwright/manifest.yaml new file mode 100644 index 0000000..5448109 --- /dev/null +++ b/.ddev/addon-metadata/playwright/manifest.yaml @@ -0,0 +1,15 @@ +name: playwright +repository: julienloizelet/ddev-playwright +version: v3.1.0 +install_date: "2026-10-25T09:07:45+01:00" +project_files: + - commands/playwright/playwright + - commands/playwright/playwright-install + - commands/playwright/playwright-init + - playwright-build/Dockerfile + - playwright-build/kasmvnc.yaml + - playwright-build/xstartup + - playwright-build/entrypoint.sh + - docker-compose.playwright.yaml +global_files: [] +removal_actions: [] diff --git a/.ddev/commands/playwright/playwright b/.ddev/commands/playwright/playwright new file mode 100755 index 0000000..a326c0b --- /dev/null +++ b/.ddev/commands/playwright/playwright @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +#ddev-generated +# Remove the line above if you don't want this file to be overwritten when you run +# ddev add-on get julienloizelet/ddev-playwright +# +# This file comes from https://github.com/julienloizelet/ddev-playwright +# +## Description: Run playwright command +## Usage: playwright [options] [command] +## Example: "ddev playwright test" +cd /var/www/html || exit 1 +cd "${PLAYWRIGHT_TEST_DIR}" || exit 1 + +export PLAYWRIGHT_BROWSERS_PATH=0 + +if [ -f "package-lock.json" ]; then + npx playwright "$@" +elif [ -f "yarn.lock" ]; then + yarn playwright "$@" +else + echo "ERROR: could not find lock file for a supported package manager (npm, yarn) in '${PLAYWRIGHT_TEST_DIR}'" + echo "Please run 'ddev playwright-install' or 'ddev playwright-init' to install playwright." + exit 1 +fi diff --git a/.ddev/commands/playwright/playwright-init b/.ddev/commands/playwright/playwright-init new file mode 100755 index 0000000..81d7385 --- /dev/null +++ b/.ddev/commands/playwright/playwright-init @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +#ddev-generated +# Remove the line above if you don't want this file to be overwritten when you run +# ddev add-on get julienloizelet/ddev-playwright +# +# This file comes from https://github.com/julienloizelet/ddev-playwright +# +## Description: Init playwright in PLAYWRIGHT_TEST_DIR +## Usage: playwright-init --pm [npm|yarn] +## Example: "ddev playwright-init --pm yarn" + +function display_help() { + echo "Usage: $0 --pm [yarn|npm]" + echo "Example: $0 --pm yarn" + exit 1 +} + +# Default package manager is not set +PACKAGE_MANAGER="" + +while [[ "$#" -gt 0 ]]; do + case $1 in + --pm) PACKAGE_MANAGER="$2"; shift ;; + *) echo "Unknown parameter passed: $1"; display_help ;; + esac + shift +done + +# Check if package manager is specified and valid +if [[ -z "$PACKAGE_MANAGER" ]] || ! { [[ "$PACKAGE_MANAGER" == "yarn" ]] || [[ "$PACKAGE_MANAGER" == "npm" ]]; }; then + echo "Warning: Package manager should be specified with --pm and be one of 'yarn' or 'npm'." + echo "Defaulting to yarn" + PACKAGE_MANAGER="yarn" +fi + +cd /var/www/html || exit 1 +cd "${PLAYWRIGHT_TEST_DIR}" || exit 1 + +export PLAYWRIGHT_BROWSERS_PATH=0 + +if [[ $PACKAGE_MANAGER == "npm" ]]; then + npm init playwright@latest +elif [[ $PACKAGE_MANAGER == "yarn" ]]; then + yarn create playwright +fi + +# Conditionally copy an .env file if an example file exists +[ -f .env.example ] && [ ! -f .env ] && cp -n .env.example .env; exit 0 diff --git a/.ddev/commands/playwright/playwright-install b/.ddev/commands/playwright/playwright-install new file mode 100755 index 0000000..8e5ec82 --- /dev/null +++ b/.ddev/commands/playwright/playwright-install @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +#ddev-generated +# Remove the line above if you don't want this file to be overwritten when you run +# ddev add-on get julienloizelet/ddev-playwright +# +# This file comes from https://github.com/julienloizelet/ddev-playwright +# +## Description: Install playwright in PLAYWRIGHT_TEST_DIR +## Usage: playwright-install --pm [npm|yarn] +## Example: "ddev playwright-install --pm npm" + +function display_help() { + echo "Usage: $0 --pm [yarn|npm]" + echo "Example: $0 --pm yarn" + exit 1 +} + +# Default package manager is not set +PACKAGE_MANAGER="" + +while [[ "$#" -gt 0 ]]; do + case $1 in + --pm) PACKAGE_MANAGER="$2"; shift ;; + *) echo "Unknown parameter passed: $1"; display_help ;; + esac + shift +done + +# Check if package manager is specified and valid +if [[ -z "$PACKAGE_MANAGER" ]] || ! { [[ "$PACKAGE_MANAGER" == "yarn" ]] || [[ "$PACKAGE_MANAGER" == "npm" ]]; }; then + echo "Warning: Package manager should be specified with --pm and be one of 'yarn' or 'npm'." + echo "Defaulting to yarn" + PACKAGE_MANAGER="yarn" +fi + +cd /var/www/html || exit 1 +cd "${PLAYWRIGHT_TEST_DIR}" || exit 1 + +export PLAYWRIGHT_BROWSERS_PATH=0 + +if [[ $PACKAGE_MANAGER == "npm" ]]; then + npm install + npx playwright install --with-deps +elif [[ $PACKAGE_MANAGER == "yarn" ]]; then + yarn install + yarn playwright install --with-deps +fi + +# Conditionally copy an .env file if an example file exists +[ -f .env.example ] && [ ! -f .env ] && cp -n .env.example .env; exit 0 diff --git a/.ddev/config.yaml b/.ddev/config.yaml index 1c4ee25..3bb74fb 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -1,6 +1,6 @@ name: os2forms-f2 type: drupal11 -docroot: Drupal +docroot: web php_version: "8.4" webserver_type: nginx-fpm xdebug_enabled: false diff --git a/.ddev/docker-compose.override.yaml b/.ddev/docker-compose.override.yaml new file mode 100644 index 0000000..4b67e9c --- /dev/null +++ b/.ddev/docker-compose.override.yaml @@ -0,0 +1,4 @@ +services: + playwright: + volumes: + - ../docs/user-manual:/docs/user-manual diff --git a/.ddev/docker-compose.playwright.yaml b/.ddev/docker-compose.playwright.yaml new file mode 100644 index 0000000..88a57dd --- /dev/null +++ b/.ddev/docker-compose.playwright.yaml @@ -0,0 +1,44 @@ +#ddev-generated +# Remove the line above if you don't want this file to be overwritten when you run +# ddev add-on get julienloizelet/ddev-playwright +# +# This file comes from https://github.com/julienloizelet/ddev-playwright +# +services: + playwright: + image: ${PLAYWRIGHT_DOCKER_IMAGE:-mcr.microsoft.com/playwright:v1.62.1-noble}-${DDEV_SITENAME}-built + build: + context: playwright-build + args: + PLAYWRIGHT_DOCKER_IMAGE: ${PLAYWRIGHT_DOCKER_IMAGE:-mcr.microsoft.com/playwright:v1.62.1-noble} + KASMVNC_VERSION: ${KASMVNC_VERSION:-1.4.0} + username: ${DDEV_USER} + uid: ${DDEV_UID} + gid: ${DDEV_GID} + container_name: ddev-${DDEV_SITENAME}-playwright + user: "${DDEV_UID}:${DDEV_GID}" + hostname: ${DDEV_SITENAME}-playwright + # These labels ensure this service is discoverable by ddev. + labels: + com.ddev.site-name: ${DDEV_SITENAME} + com.ddev.approot: ${DDEV_APPROOT} + environment: + # Modify the PLAYWRIGHT_TEST_DIR folder path to suit your needs + - PLAYWRIGHT_TEST_DIR=tests/Playwright + - NETWORK_IFACE=eth0 + - DISPLAY=:1 + - VIRTUAL_HOST=$DDEV_HOSTNAME + - HTTP_EXPOSE=8443:8444,9322:9323 + - HTTPS_EXPOSE=8444:8444,9323:9323 + expose: + - "8444" + - "9323" + depends_on: + - web + volumes: + - .:/mnt/ddev_config + - ddev-global-cache:/mnt/ddev-global-cache + - ../:/var/www/html:rw + working_dir: /var/www/html + x-ddev: + ssh-shell: bash diff --git a/.ddev/playwright-build/Dockerfile b/.ddev/playwright-build/Dockerfile new file mode 100644 index 0000000..bb9d0d9 --- /dev/null +++ b/.ddev/playwright-build/Dockerfile @@ -0,0 +1,96 @@ +#ddev-generated +# Remove the line above if you don't want this file to be overwritten when you run +# ddev add-on get julienloizelet/ddev-playwright +# +# This file comes from https://github.com/julienloizelet/ddev-playwright +# +ARG PLAYWRIGHT_DOCKER_IMAGE=mcr.microsoft.com/playwright:v1.62.1-noble +ARG KASMVNC_VERSION=1.4.0 +FROM ${PLAYWRIGHT_DOCKER_IMAGE} + +ARG PLAYWRIGHT_DOCKER_IMAGE +ARG KASMVNC_VERSION +LABEL PARENT_IMAGE="${PLAYWRIGHT_DOCKER_IMAGE}" + +# Debian images by default disable apt caching, so turn it on until we finish +# the build. +RUN mv /etc/apt/apt.conf.d/docker-clean /etc/apt/docker-clean-disabled + +# Remove any existing pwuser/ubuntu users to avoid UID conflicts +RUN deluser --remove-home pwuser || true +RUN deluser --remove-home ubuntu || true +# Accept build arguments for user creation +ARG username +ARG uid +ARG gid +# Ensure tty group exists +RUN getent group tty || groupadd tty +# Create group and user, trying multiple methods for compatibility +RUN (groupadd --gid "$gid" "$username" || groupadd "$username" || true) && \ + (useradd -G tty -l -m -s "/bin/bash" --gid "$username" --comment '' --uid "$uid" "$username" || \ + useradd -G tty -l -m -s "/bin/bash" --gid "$username" --comment '' "$username" || \ + useradd -G tty -l -m -s "/bin/bash" --gid "$gid" --comment '' "$username" || \ + useradd -G tty -l -m -s "/bin/bash" --comment '' "$username") + +# Define CAROOT for mkcert +ENV CAROOT=/mnt/ddev-global-cache/mkcert + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update \ + && apt-get install -y sudo + +# Allow the `$username` user passwordless sudo for `mkcert -install` +RUN mkdir -p /etc/sudoers.d && \ + echo "$username ALL=(ALL) NOPASSWD: ALL" > "/etc/sudoers.d/$username" && \ + chmod 0440 "/etc/sudoers.d/$username" + +# Install mkcert for the correct architecture +ARG TARGETARCH +RUN mkdir -p /usr/local/bin && \ + curl --fail -JL -s -o /usr/local/bin/mkcert "https://dl.filippo.io/mkcert/latest?for=linux/${TARGETARCH}" && \ + chmod +x /usr/local/bin/mkcert + +# Install a window manager. +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update \ + && apt-get install -y icewm xauth + +# Enable universe repository for Ubuntu (needed for KasmVNC dependencies for some versions). +RUN apt-get update && apt-get install -y software-properties-common && \ + add-apt-repository -y universe && apt-get update + + +# Install KasmVNC for remote access. +RUN if echo "$PLAYWRIGHT_DOCKER_IMAGE" | grep -q "jammy"; then \ + KASMVNC_DIST="jammy"; \ + elif echo "$PLAYWRIGHT_DOCKER_IMAGE" | grep -q "noble"; then \ + KASMVNC_DIST="noble"; \ + else \ + KASMVNC_DIST="bullseye"; \ + fi && \ + wget https://github.com/kasmtech/KasmVNC/releases/download/v${KASMVNC_VERSION}/kasmvncserver_${KASMVNC_DIST}_${KASMVNC_VERSION}_${TARGETARCH}.deb && \ + apt-get install -y ./kasmvncserver*.deb && \ + rm -f kasmvncserver*.deb + +# We're done with apt so disable caching again for the final image. +RUN mv /etc/apt/docker-clean-disabled /etc/apt/apt.conf.d/docker-clean + +# Prepare KasmVNC +RUN mkdir -p "/home/$username/.vnc" +COPY kasmvnc.yaml xstartup /home/"$username"/.vnc/ +RUN touch "/home/$username/.vnc/.de-was-selected" +# We actually disable auth, but KASM complains without it +RUN sudo -u "$username" /bin/bash -c "echo -e \"secret\nsecret\n\" | kasmvncpasswd -wo -u \"$username\"" +RUN chown -R "$uid:$gid" "/home/$username" +RUN usermod -a -G ssl-cert "$username" + +# Create the X11 socket directory with the standard permissions. +# The container runs as an unprivileged user. Xvnc would still create this +# directory itself, but it prints a misleading +# "_XSERVTransmkdir: ERROR: euid != 0" message in its log every time it does. +RUN mkdir -p /tmp/.X11-unix && chmod 1777 /tmp/.X11-unix + +COPY entrypoint.sh /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] diff --git a/.ddev/playwright-build/entrypoint.sh b/.ddev/playwright-build/entrypoint.sh new file mode 100755 index 0000000..a367585 --- /dev/null +++ b/.ddev/playwright-build/entrypoint.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +#ddev-generated +# Remove the line above if you don't want this file to be overwritten when you run +# ddev add-on get julienloizelet/ddev-playwright +# +# This file comes from https://github.com/julienloizelet/ddev-playwright +# + +# Install DDEV certificate +mkcert -install + +# Set up homeadditions if present +if [ -d /mnt/ddev_config/.homeadditions ]; then + cp -r /mnt/ddev_config/.homeadditions/. $HOME/ +fi + +# Remove stale X11 lock and socket files. +# When the container was not stopped cleanly (crash, kill, host reboot), DDEV +# restarts the same container and these files survive in /tmp. KasmVNC would +# then consider display :1 taken and start Xvnc on :2, while everything else +# expects DISPLAY=:1, so the window manager would fail and the container would +# exit. Nothing else is running at this point, so removing them is safe. +rm -f /tmp/.X*-lock /tmp/.X11-unix/X* + +# Stop Xvnc cleanly when the container is stopped, so that its lock, socket and +# pid files are removed. +stop_vnc() { + sudo -u "$(whoami)" vncserver -kill :1 + exit 0 +} +trap stop_vnc TERM INT + +# Start KasmVNC server on display :1. +# Xvnc and the applications of xstartup are started in the background, then +# vncserver returns. Requesting :1 explicitly makes vncserver fail if that +# display is not available, instead of silently using another one. +# sudo does not change the user here, but it re-initializes the supplementary +# groups (ssl-cert, tty) that Docker drops when the container is started with +# "user: uid:gid". vncserver refuses to start when it cannot read the KasmVNC +# certificate key, which belongs to the ssl-cert group. +sudo -u "$(whoami)" vncserver :1 -disableBasicAuth || exit 1 + +# Keep the container alive as long as Xvnc runs. +# The lifetime of the container is bound to Xvnc, not to the window manager +# started by xstartup: if the window manager fails, headless tests keep working. +XVNC_PID=$(cat "$HOME/.vnc/$(uname -n):1.pid") +while kill -0 "$XVNC_PID" 2>/dev/null; do + sleep 1 +done +echo "Xvnc (PID $XVNC_PID) exited, stopping the container" >&2 +exit 1 diff --git a/.ddev/playwright-build/kasmvnc.yaml b/.ddev/playwright-build/kasmvnc.yaml new file mode 100644 index 0000000..daf83dc --- /dev/null +++ b/.ddev/playwright-build/kasmvnc.yaml @@ -0,0 +1,24 @@ +#ddev-generated +# Remove the line above if you don't want this file to be overwritten when you run +# ddev add-on get julienloizelet/ddev-playwright +# +# This file comes from https://github.com/julienloizelet/ddev-playwright +# +logging: + log_writer_name: all + log_dest: syslog + level: 100 + +network: + ssl: + require_ssl: false + udp: + # Skip the public IP lookup that Xvnc performs when it starts. + # Without a fixed value, KasmVNC sends a STUN request to each of seven + # hardcoded internet servers and waits up to 10 seconds for each answer. + # On hosts where outgoing UDP is filtered, Xvnc blocks for about + # 70 seconds, X clients cannot connect in the meantime, and Xvnc then + # exits, which stops the container. + # See https://github.com/julienloizelet/ddev-playwright/issues/38 + # The UDP transport is not reachable through the DDEV router anyway. + public_ip: 127.0.0.1 diff --git a/.ddev/playwright-build/xstartup b/.ddev/playwright-build/xstartup new file mode 100755 index 0000000..07f26fb --- /dev/null +++ b/.ddev/playwright-build/xstartup @@ -0,0 +1,32 @@ +#!/bin/sh +#ddev-generated +# Remove the line above if you don't want this file to be overwritten when you run +# ddev add-on get julienloizelet/ddev-playwright +# +# This file comes from https://github.com/julienloizelet/ddev-playwright +# + +export DISPLAY=:1 + +unset SESSION_MANAGER +unset DBUS_SESSION_BUS_ADDRESS +OS=`uname -s` +if [ $OS = 'Linux' ]; then + case "$WINDOWMANAGER" in + *gnome*) + if [ -e /etc/SuSE-release ]; then + PATH=$PATH:/opt/gnome/bin + export PATH + fi + ;; + esac +fi +if [ -x /etc/X11/xinit/xinitrc ]; then + exec /etc/X11/xinit/xinitrc +fi +if [ -f /etc/X11/xinit/xinitrc ]; then + exec sh /etc/X11/xinit/xinitrc +fi +[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources +xterm -geometry 80x24+10+10 -ls -title "$VNCDESKTOP Desktop" & +icewm-session diff --git a/.github/workflows/javascript.yaml b/.github/workflows/javascript.yaml new file mode 100644 index 0000000..82a1317 --- /dev/null +++ b/.github/workflows/javascript.yaml @@ -0,0 +1,39 @@ +# Do not edit this file! Make a pull request on changing +# github/workflows/drupal-module/javascript.yaml in +# https://github.com/itk-dev/devops_itkdev-docker if need be. + +### ### Drupal module JavaScript (and TypeScript) +### +### Validates JavaScript files. +### +### #### Assumptions +### +### 1. A docker compose service named `prettier` for running +### [Prettier](https://prettier.io/) exists. + +name: JavaScript + +on: + pull_request: + paths: &paths + - "tests/**/*.{js,ts}" + - "docker-compose.yml" + push: + branches: + - main + - develop + paths: *paths + +jobs: + javascript-lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Create docker network + run: | + docker network create frontend + + - run: | + docker compose run --rm prettier --no-editorconfig 'tests/**/*.{js,ts}' --check diff --git a/.github/workflows/module.yaml b/.github/workflows/module.yaml index 45cb77a..3533517 100644 --- a/.github/workflows/module.yaml +++ b/.github/workflows/module.yaml @@ -31,25 +31,34 @@ jobs: ddev poser --no-interaction --no-security-blocking ddev symlink-project - - name: Composer - validate - run: ddev composer validate --strict + # https://github.com/mxschmitt/action-tmate?tab=readme-ov-file#manually-triggered-debug + # Enable tmate debugging if debug logging is enabled (cf. + # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#runner-context) + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + with: + limit-access-to-actor: false + # if: 1 == runner.debug + + # - name: Composer - validate + # run: ddev composer validate --strict - - name: Composer - normalize - run: ddev composer normalize --dry-run + # - name: Composer - normalize + # run: ddev composer normalize --dry-run - - name: PHP coding standards - run: ddev phpcs + # - name: PHP coding standards + # run: ddev phpcs - - name: Twig coding standards - run: >- - [[ -z $(git ls-files '*.twig') ]] || - ddev exec vendor/bin/twig-cs-fixer + # - name: Twig coding standards + # run: >- + # [[ -z $(git ls-files '*.twig') ]] || + # ddev exec vendor/bin/twig-cs-fixer - - name: Code analysis - run: ddev phpstan + # - name: Code analysis + # run: ddev phpstan - - name: Rector - run: ddev php vendor/bin/rector --dry-run + # - name: Rector + # run: ddev php vendor/bin/rector --dry-run - name: Extract translations run: task translations:extract diff --git a/.gitignore b/.gitignore index 0b20c6e..f5aeab7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,5 @@ translations/*.mo *.local # DDEV stuff -/Drupal/ +/web/ /recipes/ diff --git a/.markdownlintignore b/.markdownlintignore index 67e01a6..747414b 100644 --- a/.markdownlintignore +++ b/.markdownlintignore @@ -13,5 +13,5 @@ web/core/ web/libraries/ web/*/contrib/ # DDEV stuff -/Drupal/ +/web/ /recipes/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 81adefe..16666ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.0] - 2026-09-28 +* [PR-10](https://github.com/itk-dev/os2forms_f2/pull/10) + Updated user manual * [PR-8](https://github.com/itk-dev/os2forms_f2/pull/8) Added "Archive on case" * [PR-7](https://github.com/itk-dev/os2forms_f2/pull/7) diff --git a/README.md b/README.md index b08e826..8af8a7b 100644 --- a/README.md +++ b/README.md @@ -34,20 +34,34 @@ builders](docs/user-manual/da/form-builder.md) for more details on how to use th ### Examples -Install the F2 form examples module (`os2forms_f2_examples`) to load an example webform with F2 handlers. +Install the F2 form examples module (`drush pm:install os2forms_f2_examples`) to load an example webform with F2 +handlers. ## Development -We use [DDEV Drupal Contrib](https://github.com/ddev/ddev-drupal-contrib) for development. +> [!IMPORTANT] +> 2026-10-30: We lock `phpstan/phpstan` to version `2.3.3` as a temporary workaround for the issue reported in +> . +> +> We use [palantirnet/drupal-rector](https://github.com/palantirnet/drupal-rector) to run +> [Rector](https://github.com/rectorphp/rector). `drupal-rector` pulls in `rectorphp/rector`, but [conflicts with +> `"rector/rector": +> ">=2.6.2"`](https://github.com/palantirnet/drupal-rector/blob/3c584968fc61d55fdfce4e88b4e95841a60f3ff9/composer.json#L15-L17) +> and therefore we cannot (yet) upgrade to `rector/rector:^2.8.3`. +> +> See for details. + +We use [DDEV Drupal Contrib](https://addons.ddev.com/addons/ddev/ddev-drupal-contrib) for development.
Details on the DDEV setup The DDEV environment has been created by following the template on : -```shell -ddev config --project-type=drupal11 --docroot=Drupal +```shell name=ddev-create +ddev config --project-type=drupal11 --docroot=web ddev add-on get ddev/ddev-drupal-contrib +ddev add-on get julienloizelet/ddev-playwright ddev start ddev poser --no-interaction ddev symlink-project @@ -55,11 +69,6 @@ ddev symlink-project ddev config --update ``` -During development, we often have the Drupal modules cloned into `web/sites/default/modules` in a Drupal project. Using -`Drupal` as docroot affectively hides the DDEV site from the Drupal project during extension discovery (`Drupal` is -ignored by the [`RecursiveExtensionFilterCallback` -class](https://git.drupalcode.org/project/drupal/-/blob/11.4.11/core/lib/Drupal/Core/Extension/Discovery/RecursiveExtensionFilterCallback.php?ref_type=tags&plain=1#L46-74)). - We have a dependency on [OS2Forms](https://github.com/OS2Forms/os2forms) which requires [Drupal Lenient](https://github.com/mglaman/composer-drupal-lenient) to install some outdated modules, and therefore we use [a DDEV hook](https://docs.ddev.com/en/stable/users/configuration/hooks/) to make sure that Drupal Lenient is installed @@ -110,3 +119,70 @@ task translations:git:restore to discard any non-essential changes to translation files (e.g. timestamps being updated without any actual translation changes). + +## Testing + +``` shell +task drupal:os2forms:install +``` + +## User manual + +The Danish user manual for the F2 module sits in [`docs/user-manual/da/`](docs/user-manual/da/). + +We use [Playwright](https://playwright.dev/) to generate images for the user manual – and perform some testing along the +way – and we use [ddev-playwright](https://addons.ddev.com/addons/julienloizelet/ddev-playwright) to run Playwright in +the DDEV environment. + +Before generating the images a +little setup must be added in `tests/Playwright/.env.local`: + +``` dotenv +# Settings used to connect to a real F2 API +F2_API_URI= +F2_API_USERNAME= +F2_API_SECRET= +F2_API_F2_USERNAME= + +# An exiting case identifier +F2_CASE_IDENTIFIER= + +# An exiting matter identifier +F2_MATTER_IDENTIFIER='903' +``` + +With these in place, we can now generate images: + +``` shell +task drupal:os2forms:install --yes +task playwright:install --yes +task user-manual:images:generate +``` + +> [!TIP] +> If something fails, run +> +> ``` shell +> open tests/Playwright/playwright-report/index.html +> ``` +> +> to see an error report. + +When editing the user manual and fooling around with Playwright to generate useful images, you can run + +``` shell +task user-manual:images:generate +``` + +to quickly check that everything works as expected, but before committing anything you must run + +``` shell +task drupal:os2forms:install --yes +task user-manual:images:generate +``` + +reset Drupal before generating the images. + +> [!TIP] +> See [Run a Playwright command](https://addons.ddev.com/addons/julienloizelet/ddev-playwright#run-a-playwright-command) +> for other cool tricks supported by DDEV and the Playwright add-on. diff --git a/Taskfile.yml b/Taskfile.yml index a7a9e9d..0b42d89 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -16,6 +16,7 @@ tasks: desc: "Apply coding standards" cmds: - task: coding-standards:composer:apply + - task: coding-standards:javascript:apply - task: coding-standards:markdown:apply - task: coding-standards:php:apply - task: coding-standards:yaml:apply @@ -24,6 +25,7 @@ tasks: desc: "Check coding standards" cmds: - task: coding-standards:composer:check + - task: coding-standards:javascript:check - task: coding-standards:markdown:check - task: coding-standards:php:check - task: coding-standards:yaml:check @@ -44,6 +46,19 @@ tasks: vars: TASK_ARGS: composer validate + coding-standards:javascript:apply: + desc: "Apply coding standards for JavaScript" + cmds: + # Cf. .github/workflows/javascript.yaml + - docker compose run --rm prettier 'tests/**/*.{js,ts}' --write + + coding-standards:javascript:check: + desc: "Apply coding standards for JavaScript" + cmds: + - task: coding-standards:javascript:apply + # Cf. .github/workflows/javascript.yaml + - docker compose run --rm prettier 'tests/**/*.{js,ts}' --check + coding-standards:markdown:apply: desc: "Apply coding standards for Markdown" cmds: @@ -164,3 +179,115 @@ tasks: to check. cmd: git restore '*.po' + + drupal:os2forms:install: + desc: Install OS2Forms F2 module and configure Drupal for testing + prompt: Really reset Drupal site? + cmds: + - ddev drush --yes site:install + - ddev drush --yes user:password admin password + - ddev drush --yes pm:uninstall update + + # Patch https://github.com/OS2Forms/os2forms/blob/develop/config/optional/pathauto.pattern.os2forms_wf_webforms.yml + - | + ddev exec sed --in-place 's/id: node_type$/id: entity_bundle:node/' */modules/contrib/os2forms/config/optional/pathauto.pattern.os2forms_wf_webforms.yml + + - ddev drush --yes pm:install os2forms_f2 os2forms_f2_examples os2forms_test_playwright + + # Gin theme + - ddev drush --yes theme:install gin + - ddev drush --yes config:set system.theme default gin + - ddev drush --yes config:set system.theme admin gin + - ddev drush --yes pm:uninstall navigation + # - ddev drush --yes config:set gin.settings classic_toolbar horizontal + + - ddev drush --yes config:set webform.settings ui.description_help false --input-format yaml + - ddev drush --yes config:set webform.settings settings.default_submission_log true --input-format=yaml + + # Languages and locales + - ddev drush --yes pm:install language locale + - ddev drush --yes language:add da + + # - ddev drush --yes locale:check + # - ddev drush --yes locale:update + # Import our module translations + - ddev drush --yes locale:import da modules/custom/os2forms_f2/translations/os2forms_f2.da.po + + - task: drupal:os2forms:configure + + drupal:os2forms:configure: + decs: Create settings for os2forms_f2 module + cmds: + # Some defaults + - ddev drush --yes config:set os2forms_f2.settings f2_api.log_level debug + - ddev drush --yes config:set os2forms_f2.settings general.queue os2forms_f2 + + playwright:install: + desc: Install Playwright stuff + cmd: ddev playwright-install --pm npm + + user-manual:images:generate: + desc: Generate user manual images + cmds: + - cmd: ddev playwright test user-manual-images.spec.ts {{.CLI_ARGS}} + # We want to postprocess any updated images even if something fails + ignore_error: true + - task: user-manual:images:trim + - task: user-manual:images:restore + + user-manual:images:trim: + desc: "Trim docs images" + vars: + BORDER_WIDTH: 4 + # Trim and add border + cmd: + cmd: | + docker run --volume $PWD:/app --workdir /app --rm itkdev/php8.5-fpm mogrify \ + -strip \ + -background white \ + -trim \ + -gravity north -splice 0x{{.BORDER_WIDTH}}+0+0 \ + -gravity south -splice 0x{{.BORDER_WIDTH}}+0+0 \ + -gravity west -splice {{.BORDER_WIDTH}}x0+0+0 \ + -gravity east -splice {{.BORDER_WIDTH}}x0+0+0 \ + docs/user-manual/da/images/generated/**/*.png + # https://taskfile.dev/docs/reference/schema#shopt + shopt: [globstar] + + user-manual:images:restore: + desc: "Restore docs images that are not visually modified" + cmd: + cmd: | + # https://stackoverflow.com/a/949391 + latest_commit=$(git rev-parse --verify HEAD) + + # M docs/en/images/manager/006-entry-look-up.png + status_modified_pattern="^ M " + + debug_output_filename=false + if [[ '{{.COMPARE_OUTPUT_FILENAME | default "-"}}' != '-' ]]; then + debug_output_filename=true + fi + + shopt -s globstar nullglob + for file in docs/user-manual/da/images/generated/**/*.png; do + status=$(git status --porcelain "$file") + if [[ "$status" =~ $status_modified_pattern ]]; then + if git cat-file -p "$latest_commit":"$file" | \ + docker run --volume $PWD:/app --workdir /app --rm itkdev/php8.5-fpm compare \ + - \ + "$file" \ + {{.COMPARE_OUTPUT_FILENAME | default "-"}} > /dev/null 2>&1; then + echo "Restoring $file" + git restore "$file" + else + echo "$file modified" + if [ "$debug_output_filename" = true ]; then + echo + echo "Comparison image written to {{.COMPARE_OUTPUT_FILENAME | default "-"}}" + echo + fi + fi + fi + done + shopt: [globstar] diff --git a/composer.json b/composer.json index cd42c1d..7327800 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ "mglaman/phpstan-drupal": "^2.2", "palantirnet/drupal-rector": "^1.1", "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "2.3.3", "phpstan/phpstan-deprecation-rules": "^2.0" }, "repositories": [ @@ -40,7 +41,7 @@ "composer/installers": true, "cweagans/composer-patches": true, "dealerdirect/phpcodesniffer-composer-installer": true, - "drupal/core-composer-scaffold": true, + "drupal/core-composer-scaffold": false, "drupal/core-project-message": true, "ergebnis/composer-normalize": true, "mglaman/composer-drupal-lenient": true, diff --git a/docs/user-manual/da/administrator.md b/docs/user-manual/da/administrator.md index 0807b16..8caea96 100644 --- a/docs/user-manual/da/administrator.md +++ b/docs/user-manual/da/administrator.md @@ -1,8 +1,18 @@ # Administrator Generelle indstillinger for F2-modulet laves under Administration > Indstillinger > OS2Forms F2 -(`/admin/os2forms_f2/settings`), fx +(`/admin/os2forms_f2/settings`): -![F2-indstillinger](./images/f2-indstillinger.png) +![F2-indstillinger](./images/generated/administrator/f2-settings-empty.png) -Passende værdier for "Url", "Bruger", "Hemmelighed" og "F2-brugernavn" kan udleveres af en F2-administrator. +Passende værdier for "Url", "Bruger", "Hemmelighed" og "F2-brugernavn" kan udleveres af en F2-administrator, fx + +![Udfyldte F2-indstillinger](./images/generated/administrator/f2-settings-filled.png) + +Når indstillingerne er gemt kan man tjekke at der er hul igennem til api'et ved at trykke på "Tjek forbindelse til api": + +![Fejl under forbindelse til F2-api](./images/generated/administrator/f2-settings-api-pinged-error.png) + +Når man indsætter gyldige og korrekte værdier skulle der gerne være hul igennem til api'et: + +![Forbindelse til F2-api oprettet](./images/generated/administrator/f2-settings-api-pinged-success.png) diff --git a/docs/user-manual/da/form-builder.md b/docs/user-manual/da/form-builder.md index 9ec5224..2b4db94 100644 --- a/docs/user-manual/da/form-builder.md +++ b/docs/user-manual/da/form-builder.md @@ -1,18 +1,72 @@ # Formularbygger -F2-behandleren kan arkivere indholdet i et vedhæftningselement (OS2Forms Attachment) i F2. Behandleren indsættes ligesom -andre behandlere og konfigureres på passende vis. +F2-behandleren kan arkivere indholdet i et vedhæftningselement (OS2Forms Attachment) som et dokument i F2. Behandleren +indsættes ligesom andre behandlere -![F2-behandler](./images/f2-handler.png) +![Tilføj F2-formularbehandler](./images/generated/form-builder/webform-handler-select-f2.png) -Akt-id'et skal udpege en akt der allerede er oprettet i F2, og når man gemmer indstillingerne kontrolles at akten -findes og i tilfælde af fejl vises en passende fejlbesked: +og konfigureres på passende vis. -![F2-behandler med fejl](./images/f2-handler-error.png) +![Formularbehandler](./images/generated/form-builder/webform-handler-add-general.png) + +De afgørende indstillinger for F2-behandleren ligger under "F2-arkiveringsindstillinger": + +![F2-arkiveringsindstillinger](./images/generated/form-builder/webform-handler-add-general-archive-settings.png) + +Vælg et vedhæftningselement (af typen "OS2Forms Attachment") og angiv en titel på dokumentet der arkiveres i F2: + +![Udfyldte arkiveringsindstillinger](./images/generated/form-builder/webform-handler-add-general-archive-settings-filled.png) + +Under "Arkivér på" kan man vælge "[Sag](#arkivering-på-sag)" eller "[Akt](#arkivering-på-akt)" og valget har indflydelse +på yderligere muligheder i behandlerindstillingerne. + +## Arkivering på sag + +Som bekendt ligger dokumenter i F2 på en akt, og for at arkivere på en sag opretter behandleren derfor indledningsvist +en akt som dokumentet kan knyttes til. For at arkivere på en sag skal man angive et id der entydigt udpeger en sag i F2. +Det vil være oplagt at bruge et sags-nummer + +![Arkivér på sag](./images/generated/form-builder/webform-handler-add-archive-on-case.png) + +![Udfyldt "Arkivér på sag"](./images/generated/form-builder/webform-handler-add-archive-on-case-filled.png) + +Når man gemmer tjekker systemet at sags-id'et entydigt identificerer en sag i F2: + +![Ugyldigt sags-id](./images/generated/form-builder/webform-handler-add-archive-on-case-filled-invalid-case-identifier.png) + +Hvis id'et entydigt identificerer en sag i F2, så har man med succes tilføjet en F2-behandler: + +![Gyldigt og korrekt sags-id](./images/generated/form-builder/webform-handler-add-archive-on-case-filled-valid-case-identifier.png) + +!["Arkivér på sag"-behandler tilføjet](./images/generated/form-builder/webform-handler-added.png) + +Hvis man, som antydet, redigerer en F2-behandler kan man se detaljer om hvilken sag der faktisk arkiveres på: + +![Detaljer om sag](./images/generated/form-builder/webform-handler-archive-on-case-case-details.png) + +## Arkivering på akt + +Når man vil arkivere på en (eksisterende) akt skal man angive et id, fx akt-nummer, der entydigt udpeger akten i F2. + +![Arkivér på akt](./images/generated/form-builder/webform-handler-add-archive-on-matter.png) + +Når man gemmer tjekker systemet at akt-id'et entydigt identificerer en akt i F2: + +![Ugyldigt sags-id](./images/generated/form-builder/webform-handler-add-archive-on-matter-filled-invalid-matter-identifier.png) + +Hvis id'et entydigt identificerer en sag i F2, så har man med succes tilføjet en F2-behandler: + +!["Arkivér på akt"-behandler tilføjet](./images/generated/form-builder/webform-handler-added-2.png) + +Når man redigerer behandleren, kan man se detaljer om den akt der arkiveres på: + +![Detaljer om akt](./images/generated/form-builder/webform-handler-archive-on-matter-matter-details.png) + +## Avancerede indstillinger På fanen "Avanceret" kan man styre hvornår behandleren skal arkivere i F2: -![Avancerede indstillinger for F2-behandler](./images/f2-handler-advanced.png) +![Avancerede indstillinger for F2-behandler](./images/generated/form-builder/webform-handler-add-advanced.png) > [!NOTE] > Standardindstillingen, "Kør når … besvarelse afsluttes", vil oftest være det rigtige valg og kun i sjældne tilfælde diff --git a/docs/user-manual/da/images/f2-handler-advanced.png b/docs/user-manual/da/images/f2-handler-advanced.png deleted file mode 100644 index 7ab5d87..0000000 Binary files a/docs/user-manual/da/images/f2-handler-advanced.png and /dev/null differ diff --git a/docs/user-manual/da/images/f2-handler-error.png b/docs/user-manual/da/images/f2-handler-error.png deleted file mode 100644 index 4561c37..0000000 Binary files a/docs/user-manual/da/images/f2-handler-error.png and /dev/null differ diff --git a/docs/user-manual/da/images/f2-handler.png b/docs/user-manual/da/images/f2-handler.png deleted file mode 100644 index ab5a3d3..0000000 Binary files a/docs/user-manual/da/images/f2-handler.png and /dev/null differ diff --git a/docs/user-manual/da/images/f2-indstillinger.png b/docs/user-manual/da/images/f2-indstillinger.png deleted file mode 100644 index dca0b7c..0000000 Binary files a/docs/user-manual/da/images/f2-indstillinger.png and /dev/null differ diff --git a/docs/user-manual/da/images/generated/administrator/f2-settings-api-pinged-error.png b/docs/user-manual/da/images/generated/administrator/f2-settings-api-pinged-error.png new file mode 100644 index 0000000..a4a1594 Binary files /dev/null and b/docs/user-manual/da/images/generated/administrator/f2-settings-api-pinged-error.png differ diff --git a/docs/user-manual/da/images/generated/administrator/f2-settings-api-pinged-success.png b/docs/user-manual/da/images/generated/administrator/f2-settings-api-pinged-success.png new file mode 100644 index 0000000..aaa78d6 Binary files /dev/null and b/docs/user-manual/da/images/generated/administrator/f2-settings-api-pinged-success.png differ diff --git a/docs/user-manual/da/images/generated/administrator/f2-settings-empty.png b/docs/user-manual/da/images/generated/administrator/f2-settings-empty.png new file mode 100644 index 0000000..80e15e5 Binary files /dev/null and b/docs/user-manual/da/images/generated/administrator/f2-settings-empty.png differ diff --git a/docs/user-manual/da/images/generated/administrator/f2-settings-filled.png b/docs/user-manual/da/images/generated/administrator/f2-settings-filled.png new file mode 100644 index 0000000..c696722 Binary files /dev/null and b/docs/user-manual/da/images/generated/administrator/f2-settings-filled.png differ diff --git a/docs/user-manual/da/images/generated/administrator/f2-settings-saved.png b/docs/user-manual/da/images/generated/administrator/f2-settings-saved.png new file mode 100644 index 0000000..acc7963 Binary files /dev/null and b/docs/user-manual/da/images/generated/administrator/f2-settings-saved.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-advanced.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-advanced.png new file mode 100644 index 0000000..796a6bc Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-advanced.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case-filled-invalid-case-identifier.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case-filled-invalid-case-identifier.png new file mode 100644 index 0000000..40c2c5a Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case-filled-invalid-case-identifier.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case-filled-valid-case-identifier.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case-filled-valid-case-identifier.png new file mode 100644 index 0000000..29319d9 Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case-filled-valid-case-identifier.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case-filled.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case-filled.png new file mode 100644 index 0000000..c381b97 Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case-filled.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case.png new file mode 100644 index 0000000..9b9d70c Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-case.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter-filled-invalid-matter-identifier.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter-filled-invalid-matter-identifier.png new file mode 100644 index 0000000..65c1621 Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter-filled-invalid-matter-identifier.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter-filled-valid-matter-identifier.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter-filled-valid-matter-identifier.png new file mode 100644 index 0000000..29319d9 Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter-filled-valid-matter-identifier.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter-filled.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter-filled.png new file mode 100644 index 0000000..663df58 Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter-filled.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter.png new file mode 100644 index 0000000..5283ad1 Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-archive-on-matter.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-general-archive-settings-filled.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-general-archive-settings-filled.png new file mode 100644 index 0000000..be6173e Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-general-archive-settings-filled.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-general-archive-settings.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-general-archive-settings.png new file mode 100644 index 0000000..a83e5d5 Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-general-archive-settings.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-add-general.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-general.png new file mode 100644 index 0000000..2e02c50 Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-add-general.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-added-2.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-added-2.png new file mode 100644 index 0000000..6d4e344 Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-added-2.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-added.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-added.png new file mode 100644 index 0000000..f294bdd Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-added.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case-case-details.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case-case-details.png new file mode 100644 index 0000000..dbe95fa Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case-case-details.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case-case.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case-case.png new file mode 100644 index 0000000..fac18b5 Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case-case.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case.png new file mode 100644 index 0000000..d6fe39a Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-case.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter-matter-details.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter-matter-details.png new file mode 100644 index 0000000..142f988 Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter-matter-details.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter-matter.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter-matter.png new file mode 100644 index 0000000..28bb95c Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter-matter.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter.png new file mode 100644 index 0000000..da06ea7 Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-archive-on-matter.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handler-select-f2.png b/docs/user-manual/da/images/generated/form-builder/webform-handler-select-f2.png new file mode 100644 index 0000000..240ffdb Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handler-select-f2.png differ diff --git a/docs/user-manual/da/images/generated/form-builder/webform-handlers.png b/docs/user-manual/da/images/generated/form-builder/webform-handlers.png new file mode 100644 index 0000000..75aaebd Binary files /dev/null and b/docs/user-manual/da/images/generated/form-builder/webform-handlers.png differ diff --git a/modules/os2forms_f2_examples/os2forms_f2_examples.info.yml b/modules/os2forms_f2_examples/os2forms_f2_examples.info.yml index 65393d2..ac9e7ae 100644 --- a/modules/os2forms_f2_examples/os2forms_f2_examples.info.yml +++ b/modules/os2forms_f2_examples/os2forms_f2_examples.info.yml @@ -2,6 +2,6 @@ name: "F2 form examples" type: module description: "OS2Forms: F2 form examples" package: OS2Forms -core_version_requirement: ^10 || ^11 +core_version_requirement: ^11 dependencies: - os2forms_f2:os2forms_f2 diff --git a/modules/os2forms_test_playwright/README.md b/modules/os2forms_test_playwright/README.md new file mode 100644 index 0000000..4ba8111 --- /dev/null +++ b/modules/os2forms_test_playwright/README.md @@ -0,0 +1,49 @@ +# OS2Forms test Playwright + +This module is used only for testing with [Playwright](https://playwright.dev/) and it provides a controller that can be +called to execute Drush commands. + +> [!IMPORTANT] +> Do not enable this module in production! + +The controller responds on `/os2forms-test-playwright/exec/drush` and to `POST` requests only. The content type *must* +be `application/json` and the body must be a JSON list of arguments and flags to Drush. + +## Installation + +``` shell +ddev drush pm:install os2forms_test_playwright +``` + +## Example + +Call `drush status`: + +``` shell +$ ddev exec curl http://localhost/os2forms-test-playwright/exec/drush --json '["status"]' +Drupal version : 11.4.11 +Site URI : https://os2forms-f2.ddev.site:30001 +DB driver : mysql +DB hostname : db +DB port : 3306 +DB username : db +DB name : db +Database : Connected +Drupal bootstrap : Successful +Default theme : gin +Admin theme : gin +PHP binary : /usr/bin/php8.4 +PHP config : /etc/php/8.4/cli/php.ini +PHP OS : Linux +PHP version : 8.4.27 +Drush script : /var/www/html/vendor/bin/drush.php +Drush version : 13.8.0.0 +Drush temp : /tmp +Drush configs : /var/www/html/vendor/drush/drush/drush.yml +Install profile : standard +Drupal root : /var/www/html/web +Site path : sites/default +Files, Public : sites/default/files +Files, Temp : /tmp +Drupal config : sites/default/files/sync +``` diff --git a/modules/os2forms_test_playwright/os2forms_test_playwright.info.yml b/modules/os2forms_test_playwright/os2forms_test_playwright.info.yml new file mode 100644 index 0000000..cec53ab --- /dev/null +++ b/modules/os2forms_test_playwright/os2forms_test_playwright.info.yml @@ -0,0 +1,5 @@ +name: "OS2Forms test: Playwright" +type: module +description: "Playwright test module for OS2Forms" +package: Custom +core_version_requirement: ^11 diff --git a/modules/os2forms_test_playwright/os2forms_test_playwright.routing.yml b/modules/os2forms_test_playwright/os2forms_test_playwright.routing.yml new file mode 100644 index 0000000..e1a9833 --- /dev/null +++ b/modules/os2forms_test_playwright/os2forms_test_playwright.routing.yml @@ -0,0 +1,9 @@ +os2forms_test_playwright.exec: + path: "/os2forms-test-playwright/exec/{command}" + defaults: + _title: "Exec" + _controller: '\Drupal\os2forms_test_playwright\Controller\ExecController' + requirements: + _permission: "access content" + methods: POST + no_cache: TRUE diff --git a/modules/os2forms_test_playwright/os2forms_test_playwright.services.yml b/modules/os2forms_test_playwright/os2forms_test_playwright.services.yml new file mode 100644 index 0000000..d4abfbb --- /dev/null +++ b/modules/os2forms_test_playwright/os2forms_test_playwright.services.yml @@ -0,0 +1,7 @@ +services: + _defaults: + autowire: true + + logger.channel.os2forms_test_playwright: + parent: logger.channel_base + arguments: ["os2forms_test_playwright"] diff --git a/modules/os2forms_test_playwright/src/Controller/ExecController.php b/modules/os2forms_test_playwright/src/Controller/ExecController.php new file mode 100644 index 0000000..81cca34 --- /dev/null +++ b/modules/os2forms_test_playwright/src/Controller/ExecController.php @@ -0,0 +1,74 @@ + '../vendor/bin/drush', + default => throw new BadRequestHttpException(sprintf('command not found: %s', $command)), + }; + $contentType = $request->getContentTypeFormat(); + $arguments = match ($contentType) { + 'json' => $request->toArray(), + default => throw new BadRequestHttpException(sprintf('invalid content type: %s', $contentType)), + }; + if (empty($arguments)) { + throw new BadRequestHttpException('missing arguments'); + } + + $this->logger->debug('Running command @command @arguments', [ + '@command' => $command, + '@arguments' => $arguments, + ]); + + $response = new StreamedResponse(headers: [ + 'content-type' => 'text/plain', + ]); + $response->setCallback(function () use ($command, $arguments): void { + $process = new Process([$command, ...$arguments]); + $process->mustRun(function ($type, $buffer): void { + echo $buffer; + }); + }); + $response->headers->set('X-Accel-Buffering', 'no'); + $response->send(); + + return $response; + } + catch (\Throwable $throwable) { + $this->logger->error('Error: @message', [ + '@message' => $throwable->getMessage(), + 'throwable' => $throwable, + ]); + throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, message: $throwable->getMessage()); + } + } + +} diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 98f9d36..2fcf513 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -10,6 +10,7 @@ vendor rector.php + node_modules diff --git a/tests/Playwright/.env b/tests/Playwright/.env new file mode 100644 index 0000000..34a7e04 --- /dev/null +++ b/tests/Playwright/.env @@ -0,0 +1 @@ +BASEURL=http://web diff --git a/tests/Playwright/.gitignore b/tests/Playwright/.gitignore new file mode 100644 index 0000000..335bd46 --- /dev/null +++ b/tests/Playwright/.gitignore @@ -0,0 +1,8 @@ + +# Playwright +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/playwright/.auth/ diff --git a/tests/Playwright/package-lock.json b/tests/Playwright/package-lock.json new file mode 100644 index 0000000..0de7c26 --- /dev/null +++ b/tests/Playwright/package-lock.json @@ -0,0 +1,96 @@ +{ + "name": "playwright", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "playwright", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.64.1", + "@types/node": "^26.6.5", + "dotenv": "^18.0.9" + } + }, + "node_modules/@playwright/test": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.64.1.tgz", + "integrity": "sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.64.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@types/node": { + "version": "26.6.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.5.tgz", + "integrity": "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.9.0" + } + }, + "node_modules/dotenv": { + "version": "18.0.9", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-18.0.9.tgz", + "integrity": "sha512-3fVOW4cB02R066PUSwn8O4TJyz9NckQl7F81blIbaq9jKBm2tSmOKky7ZHIXzGvkVPXCSn6jkpziBPZdGFs8Tw==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "dotenv": "dist/index.cjs" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/playwright": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.1.tgz", + "integrity": "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.64.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.1.tgz", + "integrity": "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/undici-types": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/tests/Playwright/package.json b/tests/Playwright/package.json new file mode 100644 index 0000000..426f520 --- /dev/null +++ b/tests/Playwright/package.json @@ -0,0 +1,16 @@ +{ + "name": "playwright", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "devDependencies": { + "@playwright/test": "^1.64.1", + "@types/node": "^26.6.5", + "dotenv": "^18.0.9" + } +} diff --git a/tests/Playwright/playwright.config.ts b/tests/Playwright/playwright.config.ts new file mode 100644 index 0000000..bafccd1 --- /dev/null +++ b/tests/Playwright/playwright.config.ts @@ -0,0 +1,90 @@ +import { defineConfig, devices } from "@playwright/test"; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +import dotenv from "dotenv"; +import path from "path"; +dotenv.config({ path: path.resolve(__dirname, ".env") }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./tests", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [[process.env.CI ? "github" : "list"], ["html", { open: "never" }]], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('')`. */ + baseURL: process.env.BASEURL, + ignoreHTTPSErrors: true, + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + + screenshot: "only-on-failure", + }, + + /* Configure projects for major browsers */ + projects: [ + // https://playwright.dev/docs/auth#basic-shared-account-in-all-tests + { name: "setup", testMatch: /.*\.setup\.ts/ }, + + { + name: "chromium", + use: { + ...devices["Desktop Chrome"], + // Use prepared auth state. + storageState: "playwright/.auth/user.json", + }, + dependencies: ["setup"], + }, + + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://localhost:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/tests/Playwright/tests/auth.setup.ts b/tests/Playwright/tests/auth.setup.ts new file mode 100644 index 0000000..f3cacbc --- /dev/null +++ b/tests/Playwright/tests/auth.setup.ts @@ -0,0 +1,18 @@ +// https://playwright.dev/docs/auth#basic-shared-account-in-all-tests +import { test as setup, expect } from "@playwright/test"; +import path from "path"; + +const authFile = path.join(__dirname, "../playwright/.auth/user.json"); + +setup("authenticate", async ({ page }) => { + // Perform authentication steps. Replace these actions with your own. + await page.goto("/user/login"); + await page.locator('[name="name"]').fill("admin"); + await page.locator('[name="pass"]').fill("password"); + await page.locator('[name="pass"]').press("Enter"); + + await page.goto("/admin"); + // End of authentication steps. + + await page.context().storageState({ path: authFile }); +}); diff --git a/tests/Playwright/tests/user-manual-images.spec.ts b/tests/Playwright/tests/user-manual-images.spec.ts new file mode 100644 index 0000000..aa428f5 --- /dev/null +++ b/tests/Playwright/tests/user-manual-images.spec.ts @@ -0,0 +1,351 @@ +import { test, expect } from "@playwright/test"; + +// We want run and complete the administrator stuff before running the form-builder stuff. +// https://playwright.dev/docs/test-parallel +test.describe.configure({ mode: "serial" }); + +import dotenv from "dotenv"; +import path from "path"; +dotenv.config({ path: path.resolve(__dirname, "../.env.local") }); + +const submitSelector = 'input[data-drupal-selector="edit-submit"]'; + +test("user manual images – administrator (da)", async ({ page }) => { + const locale = "da"; + const context = "administrator"; + let screenshotCounter = 0; + + const screenshot = async (name: string, selector: string = "main") => { + const filePath = path.join( + `/docs/user-manual/${locale}/images/generated/`, + context, + // String(screenshotCounter++).padStart(3, "0") + "-" + + name + ".png", + ); + console.debug(filePath); + await page.locator(selector).screenshot({ path: filePath }); + }; + + const drush = async (args: string[]) => { + return page.request.post("/os2forms-test-playwright/exec/drush", { + data: args, + timeout: 4000, + }); + }; + + const goto = async (url: string) => { + url = `/${locale}/${url}`; + console.debug(`going to ${url} …`); + return page.goto(url); + }; + + const redact = async (selector: string, length: int = 8) => { + return page.evaluate( + ({ selector, length }) => { + const element = document.querySelector(selector); + element.value = "•".repeat(length); + + // https://css-tricks.com/fun-with-blurred-text/ + // element.style.color = 'transparent'; + // element.style.textShadow ='0 0 5px rgba(0,0,0,0.5)'; + }, + { selector, length }, + ); + }; + + await goto("admin/os2forms_f2/settings"); + await page.locator('[name="f2_api[uri]"]').fill(""); + await page.locator('[name="f2_api[username]"]').fill(""); + await page.locator('[name="f2_api[secret]"]').fill(""); + await page.locator('[name="f2_api[f2_username]"]').fill(""); + await page.locator('[name="general[queue]"]').selectOption(null); + await page.locator('[name="f2_api[uri]"]').focus(); + await page.locator('[name="f2_api[uri]"]').blur(); + await screenshot("f2-settings-empty", ".page-wrapper"); + + await goto("admin/os2forms_f2/settings"); + await page.locator('[name="f2_api[uri]"]').fill("https://f2.example.com"); + await page.locator('[name="f2_api[username]"]').fill("api-user"); + await page.locator('[name="f2_api[secret]"]').fill("api-$ecr3t"); + await page.locator('[name="f2_api[f2_username]"]').fill("f2-user"); + await page.locator('[name="general[queue]"]').selectOption("os2forms_f2"); + await page.locator('[name="f2_api[uri]"]').focus(); + await page.locator('[name="f2_api[uri]"]').blur(); + + await redact('[name="f2_api[username]"]', 16); + await redact('[name="f2_api[secret]"]', 16); + + await screenshot("f2-settings-filled"); + return; + + await page.locator(submitSelector).click(); + await screenshot("f2-settings-saved"); + + await page + .locator('input[data-drupal-selector="edit-action-ping-api"]') + .click(); + await screenshot("f2-settings-api-pinged-error", ".messages__wrapper"); + + // Refill with proper values + await page.locator('[name="f2_api[uri]"]').fill(process.env.F2_API_URI); + await page + .locator('[name="f2_api[username]"]') + .fill(process.env.F2_API_USERNAME); + await page.locator('[name="f2_api[secret]"]').fill(process.env.F2_API_SECRET); + await page + .locator('[name="f2_api[f2_username]"]') + .fill(process.env.F2_API_F2_USERNAME); + await page.locator('[name="f2_api[uri]"]').focus(); + await page.locator('[name="f2_api[uri]"]').blur(); + await page.locator(submitSelector).click(); + + await page + .locator('input[data-drupal-selector="edit-action-ping-api"]') + .click(); + await screenshot("f2-settings-api-pinged-success", ".messages__wrapper"); +}); + +test("user manual images – form-builder (da)", async ({ page }) => { + const locale = "da"; + const context = "form-builder"; + let screenshotCounter = 0; + + const goto = async (url: string) => { + url = `/${locale}/${url}`; + console.debug(` +going to ${url} …`); + return page.goto(url); + }; + + const screenshot = async (name: string, selector: string = "main") => { + const filePath = path.join( + `/docs/user-manual/${locale}/images/generated/`, + context, + // String(screenshotCounter++).padStart(3, "0") + "-" + + name + ".png", + ); + const pagePath = page.url().replace(process.env.BASEURL, ""); + console.debug(` +screenshooting ${selector} + on ${pagePath} + into ${filePath}`); + await page.locator(selector).screenshot({ path: filePath }); + }; + + const drush = async (args: string[]) => { + return page.request.post("/os2forms-test-playwright/exec/drush", { + data: args, + timeout: 4000, + }); + }; + + // Add handler + await goto("admin/structure/webform/manage/os2forms_f2_ex_000/handlers"); + await page + .locator( + '[data-drupal-link-system-path="admin/structure/webform/manage/os2forms_f2_ex_000/handlers/add"]', + ) + .click(); + await page.locator(".form-element--type-search").fill("F2"); + // Wait for the AJAX search + await page.waitForTimeout(1000); + await screenshot("webform-handler-select-f2", ".webform-ui-dialog"); + + await goto( + "admin/structure/webform/manage/os2forms_f2_ex_000/handlers/add/os2forms_f2_f2", + ); + await screenshot("webform-handler-add-general", "#webform-handler-form"); + await screenshot( + "webform-handler-add-general-archive-settings", + "#edit-settings-os2forms-f2-f2", + ); + + // Archive on case + await goto( + "admin/structure/webform/manage/os2forms_f2_ex_000/handlers/add/os2forms_f2_f2", + ); + + await page.locator('[name="label"]').fill("F2 – arkivér på sag"); + // Unhide and fill machine name field. + await page.evaluate( + 'document.querySelector(".form-type-machine-name").classList.remove("hidden")', + ); + await page.locator('[name="handler_id"]').fill("f2_arkiver_paa_sag"); + await page + .locator('[name="settings[os2forms_f2_f2][archive][attachment_element]"]') + .selectOption("attachment"); + await page + .locator('[name="settings[os2forms_f2_f2][archive][document_title]"]') + .fill("[webform_submission:label] ([site:url])"); + await page + .locator('[name="settings[os2forms_f2_f2][archive][archive_target]"]') + .focus(); + + await screenshot( + "webform-handler-add-general-archive-settings-filled", + "#edit-settings-os2forms-f2-f2", + ); + + await page + .locator('[name="settings[os2forms_f2_f2][archive][archive_target]"]') + .selectOption("case"); + await screenshot( + "webform-handler-add-archive-on-case", + "#edit-settings-os2forms-f2-f2-archive-archive-target-case", + ); + + await page + .locator( + '[name="settings[os2forms_f2_f2][archive][archive_target_case][case_identifier]"]', + ) + .fill(process.env.F2_CASE_IDENTIFIER_INVALID); + await page + .locator( + '[name="settings[os2forms_f2_f2][archive][archive_target_case][matter_title]"]', + ) + .fill("[webform_submission:label]"); + await page + .locator( + '[name="settings[os2forms_f2_f2][archive][archive_target_case][matter_type]"]', + ) + .selectOption("Internal"); + await screenshot( + "webform-handler-add-archive-on-case-filled", + "#edit-settings-os2forms-f2-f2-archive-archive-target-case", + ); + + await page.locator(submitSelector).click(); + await screenshot( + "webform-handler-add-archive-on-case-filled-invalid-case-identifier", + ".messages__wrapper", + ); + + await page + .locator( + '[name="settings[os2forms_f2_f2][archive][archive_target_case][case_identifier]"]', + ) + .fill(process.env.F2_CASE_IDENTIFIER_VALID); + await page.locator(submitSelector).click(); + await screenshot( + "webform-handler-add-archive-on-case-filled-valid-case-identifier", + ".messages__wrapper", + ); + + await goto("admin/structure/webform/manage/os2forms_f2_ex_000/handlers"); + await screenshot("webform-handler-added", ".page-wrapper"); + + await goto( + "admin/structure/webform/manage/os2forms_f2_ex_000/handlers/f2_arkiver_paa_sag/edit", + ); + await screenshot( + "webform-handler-archive-on-case-case-details", + // "#edit-settings-os2forms-f2-f2-archive-archive-target-case", + ); + + // Archive on matter + await goto( + "admin/structure/webform/manage/os2forms_f2_ex_000/handlers/add/os2forms_f2_f2", + ); + + await page.locator('[name="label"]').fill("F2 – arkivér på akt"); + // Unhide and fill machine name field. + await page.evaluate( + 'document.querySelector(".form-type-machine-name").classList.remove("hidden")', + ); + await page.locator('[name="handler_id"]').fill("f2_arkiver_paa_akt"); + await page + .locator('[name="settings[os2forms_f2_f2][archive][attachment_element]"]') + .selectOption("attachment"); + await page + .locator('[name="settings[os2forms_f2_f2][archive][document_title]"]') + .fill("[webform_submission:label] ([site:url])"); + await page + .locator('[name="settings[os2forms_f2_f2][archive][archive_target]"]') + .focus(); + + // await screenshot('webform-handler-add-general-archive-settings-filled.png', '#edit-settings-os2forms-f2-f2'); + + await page + .locator('[name="settings[os2forms_f2_f2][archive][archive_target]"]') + .selectOption("matter"); + await screenshot( + "webform-handler-add-archive-on-matter", + "#edit-settings-os2forms-f2-f2-archive-archive-target-matter", + ); + + await page + .locator( + '[name="settings[os2forms_f2_f2][archive][archive_target_matter][matter_identifier]"]', + ) + .fill(process.env.F2_MATTER_IDENTIFIER_INVALID); + await screenshot( + "webform-handler-add-archive-on-matter-filled", + "#edit-settings-os2forms-f2-f2-archive-archive-target-matter", + ); + + await page.locator(submitSelector).click(); + await screenshot( + "webform-handler-add-archive-on-matter-filled-invalid-matter-identifier", + ".messages__wrapper", + ); + + await page + .locator( + '[name="settings[os2forms_f2_f2][archive][archive_target_matter][matter_identifier]"]', + ) + .fill(process.env.F2_MATTER_IDENTIFIER_VALID); + await page.locator(submitSelector).click(); + await screenshot( + "webform-handler-add-archive-on-matter-filled-valid-matter-identifier", + ".messages__wrapper", + ); + + await goto("admin/structure/webform/manage/os2forms_f2_ex_000/handlers"); + await screenshot("webform-handler-added-2", ".page-wrapper"); + + await goto( + "admin/structure/webform/manage/os2forms_f2_ex_000/handlers/f2_arkiver_paa_akt/edit", + ); + await screenshot( + "webform-handler-archive-on-matter-matter-details", + "#edit-settings-os2forms-f2-f2-archive-archive-target-matter", + ); + + // Existing handler + + await goto( + "admin/structure/webform/manage/os2forms_f2_ex_001/handlers/f2_archive_on_case/edit", + ); + await screenshot( + "webform-handler-archive-on-case", + "#edit-settings-os2forms-f2-f2", + ); + await screenshot( + "webform-handler-archive-on-case-case", + "#edit-settings-os2forms-f2-f2-archive-archive-target-case", + ); + + await goto( + "admin/structure/webform/manage/os2forms_f2_ex_001/handlers/f2_archive_on_matter/edit", + ); + await screenshot( + "webform-handler-archive-on-matter", + "#edit-settings-os2forms-f2-f2", + ); + await screenshot( + "webform-handler-archive-on-matter-matter", + "#edit-settings-os2forms-f2-f2-archive-archive-target-matter", + ); + + await goto("admin/structure/webform/manage/os2forms_f2_ex_001/handlers"); + await screenshot("webform-handlers", "#webform-handlers"); + + await goto( + "admin/structure/webform/manage/os2forms_f2_ex_001/handlers/f2_archive_on_case/edit", + ); + await page.getByRole("tab", { name: "Avanceret" }).click(); + await expect(page.getByText("Yderligere indstillinger")).toBeVisible(); + await page.getByText("Yderligere indstillinger").click(); + await page.getByText("Yderligere indstillinger").blur(); + await screenshot("webform-handler-add-advanced", "#webform-handler-form"); +}); diff --git a/tests/Playwright/tests/user-manual-submission.spec.ts b/tests/Playwright/tests/user-manual-submission.spec.ts new file mode 100644 index 0000000..a4e489c --- /dev/null +++ b/tests/Playwright/tests/user-manual-submission.spec.ts @@ -0,0 +1,76 @@ +import { test, expect } from "@playwright/test"; + +// We want run and complete the administrator stuff before running the form-builder stuff. +// https://playwright.dev/docs/test-parallel +test.describe.configure({ mode: "serial" }); + +import dotenv from "dotenv"; +import path from "path"; +import * as child_process from "node:child_process"; +dotenv.config({ path: path.resolve(__dirname, "../.env.local") }); + +test("webform submission", async ({ page }) => { + const locale = "da"; + const context = "webform-submission"; + let screenshotCounter = 0; + + const screenshot = async ( + name: string, + selector: string = ".page-wrapper", + ) => { + const filePath = path.join( + `/docs/user-manual/${locale}/images/generated/`, + context, + String(screenshotCounter++).padStart(3, "0") + "-" + name + ".png", + ); + await page.locator(selector).screenshot({ path: filePath }); + }; + + const drush = async (args: string[]) => { + return page.request.post("/os2forms-test-playwright/exec/drush", { + data: args, + timeout: 4000, + }); + }; + + const goto = async (url: string) => { + url = `/${locale}/${url}`; + console.debug(`going to ${url} …`); + return page.goto(url); + }; + + await drush([ + "sql:query", + [ + "TRUNCATE advancedqueue", + "TRUNCATE webform_submission_log", + "TRUNCATE webform_submission_data", + "TRUNCATE webform_submission", + "ALTER TABLE webform_submission AUTO_INCREMENT = 1", + "UPDATE webform SET next_serial = 1", + ].join(";"), + ]); + + await goto("webform/os2forms_f2_ex_001"); + await screenshot("form-empty"); + + await page + .locator('input[data-drupal-selector="edit-name"]') + .fill("Anders And"); + await screenshot("form-filled"); + + await page.locator('input[data-drupal-selector="edit-submit"]').click(); + await screenshot("form-submitted"); + + await goto( + "admin/structure/webform/manage/os2forms_f2_ex_001/submission/1/log", + ); + await screenshot("submission-log"); + + await drush(["advancedqueue:queue:process", "os2forms_f2"]); + + await goto( + "admin/structure/webform/manage/os2forms_f2_ex_001/submission/1/log", + ); + await screenshot("submission-log-after-queue-processed"); +});