From e1f2ce39eecf9aa31364e8da9dad351d127379ba Mon Sep 17 00:00:00 2001 From: Robert Baruck Date: Mon, 23 Mar 2026 22:01:02 +0100 Subject: [PATCH 01/12] TASK: Add system under test for Neos 8 and 9 --- Tests/sytem_under_test/neos8/Dockerfile | 56 ++++++++++++++++++ .../neos8/docker-compose.yaml | 57 +++++++++++++++++++ .../Production/E2E-SUT/Caches.yaml | 56 ++++++++++++++++++ .../Production/E2E-SUT/Settings.yaml | 26 +++++++++ .../Production/Settings.yaml.example | 19 +++++++ .../neos8/sut-files/app/Configuration/README | 29 ++++++++++ .../app/Configuration/Settings.yaml.example | 39 +++++++++++++ .../neos8/sut-files/entrypoint.sh | 22 +++++++ .../sut-files/etc/bash.vips-arm64-hotfix.sh | 17 ++++++ .../neos8/sut-files/etc/caretakerd.yaml | 9 +++ .../neos8/sut-files/etc/frankenphp/Caddyfile | 41 +++++++++++++ .../etc/php/conf.d/php-ini-overrides.ini | 20 +++++++ Tests/sytem_under_test/neos9/Dockerfile | 57 +++++++++++++++++++ .../neos9/docker-compose.yaml | 57 +++++++++++++++++++ .../Production/E2E-SUT/Caches.yaml | 56 ++++++++++++++++++ .../Production/E2E-SUT/Settings.yaml | 26 +++++++++ .../Production/Settings.yaml.example | 19 +++++++ .../neos9/sut-files/app/Configuration/README | 29 ++++++++++ .../app/Configuration/Settings.yaml.example | 39 +++++++++++++ .../neos9/sut-files/entrypoint.sh | 25 ++++++++ .../sut-files/etc/bash.vips-arm64-hotfix.sh | 17 ++++++ .../neos9/sut-files/etc/caretakerd.yaml | 9 +++ .../neos9/sut-files/etc/frankenphp/Caddyfile | 41 +++++++++++++ .../etc/php/conf.d/php-ini-overrides.ini | 20 +++++++ 24 files changed, 786 insertions(+) create mode 100644 Tests/sytem_under_test/neos8/Dockerfile create mode 100644 Tests/sytem_under_test/neos8/docker-compose.yaml create mode 100644 Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Caches.yaml create mode 100644 Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Settings.yaml create mode 100644 Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/Settings.yaml.example create mode 100644 Tests/sytem_under_test/neos8/sut-files/app/Configuration/README create mode 100644 Tests/sytem_under_test/neos8/sut-files/app/Configuration/Settings.yaml.example create mode 100755 Tests/sytem_under_test/neos8/sut-files/entrypoint.sh create mode 100644 Tests/sytem_under_test/neos8/sut-files/etc/bash.vips-arm64-hotfix.sh create mode 100755 Tests/sytem_under_test/neos8/sut-files/etc/caretakerd.yaml create mode 100644 Tests/sytem_under_test/neos8/sut-files/etc/frankenphp/Caddyfile create mode 100644 Tests/sytem_under_test/neos8/sut-files/usr/local/etc/php/conf.d/php-ini-overrides.ini create mode 100644 Tests/sytem_under_test/neos9/Dockerfile create mode 100644 Tests/sytem_under_test/neos9/docker-compose.yaml create mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Caches.yaml create mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Settings.yaml create mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/Settings.yaml.example create mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/README create mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Settings.yaml.example create mode 100755 Tests/sytem_under_test/neos9/sut-files/entrypoint.sh create mode 100644 Tests/sytem_under_test/neos9/sut-files/etc/bash.vips-arm64-hotfix.sh create mode 100755 Tests/sytem_under_test/neos9/sut-files/etc/caretakerd.yaml create mode 100644 Tests/sytem_under_test/neos9/sut-files/etc/frankenphp/Caddyfile create mode 100644 Tests/sytem_under_test/neos9/sut-files/usr/local/etc/php/conf.d/php-ini-overrides.ini diff --git a/Tests/sytem_under_test/neos8/Dockerfile b/Tests/sytem_under_test/neos8/Dockerfile new file mode 100644 index 0000000..1747563 --- /dev/null +++ b/Tests/sytem_under_test/neos8/Dockerfile @@ -0,0 +1,56 @@ +FROM dunglas/frankenphp:1-php8.2-trixie + +COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer + +# reference: https://github.com/mlocati/docker-php-extension-installer +RUN install-php-extensions \ + intl \ + bcmath \ + opcache \ + pdo \ + pdo_mysql \ + xsl \ + ffi \ + vips \ + redis + +RUN apt update && apt install -y git unzip mariadb \ + && apt clean \ + && rm -rf /var/lib/apt/lists/* + +ARG USER=www-data + +# Give write access to /config/caddy and /data/caddy +RUN \ + useradd ${USER}; \ + chown -R ${USER}:${USER} /config/caddy /data/caddy \ + && touch /var/run/caretakerd.key && chown ${USER}:${USER} /var/run/caretakerd.key + +# Add Caretaker (Startup manager) +ARG TARGETARCH +RUN curl -SL "https://caretakerd.echocat.org/latest/download/caretakerd-linux-${TARGETARCH}.tar.gz" \ + | tar -xz --exclude caretakerd.html -C /usr/bin + +# TODO: still needed? +# HOTFIX for ARM64 Architectures and VIPS (see https://github.com/opencv/opencv/issues/14884#issuecomment-706725583 for details) +# only needed for development on Apple Silicon Macs +RUN echo '. /etc/bash.vips-arm64-hotfix.sh' >> /etc/bash.bashrc + +# Install Neos base distribution +RUN --mount=type=cache,target=/root/.composer rm -rf /app \ + && composer create-project neos/neos-base-distribution:^8 /app --no-dev + +RUN --mount=type=cache,target=/root/.composer composer require rokka/imagine-vips:0.* + +# Add config files +ADD sut-files/ / + +# chown for neos data folder and Resources ONLY +RUN mkdir -p /app/Data /app/Web/_Resources \ + && chown -R ${USER} /app/Data /app/Web/_Resources \ + && chmod +x /entrypoint.sh + +WORKDIR /app +USER ${USER} + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/Tests/sytem_under_test/neos8/docker-compose.yaml b/Tests/sytem_under_test/neos8/docker-compose.yaml new file mode 100644 index 0000000..fb84d0c --- /dev/null +++ b/Tests/sytem_under_test/neos8/docker-compose.yaml @@ -0,0 +1,57 @@ +services: + neos: + user: www-data:www-data + build: + context: . + dockerfile: Dockerfile + environment: + FLOW_CONTEXT: 'Production/E2E-SUT' + # DB connection + DB_NEOS_HOST: 'db' + DB_NEOS_PORT: 3306 + DB_NEOS_USER: 'neos' + DB_NEOS_PASSWORD: 'neos' + DB_NEOS_DATABASE: 'neos' + # Redis connection + REDIS_HOST: 'redis' + REDIS_PORT: 6379 + # this is safe because the neos container port is only exposed to the local interface + # This means that the neos container is ALWAYS accessed through the front facing Ingress + FLOW_HTTP_TRUSTED_PROXIES: '*' + networks: + - neos8_SUT + ports: + - 8081:8081 + depends_on: + - db + - redis + + db: + image: mariadb:10.11 + restart: always + ports: + - "13306:3306" + networks: + - neos8_SUT + environment: + MARIADB_RANDOM_ROOT_PASSWORD: 'true' + MARIADB_DATABASE: 'neos' + MARIADB_USER: 'neos' + MARIADB_PASSWORD: 'neos' + MARIADB_AUTO_UPGRADE: 1 + volumes: + - db:/var/lib/mysql + command: ['mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci'] + + redis: + image: redis:7 + restart: always + networks: + - neos8_SUT + +volumes: + db: + +networks: + neos8_SUT: + name: neos8_SUT diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Caches.yaml b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Caches.yaml new file mode 100644 index 0000000..3844103 --- /dev/null +++ b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Caches.yaml @@ -0,0 +1,56 @@ +Flow_Mvc_Routing_Route: + backend: 'Neos\Cache\Backend\RedisBackend' + backendOptions: + hostname: '%env:REDIS_HOST%' + port: '%env:REDIS_PORT%' + # starting with database 2 here, since 0 and 1 are used and flushed by + # the core unit tests and should not be used if possible. + database: 2 + defaultLifetime: 0 + +Flow_Mvc_Routing_Resolve: + backend: 'Neos\Cache\Backend\RedisBackend' + backendOptions: + hostname: '%env:REDIS_HOST%' + port: '%env:REDIS_PORT%' + database: 2 + defaultLifetime: 0 + +# We want to test cache settings on Fusion components. +# Therefore, at one moment in the BDD test, we need to actively trigger the ContentCacheFlusher to invalidate the tags. +# Since we have multiple Flow contexts during tests, the cache settings are unified for all contexts. +# Explanation: +# - The system under test runs in Production/E2E-SUT; it writes cache entries +# - The Test Runner (behat) runs in Testing/Behat; it needs to invalidate cache for the SuT via service API call +# For now, we simply keep the cache settings in sync between those two profiles (like we do with the DB config). +Neos_Fusion_Content: + backend: 'Neos\Cache\Backend\RedisBackend' + backendOptions: + hostname: '%env:REDIS_HOST%' + port: '%env:REDIS_PORT%' + database: 2 + defaultLifetime: 0 + +Flow_Session_MetaData: + backend: 'Neos\Cache\Backend\RedisBackend' + backendOptions: + hostname: '%env:REDIS_HOST%' + port: '%env:REDIS_PORT%' + database: 2 + defaultLifetime: 0 + +Flow_Session_Storage: + backend: 'Neos\Cache\Backend\RedisBackend' + backendOptions: + hostname: '%env:REDIS_HOST%' + port: '%env:REDIS_PORT%' + database: 2 + defaultLifetime: 0 + +Neos_Media_ImageSize: + backend: 'Neos\Cache\Backend\RedisBackend' + backendOptions: + hostname: '%env:REDIS_HOST%' + port: '%env:REDIS_PORT%' + database: 2 + defaultLifetime: 0 diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Settings.yaml b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Settings.yaml new file mode 100644 index 0000000..27101ea --- /dev/null +++ b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Settings.yaml @@ -0,0 +1,26 @@ +Neos: + Flow: + persistence: + backendOptions: + driver: 'pdo_mysql' + charset: 'utf8mb4' + host: '%env:DB_NEOS_HOST%' + port: '%env:DB_NEOS_PORT%' + password: '%env:DB_NEOS_PASSWORD%' + user: '%env:DB_NEOS_USER%' + dbname: '%env:DB_NEOS_DATABASE%' + cache: + applicationIdentifier: 'app' + + Imagine: + driver: 'Vips' + enabledDrivers: + Vips: true + Gd: true + Imagick: true + + Media: + image: + defaultOptions: + # The Vips driver does not support interlace + interlace: ~ diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/Settings.yaml.example b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/Settings.yaml.example new file mode 100644 index 0000000..58050de --- /dev/null +++ b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/Settings.yaml.example @@ -0,0 +1,19 @@ +# # +# Example Settings # +# # +# This file contains settings for various parts of the application. # +# Copy this file to Settings.yaml, and adjust as necessary. # +# # +# Please refer to the default settings file(s) or the manuals for # +# possible configuration options. # +# # + +Neos: + Flow: + persistence: + # For a reference of the possible options, take a look at + # https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html + backendOptions: + dbname: 'flow' # adjust to your database name + user: '' # adjust to your database user + password: '' # adjust to your database password diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/README b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/README new file mode 100644 index 0000000..1f840e7 --- /dev/null +++ b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/README @@ -0,0 +1,29 @@ +============================================================================= +Flow Configuration Directory +============================================================================= + +This directory contains the configuration for the Flow framework and the +application based on it. The configuration files only contain additions and +modifications of the original configuration and therefore don't contain all +possible configuration options. + +The following files play a role in the configuration system: + + Settings.yaml Various application-level settings can be set in this file. + + Objects.yaml Additional objects configuration, overriding the + configuration which was defined in the various packages. + + Routes.yaml Provides the routing configuration for the MVC framework. + + Views.yaml Can be used to influence resolutions of views in MVC. + + Policy.yaml Provides the configuration for the security framework. + + Caches.yaml Provides the configuration for the caching framework. + +All of these files become active in any application context if they reside +in the main Configuration directory (the same directory where this README is +located). However, context-specific configuration may be defined in sub directories +which carry the same name as the application context. These configuration +files are loaded after the default and global configuration has been invoked. \ No newline at end of file diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Settings.yaml.example b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Settings.yaml.example new file mode 100644 index 0000000..c02aafc --- /dev/null +++ b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Settings.yaml.example @@ -0,0 +1,39 @@ +# +# Example Settings +# +# This file can contain settings for your Flow application that override +# the various defaults defined by other packages. +# +# Copy this file to Settings.yaml, and adjust as necessary. +# +# For possible configuration options you may refer to the packages' +# default settings file(s) in the corresponding package directories (for +# example, Packages/Framework/Neos.Flow/Configuration/Settings.yaml) +# or read the fine manuals. + +Neos: + Flow: + persistence: + + # It is good practice to not specify user name and password of the database + # connection in this global Settings.yaml file. Rather specify them in the + # settings of the respective context (Production / Development ...). + # For a reference of the possible options, take a look at + # https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html + backendOptions: + driver: 'pdo_mysql' # use pdo_pgsql for PostgreSQL + charset: 'utf8mb4' # change to utf8 when using PostgreSQL + host: '127.0.0.1' # adjust to your database host + + # The following lines register the Flow default routes + # For productive use you should move this setting to the main package of this + # application and/or create custom routes. + mvc: + routes: + 'Neos.Flow': TRUE + + # You might need to uncomment the following lines and specify + # the location of the PHP binary manually. +# core: +# phpBinaryPathAndFilename: 'C:/path/to/php.exe' + diff --git a/Tests/sytem_under_test/neos8/sut-files/entrypoint.sh b/Tests/sytem_under_test/neos8/sut-files/entrypoint.sh new file mode 100755 index 0000000..8e6130c --- /dev/null +++ b/Tests/sytem_under_test/neos8/sut-files/entrypoint.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -eou pipefail + +# ping db until it is ready +echo "Waiting for database..." +until mariadb -h"${DB_NEOS_HOST}" -P"${DB_NEOS_PORT}" -u"${DB_NEOS_USER}" -p"${DB_NEOS_PASSWORD}" -D"${DB_NEOS_DATABASE}" --disable-ssl --silent -e "SELECT 1;" 2>/dev/null; do + sleep 2 +done + +./flow flow:cache:flush + +# this does the warmup as well +./flow doctrine:migrate + +yes y | ./flow resource:clean || true + +./flow site:import --package-key Neos.Demo + +./flow resource:publish --collection static + +# 2. We now can start caretakerd, which will run the remaining steps in parallel and restart the container if they fail. +/usr/bin/caretakerd run diff --git a/Tests/sytem_under_test/neos8/sut-files/etc/bash.vips-arm64-hotfix.sh b/Tests/sytem_under_test/neos8/sut-files/etc/bash.vips-arm64-hotfix.sh new file mode 100644 index 0000000..9bb2110 --- /dev/null +++ b/Tests/sytem_under_test/neos8/sut-files/etc/bash.vips-arm64-hotfix.sh @@ -0,0 +1,17 @@ +if [ "$(uname -m)" = "aarch64" ]; then + echo "Using LD_Preload workaround for gomp issue" + + # WORKAROUND for Apple M1 Chips. Without this line, we get the error message: + # + # Warning: PHP Startup: Unable to load dynamic library 'vips.so' (tried: + # /usr/local/lib/php/extensions/no-debug-non-zts-20200930/vips.so + # (/usr/lib/aarch64-linux-gnu/libgomp.so.1: cannot allocate memory in + # static TLS block), /usr/local/lib/php/extensions/no-debug-non-zts-20200930/vips.so.so + # (/usr/local/lib/php/extensions/no-debug-non-zts-20200930/vips.so.so: cannot open + # shared object file: No such file or directory)) in Unknown on line 0 + # + # This error seems to be related to some OpenCV bug or issue described at + # https://github.com/opencv/opencv/issues/14884#issuecomment-706725583 + # And the workaround is to ensure that libgomp is loaded first. + export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libgomp.so.1 +fi diff --git a/Tests/sytem_under_test/neos8/sut-files/etc/caretakerd.yaml b/Tests/sytem_under_test/neos8/sut-files/etc/caretakerd.yaml new file mode 100755 index 0000000..f57404b --- /dev/null +++ b/Tests/sytem_under_test/neos8/sut-files/etc/caretakerd.yaml @@ -0,0 +1,9 @@ +# DOCS: https://caretakerd.echocat.org/#configuration.structure + +rpc: + enabled: true + +services: + php-fpm: + type: master + command: ['frankenphp', 'run', '--config', '/etc/frankenphp/Caddyfile'] diff --git a/Tests/sytem_under_test/neos8/sut-files/etc/frankenphp/Caddyfile b/Tests/sytem_under_test/neos8/sut-files/etc/frankenphp/Caddyfile new file mode 100644 index 0000000..c2d5f0f --- /dev/null +++ b/Tests/sytem_under_test/neos8/sut-files/etc/frankenphp/Caddyfile @@ -0,0 +1,41 @@ +# The Caddyfile is an easy way to configure FrankenPHP and the Caddy web server. +# +# https://frankenphp.dev/docs/config +# https://caddyserver.com/docs/caddyfile +# https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile +{ + skip_install_trust + + # debug + + frankenphp { + # num_threads # Sets the number of PHP threads to start. Default: 2x the number of available CPUs. + #max_threads # Limits the number of additional PHP threads that can be started at runtime. Default: num_threads. Can be set to 'auto'. + #max_wait_time # Sets the maximum time a request may wait for a free PHP thread before timing out. Default: disabled. + #php_ini # Set a php.ini directive. Can be used several times to set multiple directives. + } +} + +:8081 { + log + + root /app/Web + encode zstd br gzip + + request_body { + max_size 256MB + } + + # Block direct access to PHP files except index.php + @blockPhp { + path *.php + not path /index.php + } + + handle @blockPhp { + respond 404 + } + + + php_server +} diff --git a/Tests/sytem_under_test/neos8/sut-files/usr/local/etc/php/conf.d/php-ini-overrides.ini b/Tests/sytem_under_test/neos8/sut-files/usr/local/etc/php/conf.d/php-ini-overrides.ini new file mode 100644 index 0000000..1190ae0 --- /dev/null +++ b/Tests/sytem_under_test/neos8/sut-files/usr/local/etc/php/conf.d/php-ini-overrides.ini @@ -0,0 +1,20 @@ +; ================== +; Various defaults +; ================== + +; for PHP >= 8.1, disable deprecations to temporarily make Neos/Flow work +error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT +memory_limit = 512M +upload_max_filesize = 256M +post_max_size = 256M +date.timezone = "Europe/Berlin" + +; ================== +; VIPS +; ================== +; required for VIPS +ffi.enable = true + +; for VIPS on PHP >= 8.3 the following line is required, +; see https://github.com/libvips/php-vips +zend.max_allowed_stack_size = -1 diff --git a/Tests/sytem_under_test/neos9/Dockerfile b/Tests/sytem_under_test/neos9/Dockerfile new file mode 100644 index 0000000..76929e9 --- /dev/null +++ b/Tests/sytem_under_test/neos9/Dockerfile @@ -0,0 +1,57 @@ +FROM dunglas/frankenphp:1-php8.3-trixie + +COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer + +# reference: https://github.com/mlocati/docker-php-extension-installer +RUN install-php-extensions \ + intl \ + bcmath \ + opcache \ + pdo \ + pdo_mysql \ + xsl \ + ffi \ + vips \ + redis + +RUN apt update \ + && apt install -y git unzip mariadb-client \ + && apt clean \ + && rm -rf /var/lib/apt/lists/* + +ARG USER=www-data + +# Give write access to /config/caddy and /data/caddy +RUN \ + useradd ${USER}; \ + chown -R ${USER}:${USER} /config/caddy /data/caddy \ + && touch /var/run/caretakerd.key && chown ${USER}:${USER} /var/run/caretakerd.key + +# Add Caretaker (Startup manager) +ARG TARGETARCH +RUN curl -SL "https://caretakerd.echocat.org/latest/download/caretakerd-linux-${TARGETARCH}.tar.gz" \ + | tar -xz --exclude caretakerd.html -C /usr/bin + +# TODO: still needed? +# HOTFIX for ARM64 Architectures and VIPS (see https://github.com/opencv/opencv/issues/14884#issuecomment-706725583 for details) +# only needed for development on Apple Silicon Macs +RUN echo '. /etc/bash.vips-arm64-hotfix.sh' >> /etc/bash.bashrc + +# Install Neos base distribution +RUN --mount=type=cache,target=/root/.composer rm -rf /app \ + && composer create-project neos/neos-base-distribution:^9 /app + +RUN --mount=type=cache,target=/root/.composer composer require rokka/imagine-vips:0.* + +# Add config files +ADD sut-files/ / + +# chown for neos data folder and Resources ONLY +RUN mkdir -p /app/Data /app/Web/_Resources \ + && chown -R ${USER} /app/Data /app/Web/_Resources \ + && chmod +x /entrypoint.sh + +WORKDIR /app +USER ${USER} + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/Tests/sytem_under_test/neos9/docker-compose.yaml b/Tests/sytem_under_test/neos9/docker-compose.yaml new file mode 100644 index 0000000..346753b --- /dev/null +++ b/Tests/sytem_under_test/neos9/docker-compose.yaml @@ -0,0 +1,57 @@ +services: + neos: + user: www-data:www-data + build: + context: . + dockerfile: Dockerfile + environment: + FLOW_CONTEXT: 'Production/E2E-SUT' + # DB connection + DB_NEOS_HOST: 'db' + DB_NEOS_PORT: 3306 + DB_NEOS_USER: 'neos' + DB_NEOS_PASSWORD: 'neos' + DB_NEOS_DATABASE: 'neos' + # Redis connection + REDIS_HOST: 'redis' + REDIS_PORT: 6379 + # this is safe because the neos container port is only exposed to the local interface + # This means that the neos container is ALWAYS accessed through the front facing Ingress + FLOW_HTTP_TRUSTED_PROXIES: '*' + networks: + - neos9_SUT + ports: + - 8081:8081 + depends_on: + - db + - redis + + db: + image: mariadb:10.11 + restart: always + ports: + - "13306:3306" + networks: + - neos9_SUT + environment: + MARIADB_RANDOM_ROOT_PASSWORD: 'true' + MARIADB_DATABASE: 'neos' + MARIADB_USER: 'neos' + MARIADB_PASSWORD: 'neos' + MARIADB_AUTO_UPGRADE: 1 + volumes: + - db:/var/lib/mysql + command: ['mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci'] + + redis: + image: redis:7 + restart: always + networks: + - neos9_SUT + +volumes: + db: + +networks: + neos9_SUT: + name: neos9_SUT diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Caches.yaml b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Caches.yaml new file mode 100644 index 0000000..3844103 --- /dev/null +++ b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Caches.yaml @@ -0,0 +1,56 @@ +Flow_Mvc_Routing_Route: + backend: 'Neos\Cache\Backend\RedisBackend' + backendOptions: + hostname: '%env:REDIS_HOST%' + port: '%env:REDIS_PORT%' + # starting with database 2 here, since 0 and 1 are used and flushed by + # the core unit tests and should not be used if possible. + database: 2 + defaultLifetime: 0 + +Flow_Mvc_Routing_Resolve: + backend: 'Neos\Cache\Backend\RedisBackend' + backendOptions: + hostname: '%env:REDIS_HOST%' + port: '%env:REDIS_PORT%' + database: 2 + defaultLifetime: 0 + +# We want to test cache settings on Fusion components. +# Therefore, at one moment in the BDD test, we need to actively trigger the ContentCacheFlusher to invalidate the tags. +# Since we have multiple Flow contexts during tests, the cache settings are unified for all contexts. +# Explanation: +# - The system under test runs in Production/E2E-SUT; it writes cache entries +# - The Test Runner (behat) runs in Testing/Behat; it needs to invalidate cache for the SuT via service API call +# For now, we simply keep the cache settings in sync between those two profiles (like we do with the DB config). +Neos_Fusion_Content: + backend: 'Neos\Cache\Backend\RedisBackend' + backendOptions: + hostname: '%env:REDIS_HOST%' + port: '%env:REDIS_PORT%' + database: 2 + defaultLifetime: 0 + +Flow_Session_MetaData: + backend: 'Neos\Cache\Backend\RedisBackend' + backendOptions: + hostname: '%env:REDIS_HOST%' + port: '%env:REDIS_PORT%' + database: 2 + defaultLifetime: 0 + +Flow_Session_Storage: + backend: 'Neos\Cache\Backend\RedisBackend' + backendOptions: + hostname: '%env:REDIS_HOST%' + port: '%env:REDIS_PORT%' + database: 2 + defaultLifetime: 0 + +Neos_Media_ImageSize: + backend: 'Neos\Cache\Backend\RedisBackend' + backendOptions: + hostname: '%env:REDIS_HOST%' + port: '%env:REDIS_PORT%' + database: 2 + defaultLifetime: 0 diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Settings.yaml b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Settings.yaml new file mode 100644 index 0000000..27101ea --- /dev/null +++ b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Settings.yaml @@ -0,0 +1,26 @@ +Neos: + Flow: + persistence: + backendOptions: + driver: 'pdo_mysql' + charset: 'utf8mb4' + host: '%env:DB_NEOS_HOST%' + port: '%env:DB_NEOS_PORT%' + password: '%env:DB_NEOS_PASSWORD%' + user: '%env:DB_NEOS_USER%' + dbname: '%env:DB_NEOS_DATABASE%' + cache: + applicationIdentifier: 'app' + + Imagine: + driver: 'Vips' + enabledDrivers: + Vips: true + Gd: true + Imagick: true + + Media: + image: + defaultOptions: + # The Vips driver does not support interlace + interlace: ~ diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/Settings.yaml.example b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/Settings.yaml.example new file mode 100644 index 0000000..58050de --- /dev/null +++ b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/Settings.yaml.example @@ -0,0 +1,19 @@ +# # +# Example Settings # +# # +# This file contains settings for various parts of the application. # +# Copy this file to Settings.yaml, and adjust as necessary. # +# # +# Please refer to the default settings file(s) or the manuals for # +# possible configuration options. # +# # + +Neos: + Flow: + persistence: + # For a reference of the possible options, take a look at + # https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html + backendOptions: + dbname: 'flow' # adjust to your database name + user: '' # adjust to your database user + password: '' # adjust to your database password diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/README b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/README new file mode 100644 index 0000000..1f840e7 --- /dev/null +++ b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/README @@ -0,0 +1,29 @@ +============================================================================= +Flow Configuration Directory +============================================================================= + +This directory contains the configuration for the Flow framework and the +application based on it. The configuration files only contain additions and +modifications of the original configuration and therefore don't contain all +possible configuration options. + +The following files play a role in the configuration system: + + Settings.yaml Various application-level settings can be set in this file. + + Objects.yaml Additional objects configuration, overriding the + configuration which was defined in the various packages. + + Routes.yaml Provides the routing configuration for the MVC framework. + + Views.yaml Can be used to influence resolutions of views in MVC. + + Policy.yaml Provides the configuration for the security framework. + + Caches.yaml Provides the configuration for the caching framework. + +All of these files become active in any application context if they reside +in the main Configuration directory (the same directory where this README is +located). However, context-specific configuration may be defined in sub directories +which carry the same name as the application context. These configuration +files are loaded after the default and global configuration has been invoked. \ No newline at end of file diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Settings.yaml.example b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Settings.yaml.example new file mode 100644 index 0000000..c02aafc --- /dev/null +++ b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Settings.yaml.example @@ -0,0 +1,39 @@ +# +# Example Settings +# +# This file can contain settings for your Flow application that override +# the various defaults defined by other packages. +# +# Copy this file to Settings.yaml, and adjust as necessary. +# +# For possible configuration options you may refer to the packages' +# default settings file(s) in the corresponding package directories (for +# example, Packages/Framework/Neos.Flow/Configuration/Settings.yaml) +# or read the fine manuals. + +Neos: + Flow: + persistence: + + # It is good practice to not specify user name and password of the database + # connection in this global Settings.yaml file. Rather specify them in the + # settings of the respective context (Production / Development ...). + # For a reference of the possible options, take a look at + # https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html + backendOptions: + driver: 'pdo_mysql' # use pdo_pgsql for PostgreSQL + charset: 'utf8mb4' # change to utf8 when using PostgreSQL + host: '127.0.0.1' # adjust to your database host + + # The following lines register the Flow default routes + # For productive use you should move this setting to the main package of this + # application and/or create custom routes. + mvc: + routes: + 'Neos.Flow': TRUE + + # You might need to uncomment the following lines and specify + # the location of the PHP binary manually. +# core: +# phpBinaryPathAndFilename: 'C:/path/to/php.exe' + diff --git a/Tests/sytem_under_test/neos9/sut-files/entrypoint.sh b/Tests/sytem_under_test/neos9/sut-files/entrypoint.sh new file mode 100755 index 0000000..53d1b8a --- /dev/null +++ b/Tests/sytem_under_test/neos9/sut-files/entrypoint.sh @@ -0,0 +1,25 @@ +#!/bin/bash +set -eou pipefail + +# ping db until it is ready +echo "Waiting for database..." +until mariadb -h"${DB_NEOS_HOST}" -P"${DB_NEOS_PORT}" -u"${DB_NEOS_USER}" -p"${DB_NEOS_PASSWORD}" -D"${DB_NEOS_DATABASE}" --disable-ssl --silent -e "SELECT 1;" 2>/dev/null; do + sleep 2 +done + +./flow flow:cache:flush + +# this does the warmup as well +./flow doctrine:migrate + +yes y | ./flow resource:clean || true + +./flow cr:setup +./flow cr:status + +./flow site:importall --package-key Neos.Demo + +./flow resource:publish --collection static + +# 2. We now can start caretakerd, which will run the remaining steps in parallel and restart the container if they fail. +/usr/bin/caretakerd run diff --git a/Tests/sytem_under_test/neos9/sut-files/etc/bash.vips-arm64-hotfix.sh b/Tests/sytem_under_test/neos9/sut-files/etc/bash.vips-arm64-hotfix.sh new file mode 100644 index 0000000..9bb2110 --- /dev/null +++ b/Tests/sytem_under_test/neos9/sut-files/etc/bash.vips-arm64-hotfix.sh @@ -0,0 +1,17 @@ +if [ "$(uname -m)" = "aarch64" ]; then + echo "Using LD_Preload workaround for gomp issue" + + # WORKAROUND for Apple M1 Chips. Without this line, we get the error message: + # + # Warning: PHP Startup: Unable to load dynamic library 'vips.so' (tried: + # /usr/local/lib/php/extensions/no-debug-non-zts-20200930/vips.so + # (/usr/lib/aarch64-linux-gnu/libgomp.so.1: cannot allocate memory in + # static TLS block), /usr/local/lib/php/extensions/no-debug-non-zts-20200930/vips.so.so + # (/usr/local/lib/php/extensions/no-debug-non-zts-20200930/vips.so.so: cannot open + # shared object file: No such file or directory)) in Unknown on line 0 + # + # This error seems to be related to some OpenCV bug or issue described at + # https://github.com/opencv/opencv/issues/14884#issuecomment-706725583 + # And the workaround is to ensure that libgomp is loaded first. + export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libgomp.so.1 +fi diff --git a/Tests/sytem_under_test/neos9/sut-files/etc/caretakerd.yaml b/Tests/sytem_under_test/neos9/sut-files/etc/caretakerd.yaml new file mode 100755 index 0000000..f57404b --- /dev/null +++ b/Tests/sytem_under_test/neos9/sut-files/etc/caretakerd.yaml @@ -0,0 +1,9 @@ +# DOCS: https://caretakerd.echocat.org/#configuration.structure + +rpc: + enabled: true + +services: + php-fpm: + type: master + command: ['frankenphp', 'run', '--config', '/etc/frankenphp/Caddyfile'] diff --git a/Tests/sytem_under_test/neos9/sut-files/etc/frankenphp/Caddyfile b/Tests/sytem_under_test/neos9/sut-files/etc/frankenphp/Caddyfile new file mode 100644 index 0000000..c2d5f0f --- /dev/null +++ b/Tests/sytem_under_test/neos9/sut-files/etc/frankenphp/Caddyfile @@ -0,0 +1,41 @@ +# The Caddyfile is an easy way to configure FrankenPHP and the Caddy web server. +# +# https://frankenphp.dev/docs/config +# https://caddyserver.com/docs/caddyfile +# https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile +{ + skip_install_trust + + # debug + + frankenphp { + # num_threads # Sets the number of PHP threads to start. Default: 2x the number of available CPUs. + #max_threads # Limits the number of additional PHP threads that can be started at runtime. Default: num_threads. Can be set to 'auto'. + #max_wait_time # Sets the maximum time a request may wait for a free PHP thread before timing out. Default: disabled. + #php_ini # Set a php.ini directive. Can be used several times to set multiple directives. + } +} + +:8081 { + log + + root /app/Web + encode zstd br gzip + + request_body { + max_size 256MB + } + + # Block direct access to PHP files except index.php + @blockPhp { + path *.php + not path /index.php + } + + handle @blockPhp { + respond 404 + } + + + php_server +} diff --git a/Tests/sytem_under_test/neos9/sut-files/usr/local/etc/php/conf.d/php-ini-overrides.ini b/Tests/sytem_under_test/neos9/sut-files/usr/local/etc/php/conf.d/php-ini-overrides.ini new file mode 100644 index 0000000..1190ae0 --- /dev/null +++ b/Tests/sytem_under_test/neos9/sut-files/usr/local/etc/php/conf.d/php-ini-overrides.ini @@ -0,0 +1,20 @@ +; ================== +; Various defaults +; ================== + +; for PHP >= 8.1, disable deprecations to temporarily make Neos/Flow work +error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT +memory_limit = 512M +upload_max_filesize = 256M +post_max_size = 256M +date.timezone = "Europe/Berlin" + +; ================== +; VIPS +; ================== +; required for VIPS +ffi.enable = true + +; for VIPS on PHP >= 8.3 the following line is required, +; see https://github.com/libvips/php-vips +zend.max_allowed_stack_size = -1 From fa1e30ce73d0522c676493c41a01c4a131023dcd Mon Sep 17 00:00:00 2001 From: Robert Baruck Date: Tue, 24 Mar 2026 15:30:32 +0100 Subject: [PATCH 02/12] WIP tests --- .dockerignore | 3 + .gitignore | 1 + .../Command/SecondFactorCommandController.php | 42 +++ Configuration/Settings.2FA.yaml | 10 + Configuration/Settings.yaml | 11 - Tests/E2E/.nvmrc | 1 + Tests/E2E/global-teardown.ts | 13 + Tests/E2E/helpers/pages.ts | 77 +++++ Tests/E2E/helpers/totp.ts | 5 + Tests/E2E/package-lock.json | 210 +++++++++++ Tests/E2E/package.json | 23 ++ Tests/E2E/playwright.config.ts | 33 ++ Tests/E2E/test-results/.last-run.json | 14 + .../error-context.md | 25 ++ .../error-context.md | 327 ++++++++++++++++++ .../error-context.md | 327 ++++++++++++++++++ .../error-context.md | 327 ++++++++++++++++++ .../error-context.md | 327 ++++++++++++++++++ .../error-context.md | 327 ++++++++++++++++++ .../error-context.md | 25 ++ .../error-context.md | 25 ++ .../error-context.md | 25 ++ Tests/E2E/tests/backend-module.spec.ts | 74 ++++ Tests/E2E/tests/enforce-for-all.spec.ts | 24 ++ Tests/E2E/tests/enforce-for-provider.spec.ts | 25 ++ Tests/E2E/tests/enforce-for-role.spec.ts | 26 ++ Tests/E2E/tests/issuer-name-change.spec.ts | 47 +++ Tests/E2E/tests/login-without-2fa.spec.ts | 12 + Tests/E2E/tests/second-factor-login.spec.ts | 77 +++++ Tests/E2E/tests/second-factor-setup.spec.ts | 48 +++ Tests/E2E/tsconfig.json | 43 +++ Tests/sytem_under_test/neos8/Dockerfile | 14 +- .../neos8/docker-compose.yaml | 6 +- .../E2E-SUT/EnforceForAll/Settings.2FA.yaml | 4 + .../EnforceForProvider/Settings.2FA.yaml | 4 + .../E2E-SUT/EnforceForRole/Settings.2FA.yaml | 4 + .../IssuerNameChange/Settings.2FA.yaml | 4 + .../neos8/sut-files/entrypoint.sh | 3 + Tests/sytem_under_test/neos9/Dockerfile | 11 +- .../neos9/docker-compose.yaml | 6 +- .../E2E-SUT/EnforceForAll/Settings.2FA.yaml | 4 + .../EnforceForProvider/Settings.2FA.yaml | 4 + .../E2E-SUT/EnforceForRole/Settings.2FA.yaml | 4 + .../IssuerNameChange/Settings.2FA.yaml | 4 + .../neos9/sut-files/entrypoint.sh | 3 + 45 files changed, 2609 insertions(+), 20 deletions(-) create mode 100644 .dockerignore create mode 100644 Classes/Command/SecondFactorCommandController.php create mode 100644 Configuration/Settings.2FA.yaml create mode 100644 Tests/E2E/.nvmrc create mode 100644 Tests/E2E/global-teardown.ts create mode 100644 Tests/E2E/helpers/pages.ts create mode 100644 Tests/E2E/helpers/totp.ts create mode 100644 Tests/E2E/package-lock.json create mode 100644 Tests/E2E/package.json create mode 100644 Tests/E2E/playwright.config.ts create mode 100644 Tests/E2E/test-results/.last-run.json create mode 100644 Tests/E2E/test-results/backend-module-backend-module-shows-enrolled-2FA-devices-chromium/error-context.md create mode 100644 Tests/E2E/test-results/enforce-for-all-administrator-is-redirected-to-2FA-setup-chromium/error-context.md create mode 100644 Tests/E2E/test-results/enforce-for-all-editor-is-redirected-to-2FA-setup-chromium/error-context.md create mode 100644 Tests/E2E/test-results/enforce-for-provider-admin-97496--is-redirected-to-2FA-setup-chromium/error-context.md create mode 100644 Tests/E2E/test-results/enforce-for-provider-editor-is-redirected-to-2FA-setup-chromium/error-context.md create mode 100644 Tests/E2E/test-results/enforce-for-role-administrator-is-redirected-to-2FA-setup-chromium/error-context.md create mode 100644 Tests/E2E/test-results/issuer-name-change-2FA-set-63185-with-the-custom-issuer-name-chromium/error-context.md create mode 100644 Tests/E2E/test-results/second-factor-login-login--8650e-en-a-2FA-device-is-enrolled-chromium/error-context.md create mode 100644 Tests/E2E/test-results/second-factor-setup-comple-4d756-ts-away-from-the-setup-page-chromium/error-context.md create mode 100644 Tests/E2E/tests/backend-module.spec.ts create mode 100644 Tests/E2E/tests/enforce-for-all.spec.ts create mode 100644 Tests/E2E/tests/enforce-for-provider.spec.ts create mode 100644 Tests/E2E/tests/enforce-for-role.spec.ts create mode 100644 Tests/E2E/tests/issuer-name-change.spec.ts create mode 100644 Tests/E2E/tests/login-without-2fa.spec.ts create mode 100644 Tests/E2E/tests/second-factor-login.spec.ts create mode 100644 Tests/E2E/tests/second-factor-setup.spec.ts create mode 100644 Tests/E2E/tsconfig.json create mode 100644 Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForAll/Settings.2FA.yaml create mode 100644 Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForProvider/Settings.2FA.yaml create mode 100644 Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml create mode 100644 Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/IssuerNameChange/Settings.2FA.yaml create mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForAll/Settings.2FA.yaml create mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForProvider/Settings.2FA.yaml create mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml create mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/IssuerNameChange/Settings.2FA.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fdbf90e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git/ +.idea/ +node_modules/ diff --git a/.gitignore b/.gitignore index e6abe13..e9a196e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # 3rd party sources Packages/ vendor/ +node_modules/ # composer composer.lock diff --git a/Classes/Command/SecondFactorCommandController.php b/Classes/Command/SecondFactorCommandController.php new file mode 100644 index 0000000..660ad48 --- /dev/null +++ b/Classes/Command/SecondFactorCommandController.php @@ -0,0 +1,42 @@ +secondFactorRepository->createQuery(); + $factors = $query->matching( + $query->equals('account.accountIdentifier', $username) + )->execute(); + + $count = 0; + foreach ($factors as $factor) { + $this->secondFactorRepository->remove($factor); + $count++; + } + $this->persistenceManager->persistAll(); + + $this->outputLine('Deleted %d second factor(s) for account "%s".', [$count, $username]); + } +} diff --git a/Configuration/Settings.2FA.yaml b/Configuration/Settings.2FA.yaml new file mode 100644 index 0000000..3ee0629 --- /dev/null +++ b/Configuration/Settings.2FA.yaml @@ -0,0 +1,10 @@ +Sandstorm: + NeosTwoFactorAuthentication: + # enforce 2FA for all users + enforceTwoFactorAuthentication: false + # enforce 2FA for specific authentication providers (e.g. Neos.Neos:Backend) + enforce2FAForAuthenticationProviders : [] + # enforce 2FA for specific roles (e.g. Neos.Neos:Administrator) + enforce2FAForRoles: [] + # (optional) if set this will be used as a naming convention for the TOTP. If empty the Site name will be used + issuerName: '' diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index 559bc32..a5d32b1 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -48,14 +48,3 @@ Neos: pattern: 'ControllerObjectName' patternOptions: controllerObjectNamePattern: 'Sandstorm\NeosTwoFactorAuthentication\Controller\(LoginController|BackendController)' - -Sandstorm: - NeosTwoFactorAuthentication: - # enforce 2FA for all users - enforceTwoFactorAuthentication: false - # enforce 2FA for specific authentication providers - enforce2FAForAuthenticationProviders : [] - # enforce 2FA for specific roles - enforce2FAForRoles: [] - # (optional) if set this will be used as a naming convention for the TOTP. If empty the Site name will be used - issuerName: '' diff --git a/Tests/E2E/.nvmrc b/Tests/E2E/.nvmrc new file mode 100644 index 0000000..a4a7a41 --- /dev/null +++ b/Tests/E2E/.nvmrc @@ -0,0 +1 @@ +v24.14.0 diff --git a/Tests/E2E/global-teardown.ts b/Tests/E2E/global-teardown.ts new file mode 100644 index 0000000..5c9378e --- /dev/null +++ b/Tests/E2E/global-teardown.ts @@ -0,0 +1,13 @@ +import { execSync } from 'node:child_process'; + +export default async function globalTeardown() { + if (process.env.REUSE_SUT) { + return; + } + const sut = process.env.SUT || 'neos8'; + const flowContext = process.env.FLOW_CONTEXT || 'Production/E2E-SUT'; + execSync( + `FLOW_CONTEXT=${flowContext} docker compose -f ../sytem_under_test/${sut}/docker-compose.yaml down -v`, + { stdio: 'inherit', cwd: __dirname } + ); +} diff --git a/Tests/E2E/helpers/pages.ts b/Tests/E2E/helpers/pages.ts new file mode 100644 index 0000000..b1db9d4 --- /dev/null +++ b/Tests/E2E/helpers/pages.ts @@ -0,0 +1,77 @@ +import { Page, expect } from '@playwright/test'; +import { generateOtp } from './totp'; + +export class NeosLoginPage { + constructor(private page: Page) {} + + async goto() { + await this.page.goto('/neos/login'); + } + + async login(username: string, password: string) { + await this.page.locator('input[type="text"]').fill(username); + await this.page.locator('input[type="password"]').fill(password); + await this.page.locator('.neos-login-btn:not(.neos-disabled):not(.neos-hidden)').click(); + } +} + +export class SecondFactorLoginPage { + constructor(private page: Page) {} + + async waitForPage() { + await this.page.waitForURL('**/neos/second-factor-login'); + } + + async enterOtp(otp: string) { + await this.page.locator('input#secondFactor').fill(otp); + await this.page.locator('.neos-login-btn:not(.neos-disabled):not(.neos-hidden)').first().click(); + } + + async getErrorMessage(): Promise { + const el = this.page.locator('.neos-tooltip-error .neos-tooltip-inner'); + await el.waitFor(); + return el.innerText(); + } +} + +export class SecondFactorSetupPage { + constructor(private page: Page) {} + + async waitForPage() { + await this.page.waitForURL('**/neos/second-factor-setup'); + } + + async getSecret(): Promise { + const secret = await this.page.locator('input#secret').getAttribute('value'); + if (!secret) throw new Error('Secret not found on setup page'); + return secret; + } + + async submitOtp(secret: string) { + const otp = generateOtp(secret); + await this.page.locator('input#secondFactorFromApp').fill(otp); + await this.page.locator('button[type="submit"]').click(); + } +} + +export class BackendModulePage { + constructor(private page: Page) {} + + async goto() { + await this.page.goto('/neos/management/twoFactorAuthentication'); + } + + async waitForPage() { + await this.page.waitForURL('**/neos/management/twoFactorAuthentication**'); + } + + async getDeviceCount(): Promise { + return this.page.locator('.neos-table tbody tr').count(); + } + + async deleteFirstDevice() { + await this.page.locator('button.neos-button-danger').first().click(); + // Confirm in modal + await this.page.locator('.neos-modal-footer button.neos-button-danger').click(); + } +} diff --git a/Tests/E2E/helpers/totp.ts b/Tests/E2E/helpers/totp.ts new file mode 100644 index 0000000..a970783 --- /dev/null +++ b/Tests/E2E/helpers/totp.ts @@ -0,0 +1,5 @@ +import { authenticator } from 'otplib'; + +export function generateOtp(secret: string): string { + return authenticator.generate(secret); +} diff --git a/Tests/E2E/package-lock.json b/Tests/E2E/package-lock.json new file mode 100644 index 0000000..dc9f57c --- /dev/null +++ b/Tests/E2E/package-lock.json @@ -0,0 +1,210 @@ +{ + "name": "sandstorm-neostwofactorauthentication-e2e", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandstorm-neostwofactorauthentication-e2e", + "devDependencies": { + "@playwright/test": "^1.58.2", + "@types/node": "^25.5.0", + "otplib": "^13.4.0", + "typescript": "^6.0.2" + } + }, + "node_modules/@noble/hashes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@otplib/core": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@otplib/core/-/core-13.4.0.tgz", + "integrity": "sha512-JqOGcvZQi2wIkEQo8f3/iAjstavpXy6gouIDMHygjNuH6Q0FjbHOiXMdcE94RwfgDNMABhzwUmvaPsxvgm9NYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@otplib/hotp": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@otplib/hotp/-/hotp-13.4.0.tgz", + "integrity": "sha512-MJjE0x06mn2ptymz5qZmQveb+vWFuaIftqE0b5/TZZqUOK7l97cV8lRTmid5BpAQMwJDNLW6RnYxGeCRiNdekw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@otplib/core": "13.4.0", + "@otplib/uri": "13.4.0" + } + }, + "node_modules/@otplib/plugin-base32-scure": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@otplib/plugin-base32-scure/-/plugin-base32-scure-13.4.0.tgz", + "integrity": "sha512-/t9YWJmMbB8bF5z8mXrBZc2FXBe8B/3hG5FhWr9K8cFwFhyxScbPysmZe8s1UTzSA6N+s8Uv8aIfCtVXPNjJWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@otplib/core": "13.4.0", + "@scure/base": "^2.0.0" + } + }, + "node_modules/@otplib/plugin-crypto-noble": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto-noble/-/plugin-crypto-noble-13.4.0.tgz", + "integrity": "sha512-KrvE4m7Zv+TT1944HzgqFJWJpKb6AyoxDbvhPStmBqdMlv5Gekb80d66cuFRL08kkPgJ5gXUSb5SFpYeB+bACg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^2.0.1", + "@otplib/core": "13.4.0" + } + }, + "node_modules/@otplib/totp": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@otplib/totp/-/totp-13.4.0.tgz", + "integrity": "sha512-dK+vl0f0ekzf6mCENRI9AKS2NJUC7OjI3+X8e7QSnhQ2WM7I+i4PGpb3QxKi5hxjTtwVuoZwXR2CFtXdcRtNdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@otplib/core": "13.4.0", + "@otplib/hotp": "13.4.0", + "@otplib/uri": "13.4.0" + } + }, + "node_modules/@otplib/uri": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@otplib/uri/-/uri-13.4.0.tgz", + "integrity": "sha512-x1ozBa5bPbdZCrrTL/HK21qchiK7jYElTu+0ft22abeEhiLYgH1+SIULvOcVk3CK8YwF4kdcidvkq4ciejucJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@otplib/core": "13.4.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@scure/base": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.0.0.tgz", + "integrity": "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/otplib": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/otplib/-/otplib-13.4.0.tgz", + "integrity": "sha512-RUcYcRMCgRWhUE/XabRppXpUwCwaWBNHe5iPXhdvP8wwDGpGpsIf/kxX/ec3zFsOaM1Oq8lEhUqDwk6W7DHkwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@otplib/core": "13.4.0", + "@otplib/hotp": "13.4.0", + "@otplib/plugin-base32-scure": "13.4.0", + "@otplib/plugin-crypto-noble": "13.4.0", + "@otplib/totp": "13.4.0", + "@otplib/uri": "13.4.0" + } + }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/Tests/E2E/package.json b/Tests/E2E/package.json new file mode 100644 index 0000000..5a4cb43 --- /dev/null +++ b/Tests/E2E/package.json @@ -0,0 +1,23 @@ +{ + "name": "sandstorm-neostwofactorauthentication-e2e", + "private": true, + "scripts": { + "test:neos8": "SUT=neos8 npx playwright test --headed", + "test:neos8:enforce-all": "SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForAll npx playwright test tests/enforce-for-all.spec.ts", + "test:neos8:enforce-role": "SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole npx playwright test tests/enforce-for-role.spec.ts", + "test:neos8:enforce-provider": "SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider npx playwright test tests/enforce-for-provider.spec.ts", + "test:neos8:issuer-name": "SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/IssuerNameChange npx playwright test tests/issuer-name-change.spec.ts", + "test:neos9": "SUT=neos9 npx playwright test", + "test:neos9:enforce-all": "SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForAll npx playwright test tests/enforce-for-all.spec.ts", + "test:neos9:enforce-role": "SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole npx playwright test tests/enforce-for-role.spec.ts", + "test:neos9:enforce-provider": "SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider npx playwright test tests/enforce-for-provider.spec.ts", + "test:neos9:issuer-name": "SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/IssuerNameChange npx playwright test tests/issuer-name-change.spec.ts", + "test": "npm run test:neos8 && npm run test:neos8:enforce-all && npm run test:neos8:enforce-role && npm run test:neos8:enforce-provider && npm run test:neos8:issuer-name && npm run test:neos9 && npm run test:neos9:enforce-all && npm run test:neos9:enforce-role && npm run test:neos9:enforce-provider && npm run test:neos9:issuer-name" + }, + "devDependencies": { + "@playwright/test": "^1.58.2", + "@types/node": "^25.5.0", + "otplib": "^13.4.0", + "typescript": "^6.0.2" + } +} diff --git a/Tests/E2E/playwright.config.ts b/Tests/E2E/playwright.config.ts new file mode 100644 index 0000000..1f27434 --- /dev/null +++ b/Tests/E2E/playwright.config.ts @@ -0,0 +1,33 @@ +import { defineConfig, devices } from '@playwright/test'; + +const SUT = process.env.SUT || 'neos8'; +const FLOW_CONTEXT = process.env.FLOW_CONTEXT || 'Production/E2E-SUT'; +const HEADLESS = process.env.HEADLESS !== 'false'; +const sutDir = `../sytem_under_test/${SUT}`; + +export default defineConfig({ + testDir: './tests', + fullyParallel: false, + workers: 1, + retries: 0, + use: { + baseURL: 'http://localhost:8081', + headless: HEADLESS, + trace: 'on-first-retry', + }, + globalTeardown: './global-teardown.ts', + webServer: { + command: `FLOW_CONTEXT=${FLOW_CONTEXT} docker compose -f ${sutDir}/docker-compose.yaml up --build`, + url: 'http://localhost:8081/neos/login', + reuseExistingServer: !!process.env.REUSE_SUT, + timeout: 600_000, + stdout: 'pipe', + stderr: 'pipe', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}); diff --git a/Tests/E2E/test-results/.last-run.json b/Tests/E2E/test-results/.last-run.json new file mode 100644 index 0000000..738842c --- /dev/null +++ b/Tests/E2E/test-results/.last-run.json @@ -0,0 +1,14 @@ +{ + "status": "failed", + "failedTests": [ + "f3be858742d2a3b00c66-d143c8f7852f60650513", + "5f59ffc4d5796e3b8b4e-3d928260d0e69b8582bf", + "5f59ffc4d5796e3b8b4e-f5fb17f37c442b4e0719", + "d891987aa24c5b614103-45830fca0237deef430e", + "d891987aa24c5b614103-a5b74a87c56b90155e44", + "6a4a1a14e2f074902f6e-b6275a7c4cf69acc0089", + "871a0b6f817d5ed73bdd-5af3010376f9d40029af", + "498ffde57e01a71f086f-d2349f8fa3fc72faa303", + "c90b11560d4b8aa219dc-e07dbcf29cd8b53b3353" + ] +} \ No newline at end of file diff --git a/Tests/E2E/test-results/backend-module-backend-module-shows-enrolled-2FA-devices-chromium/error-context.md b/Tests/E2E/test-results/backend-module-backend-module-shows-enrolled-2FA-devices-chromium/error-context.md new file mode 100644 index 0000000..d21e3c5 --- /dev/null +++ b/Tests/E2E/test-results/backend-module-backend-module-shows-enrolled-2FA-devices-chromium/error-context.md @@ -0,0 +1,25 @@ +# Page snapshot + +```yaml +- generic [ref=e2]: + - main [ref=e3]: + - generic [ref=e4]: + - figure [ref=e5]: + - img "Neos Logo" [ref=e6] + - heading "Login to Neos Demo Site" [level=1] [ref=e7]: + - text: Login to + - strong [ref=e8]: Neos Demo Site + - generic [ref=e10]: + - img [ref=e12] + - button "Show Code" [ref=e15] [cursor=pointer] + - textbox "OTP from App" [ref=e17] + - button "Register new OTP" [ref=e19] [cursor=pointer] + - contentinfo [ref=e20]: + - paragraph [ref=e21]: + - link "Neos" [ref=e22] [cursor=pointer]: + - /url: http://neos.io + - text: – © 2006-2026 This is free software, licensed under GPL3 or higher, and you are welcome to redistribute it under certain conditions; Neos comes with ABSOLUTELY NO WARRANTY; See + - link "neos.io" [ref=e23] [cursor=pointer]: + - /url: http://neos.io + - text: for more details. Obstructing the appearance of this notice is prohibited by law. +``` \ No newline at end of file diff --git a/Tests/E2E/test-results/enforce-for-all-administrator-is-redirected-to-2FA-setup-chromium/error-context.md b/Tests/E2E/test-results/enforce-for-all-administrator-is-redirected-to-2FA-setup-chromium/error-context.md new file mode 100644 index 0000000..018279c --- /dev/null +++ b/Tests/E2E/test-results/enforce-for-all-administrator-is-redirected-to-2FA-setup-chromium/error-context.md @@ -0,0 +1,327 @@ +# Page snapshot + +```yaml +- generic [ref=e4]: + - generic [ref=e5]: + - generic [ref=e6]: + - button "Menu" [ref=e7] [cursor=pointer] + - img [ref=e11] + - generic [ref=e16]: + - button "In-Place" [ref=e19] [cursor=pointer]: + - img + - generic [ref=e20]: In-Place + - img + - button "English (US)" [ref=e23] [cursor=pointer]: + - button "English (US)" [ref=e26]: + - generic [ref=e27]: + - img + - link "English (US)" [ref=e28]: + - /url: http://localhost:8081/neos/content?node=%2Fsites%2Fneosdemo%3Blanguage%3Den_US + - img + - button "E2E Admin" [ref=e31] [cursor=pointer]: + - img + - text: E2E Admin + - img + - generic [ref=e32]: + - button "Published - live" [disabled] [ref=e33] + - button [ref=e34] [cursor=pointer]: + - img + - generic [ref=e36]: + - button "Display Keyboard Shortcuts" [ref=e37] [cursor=pointer]: + - img [ref=e38] + - link "Show preview" [ref=e40] [cursor=pointer]: + - /url: http://localhost:8081/neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eadmin%3Blanguage%3Den_US + - img [ref=e41] + - button "Activate Fullscreen edit mode" [ref=e43] [cursor=pointer]: + - img [ref=e44] + - iframe [ref=e48]: + - generic [active] [ref=f1e1]: + - banner [ref=f1e2]: + - generic [ref=f1e3]: + - link [ref=f1e4] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eadmin%3Blanguage%3Den_US + - img [ref=f1e5] + - navigation + - button "Open menu" [ref=f1e11] [cursor=pointer]: + - generic [ref=f1e12]: Open menu + - img [ref=f1e13] + - navigation [ref=f1e15]: + - button "English (US)" [ref=f1e16] [cursor=pointer]: + - text: English (US) + - img [ref=f1e17] + - heading "Welcome to the Neos CMS demo" [level=1] [ref=f1e22]: + - textbox "Rich Text Editor, main" [ref=f1e23] + - main [ref=f1e24]: + - generic [ref=f1e25]: + - generic [ref=f1e26]: + - generic [ref=f1e27]: + - heading "Imagine this..." [level=2] [ref=f1e28]: + - textbox "Rich Text Editor, main" [ref=f1e29] + - textbox "Rich Text Editor, main" [ref=f1e31]: + - paragraph [ref=f1e32]: In only a few years from now, the world will have entirely new cities. Cities where development has not even started. Cities that can draw from all we know about liveability and functionality. + - paragraph [ref=f1e33]: "The same goes for websites. Major websites will be built with the tools we’re constructing today. But we don't know how they will be like. Just think of how different sites were 3 years ago. For both cities and websites, one basic rule counts: Things are constantly changing and can never be fully anticipated." + - paragraph [ref=f1e34]: The inspiration from urban planning permeates our UX master plan, giving us crucial guidelines for structuring everything we know - and making room for all the future change we don’t know. + - generic [ref=f1e35]: + - heading "Power when you need it" [level=2] [ref=f1e36]: + - textbox "Rich Text Editor, main" [ref=f1e37] + - textbox "Rich Text Editor, main" [ref=f1e39]: + - paragraph [ref=f1e40]: The distribution of power in a city makes sure that everyone gets what they need. You don’t power a shed in the way you power a nuclear plant. The ultra-simple editing makes editors center their attention on content. If you need to add SEO data next to the content, Neos has an inspector that opens up for that kind of functionality. Just a bit more power, one step at a time... + - paragraph [ref=f1e41]: If you need more, the inspector will be expandable to the full backend screen providing the user with all the options. That’s not enough? Create custom backend modules. And if even that's not enough, underlying Neos, we have Flow - a first-class web framework that enables you to build web applications that integrate with your other systems. + - generic [ref=f1e42]: + - generic [ref=f1e43]: + - heading "Create an account and access the backend" [level=2] [ref=f1e44]: + - textbox "Rich Text Editor, main" [ref=f1e45]: + - generic [ref=f1e46]: Create an account and access the backend + - textbox "Rich Text Editor, main" [ref=f1e48]: + - paragraph [ref=f1e49]: Try out our latest development of the Neos user interface. Get a first impression of CKEditor, the content editor, and try out the simple publishing mechanism based on workspaces. + - paragraph [ref=f1e50]: The registration form is also an example of a "Plugin"-element. + - generic [ref=f1e53]: + - group [ref=f1e54]: + - generic [ref=f1e55]: + - generic [ref=f1e56]: Username + - textbox "Username" [ref=f1e57] + - generic [ref=f1e58]: (lowercase letters and numbers only) + - generic [ref=f1e59]: + - generic [ref=f1e60]: Password + - textbox "Password" [ref=f1e61] + - button "Submit" [ref=f1e62] [cursor=pointer] + - contentinfo [ref=f1e64]: + - navigation [ref=f1e65]: + - link "Contribute" [ref=f1e66] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fcontribute%40user-e2eadmin%3Blanguage%3Den_US + - link "Documentation & Support" [ref=f1e67] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdocumentation%40user-e2eadmin%3Blanguage%3Den_US + - link "Download" [ref=f1e68] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdownload%40user-e2eadmin%3Blanguage%3Den_US + - textbox "Rich Text Editor, main" [ref=f1e72]: + - paragraph [ref=f1e73]: Powered by Neos & Flow + - generic [ref=e53]: "UI version: 8.4.3" + - button "Document Tree" [ref=e54] [cursor=pointer]: + - button "Navigate" [ref=e55]: + - img [ref=e56] + - text: Document Tree + - generic [ref=e58]: + - generic [ref=e59]: + - generic [ref=e62]: + - button "Create new" [ref=e63] [cursor=pointer]: + - img [ref=e64] + - button "Hide / Unhide" [disabled] [ref=e66]: + - img [ref=e67] + - button "Copy" [ref=e69] [cursor=pointer]: + - img [ref=e70] + - button "Cut" [disabled] [ref=e72]: + - img [ref=e73] + - button "Paste" [disabled] [ref=e75]: + - img [ref=e76] + - button "Delete" [disabled] [ref=e78]: + - img [ref=e79] + - button "Refresh" [ref=e81] [cursor=pointer]: + - img [ref=e82] + - button [ref=e84] [cursor=pointer]: + - img [ref=e85] + - tree [ref=e87]: + - treeitem "Home" [ref=e88]: + - button "Collapse All" [ref=e89] [cursor=pointer]: + - img [ref=e90] + - treeitem "Home" [expanded] [ref=e92]: + - generic [ref=e94]: + - button "Home" [ref=e96] [cursor=pointer]: + - generic [ref=e97]: + - img [ref=e99] + - treeitem "Home" [ref=e101] + - button [ref=e102] [cursor=pointer]: + - img [ref=e103] + - generic [ref=e105]: + - treeitem "Home" [expanded] [ref=e106]: + - button "Home" [ref=e110] [cursor=pointer]: + - generic [ref=e111]: + - img [ref=e113] + - treeitem "Home" [ref=e115] + - treeitem "Features" [expanded] [ref=e116]: + - generic [ref=e118]: + - button "Features" [ref=e120] [cursor=pointer]: + - generic [ref=e121]: + - img [ref=e123] + - treeitem "Features" [ref=e125] + - button [ref=e126] [cursor=pointer]: + - img [ref=e127] + - generic [ref=e129]: + - treeitem "Multiple columns" [ref=e130]: + - button "Multiple columns" [ref=e134] [cursor=pointer]: + - generic [ref=e135]: + - img [ref=e137] + - treeitem "Multiple columns" [ref=e139] + - treeitem "Text & images" [ref=e140]: + - button "Text & images" [ref=e144] [cursor=pointer]: + - generic [ref=e145]: + - img [ref=e147] + - treeitem "Text & images" [ref=e149] + - treeitem "Navigation elements" [ref=e150]: + - generic [ref=e152]: + - button "Navigation elements" [ref=e154] [cursor=pointer]: + - generic [ref=e155]: + - img [ref=e157] + - treeitem "Navigation elements" [ref=e159] + - button [ref=e160] [cursor=pointer]: + - img [ref=e161] + - treeitem "Custom elements" [ref=e163]: + - button "Custom elements" [ref=e167] [cursor=pointer]: + - generic [ref=e168]: + - img [ref=e170] + - treeitem "Custom elements" [ref=e172] + - treeitem "Other elements" [ref=e173]: + - button "Other elements" [ref=e177] [cursor=pointer]: + - generic [ref=e178]: + - img [ref=e180] + - treeitem "Other elements" [ref=e182] + - treeitem "Forms" [ref=e183]: + - button "Forms" [ref=e187] [cursor=pointer]: + - generic [ref=e188]: + - img [ref=e190] + - treeitem "Forms" [ref=e192] + - treeitem "Blog" [expanded] [ref=e194]: + - generic [ref=e196]: + - button "Blog" [ref=e198] [cursor=pointer]: + - generic [ref=e199]: + - img [ref=e201] + - treeitem "Blog" [ref=e203] + - button [ref=e204] [cursor=pointer]: + - img [ref=e205] + - generic [ref=e207]: + - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e208]: + - button "Neos 8.0 \"Mad Hatter\" released" [ref=e212] [cursor=pointer]: + - generic [ref=e213]: + - img [ref=e215] + - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e217] + - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e218]: + - button "Neos 8.1 \"Frog–Footman\" released" [ref=e222] [cursor=pointer]: + - generic [ref=e223]: + - img [ref=e225] + - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e227] + - treeitem "Neos 8.2 released" [ref=e228]: + - button "Neos 8.2 released" [ref=e232] [cursor=pointer]: + - generic [ref=e233]: + - img [ref=e235] + - treeitem "Neos 8.2 released" [ref=e237] + - treeitem "Neos 8.3 released" [ref=e238]: + - button "Neos 8.3 released" [ref=e242] [cursor=pointer]: + - generic [ref=e243]: + - img [ref=e245] + - treeitem "Neos 8.3 released" [ref=e247] + - treeitem "Neos 8.4 released" [ref=e248]: + - button "Neos 8.4 released" [ref=e252] [cursor=pointer]: + - generic [ref=e253]: + - img [ref=e255] + - treeitem "Neos 8.4 released" [ref=e257] + - treeitem "Download" [expanded] [ref=e259]: + - button "Download" [ref=e263] [cursor=pointer]: + - generic [ref=e264]: + - img [ref=e266] + - treeitem "Download" [ref=e268] + - treeitem "Contribute" [expanded] [ref=e269]: + - button "Contribute" [ref=e273] [cursor=pointer]: + - generic [ref=e274]: + - img [ref=e276] + - treeitem "Contribute" [ref=e278] + - treeitem "Documentation & Support" [expanded] [ref=e279]: + - button "Documentation & Support" [ref=e283] [cursor=pointer]: + - generic [ref=e284]: + - img [ref=e286] + - treeitem "Documentation & Support" [ref=e288] + - treeitem "Not Found" [expanded] [ref=e289]: + - button "Not Found" [ref=e293] [cursor=pointer]: + - generic [ref=e294]: + - img [ref=e296] + - treeitem "Not Found" [ref=e298] + - generic [ref=e301]: + - button "Toggle content tree Content Tree" [ref=e303] [cursor=pointer]: + - button "Toggle content tree" [ref=e304]: + - img [ref=e305] + - text: Content Tree + - generic [ref=e308]: + - button "Create new" [ref=e309] [cursor=pointer]: + - img [ref=e310] + - button "Hide / Unhide" [ref=e312] [cursor=pointer]: + - img [ref=e313] + - button "Copy" [ref=e315] [cursor=pointer]: + - img [ref=e316] + - button "Cut" [ref=e318] [cursor=pointer]: + - img [ref=e319] + - button "Paste" [disabled] [ref=e321]: + - img [ref=e322] + - button "Delete" [ref=e324] [cursor=pointer]: + - img [ref=e325] + - button "Refresh" [ref=e327] [cursor=pointer]: + - img [ref=e328] + - form [ref=e330]: + - button "Toggle inspector" [ref=e331] [cursor=pointer]: + - img [ref=e332] + - generic [ref=e335]: + - generic [ref=e336]: + - heading "Selected element" [level=1] [ref=e337]: + - generic [ref=e339]: Selected element + - button "Home" [ref=e342] [cursor=pointer]: + - button "Home" [ref=e345]: + - generic [ref=e346]: + - img + - text: Home + - img + - tablist [ref=e347]: + - list [ref=e348]: + - tab "General" [selected] [ref=e349] [cursor=pointer]: + - img [ref=e350] + - tab "Site configuration" [ref=e352] [cursor=pointer]: + - img [ref=e353] + - tab "Metadata" [ref=e355] [cursor=pointer]: + - img [ref=e356] + - tab "SEO" [ref=e358] [cursor=pointer]: + - img [ref=e359] + - tabpanel [ref=e362]: + - generic [ref=e363]: + - generic [ref=e364]: + - button "Document" [expanded] [ref=e365]: + - heading "Document" [level=2] [ref=e366] [cursor=pointer]: + - img [ref=e368] + - text: Document + - button [ref=e370] [cursor=pointer]: + - img [ref=e371] + - generic [ref=e375]: + - generic [ref=e376]: + - generic [ref=e379] [cursor=pointer]: Title + - textbox "Title" [ref=e381]: Home + - generic [ref=e382]: + - generic [ref=e385] [cursor=pointer]: URL path segment + - generic [ref=e386]: + - textbox "URL path segment" [ref=e389]: home + - button "Syncronize with the title property" [ref=e391] [cursor=pointer]: + - img [ref=e392] + - generic [ref=e394]: + - generic [ref=e397] [cursor=pointer]: Title Override + - textbox "Title Override" [ref=e398]: + - /placeholder: Used in tag, max. 60 chars + - generic [ref=e399]: + - button "Teaser image" [expanded] [ref=e400]: + - heading "Teaser image" [level=2] [ref=e401] [cursor=pointer]: + - img [ref=e403] + - text: Teaser image + - button [ref=e405] [cursor=pointer]: + - img [ref=e406] + - generic [ref=e411]: + - generic [ref=e414] [cursor=pointer]: Image + - generic [ref=e415]: + - button "image" [ref=e418] [cursor=pointer]: + - img "image" [ref=e420] + - generic [ref=e421]: + - generic [ref=e422]: + - button "Media" [ref=e423] [cursor=pointer]: + - img [ref=e424] + - button "Choose file" [ref=e426] [cursor=pointer]: + - img [ref=e427] + - button "Remove" [ref=e429] [cursor=pointer]: + - img [ref=e430] + - button "Crop" [ref=e432] [cursor=pointer]: + - img [ref=e433] + - generic [ref=e435]: + - button "Discard" [disabled] [ref=e436] + - button "Apply" [disabled] [ref=e437] +``` \ No newline at end of file diff --git a/Tests/E2E/test-results/enforce-for-all-editor-is-redirected-to-2FA-setup-chromium/error-context.md b/Tests/E2E/test-results/enforce-for-all-editor-is-redirected-to-2FA-setup-chromium/error-context.md new file mode 100644 index 0000000..ab3dd36 --- /dev/null +++ b/Tests/E2E/test-results/enforce-for-all-editor-is-redirected-to-2FA-setup-chromium/error-context.md @@ -0,0 +1,327 @@ +# Page snapshot + +```yaml +- generic [ref=e4]: + - generic [ref=e5]: + - generic [ref=e6]: + - button "Menu" [ref=e7] [cursor=pointer] + - img [ref=e11] + - generic [ref=e16]: + - button "In-Place" [ref=e19] [cursor=pointer]: + - img + - generic [ref=e20]: In-Place + - img + - button "English (US)" [ref=e23] [cursor=pointer]: + - button "English (US)" [ref=e26]: + - generic [ref=e27]: + - img + - link "English (US)" [ref=e28]: + - /url: http://localhost:8081/neos/content?node=%2Fsites%2Fneosdemo%3Blanguage%3Den_US + - img + - button "E2E Editor" [ref=e31] [cursor=pointer]: + - img + - text: E2E Editor + - img + - generic [ref=e32]: + - button "Published - live" [disabled] [ref=e33] + - button [ref=e34] [cursor=pointer]: + - img + - generic [ref=e36]: + - button "Display Keyboard Shortcuts" [ref=e37] [cursor=pointer]: + - img [ref=e38] + - link "Show preview" [ref=e40] [cursor=pointer]: + - /url: http://localhost:8081/neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eeditor%3Blanguage%3Den_US + - img [ref=e41] + - button "Activate Fullscreen edit mode" [ref=e43] [cursor=pointer]: + - img [ref=e44] + - iframe [ref=e48]: + - generic [active] [ref=f1e1]: + - banner [ref=f1e2]: + - generic [ref=f1e3]: + - link [ref=f1e4] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eeditor%3Blanguage%3Den_US + - img [ref=f1e5] + - navigation + - button "Open menu" [ref=f1e11] [cursor=pointer]: + - generic [ref=f1e12]: Open menu + - img [ref=f1e13] + - navigation [ref=f1e15]: + - button "English (US)" [ref=f1e16] [cursor=pointer]: + - text: English (US) + - img [ref=f1e17] + - heading "Welcome to the Neos CMS demo" [level=1] [ref=f1e22]: + - textbox "Rich Text Editor, main" [ref=f1e23] + - main [ref=f1e24]: + - generic [ref=f1e25]: + - generic [ref=f1e26]: + - generic [ref=f1e27]: + - heading "Imagine this..." [level=2] [ref=f1e28]: + - textbox "Rich Text Editor, main" [ref=f1e29] + - textbox "Rich Text Editor, main" [ref=f1e31]: + - paragraph [ref=f1e32]: In only a few years from now, the world will have entirely new cities. Cities where development has not even started. Cities that can draw from all we know about liveability and functionality. + - paragraph [ref=f1e33]: "The same goes for websites. Major websites will be built with the tools we’re constructing today. But we don't know how they will be like. Just think of how different sites were 3 years ago. For both cities and websites, one basic rule counts: Things are constantly changing and can never be fully anticipated." + - paragraph [ref=f1e34]: The inspiration from urban planning permeates our UX master plan, giving us crucial guidelines for structuring everything we know - and making room for all the future change we don’t know. + - generic [ref=f1e35]: + - heading "Power when you need it" [level=2] [ref=f1e36]: + - textbox "Rich Text Editor, main" [ref=f1e37] + - textbox "Rich Text Editor, main" [ref=f1e39]: + - paragraph [ref=f1e40]: The distribution of power in a city makes sure that everyone gets what they need. You don’t power a shed in the way you power a nuclear plant. The ultra-simple editing makes editors center their attention on content. If you need to add SEO data next to the content, Neos has an inspector that opens up for that kind of functionality. Just a bit more power, one step at a time... + - paragraph [ref=f1e41]: If you need more, the inspector will be expandable to the full backend screen providing the user with all the options. That’s not enough? Create custom backend modules. And if even that's not enough, underlying Neos, we have Flow - a first-class web framework that enables you to build web applications that integrate with your other systems. + - generic [ref=f1e42]: + - generic [ref=f1e43]: + - heading "Create an account and access the backend" [level=2] [ref=f1e44]: + - textbox "Rich Text Editor, main" [ref=f1e45]: + - generic [ref=f1e46]: Create an account and access the backend + - textbox "Rich Text Editor, main" [ref=f1e48]: + - paragraph [ref=f1e49]: Try out our latest development of the Neos user interface. Get a first impression of CKEditor, the content editor, and try out the simple publishing mechanism based on workspaces. + - paragraph [ref=f1e50]: The registration form is also an example of a "Plugin"-element. + - generic [ref=f1e53]: + - group [ref=f1e54]: + - generic [ref=f1e55]: + - generic [ref=f1e56]: Username + - textbox "Username" [ref=f1e57] + - generic [ref=f1e58]: (lowercase letters and numbers only) + - generic [ref=f1e59]: + - generic [ref=f1e60]: Password + - textbox "Password" [ref=f1e61] + - button "Submit" [ref=f1e62] [cursor=pointer] + - contentinfo [ref=f1e64]: + - navigation [ref=f1e65]: + - link "Contribute" [ref=f1e66] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fcontribute%40user-e2eeditor%3Blanguage%3Den_US + - link "Documentation & Support" [ref=f1e67] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdocumentation%40user-e2eeditor%3Blanguage%3Den_US + - link "Download" [ref=f1e68] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdownload%40user-e2eeditor%3Blanguage%3Den_US + - textbox "Rich Text Editor, main" [ref=f1e72]: + - paragraph [ref=f1e73]: Powered by Neos & Flow + - generic [ref=e53]: "UI version: 8.4.3" + - button "Document Tree" [ref=e54] [cursor=pointer]: + - button "Navigate" [ref=e55]: + - img [ref=e56] + - text: Document Tree + - generic [ref=e58]: + - generic [ref=e59]: + - generic [ref=e62]: + - button "Create new" [ref=e63] [cursor=pointer]: + - img [ref=e64] + - button "Hide / Unhide" [disabled] [ref=e66]: + - img [ref=e67] + - button "Copy" [ref=e69] [cursor=pointer]: + - img [ref=e70] + - button "Cut" [disabled] [ref=e72]: + - img [ref=e73] + - button "Paste" [disabled] [ref=e75]: + - img [ref=e76] + - button "Delete" [disabled] [ref=e78]: + - img [ref=e79] + - button "Refresh" [ref=e81] [cursor=pointer]: + - img [ref=e82] + - button [ref=e84] [cursor=pointer]: + - img [ref=e85] + - tree [ref=e87]: + - treeitem "Home" [ref=e88]: + - button "Collapse All" [ref=e89] [cursor=pointer]: + - img [ref=e90] + - treeitem "Home" [expanded] [ref=e92]: + - generic [ref=e94]: + - button "Home" [ref=e96] [cursor=pointer]: + - generic [ref=e97]: + - img [ref=e99] + - treeitem "Home" [ref=e101] + - button [ref=e102] [cursor=pointer]: + - img [ref=e103] + - generic [ref=e105]: + - treeitem "Home" [expanded] [ref=e106]: + - button "Home" [ref=e110] [cursor=pointer]: + - generic [ref=e111]: + - img [ref=e113] + - treeitem "Home" [ref=e115] + - treeitem "Features" [expanded] [ref=e116]: + - generic [ref=e118]: + - button "Features" [ref=e120] [cursor=pointer]: + - generic [ref=e121]: + - img [ref=e123] + - treeitem "Features" [ref=e125] + - button [ref=e126] [cursor=pointer]: + - img [ref=e127] + - generic [ref=e129]: + - treeitem "Multiple columns" [ref=e130]: + - button "Multiple columns" [ref=e134] [cursor=pointer]: + - generic [ref=e135]: + - img [ref=e137] + - treeitem "Multiple columns" [ref=e139] + - treeitem "Text & images" [ref=e140]: + - button "Text & images" [ref=e144] [cursor=pointer]: + - generic [ref=e145]: + - img [ref=e147] + - treeitem "Text & images" [ref=e149] + - treeitem "Navigation elements" [ref=e150]: + - generic [ref=e152]: + - button "Navigation elements" [ref=e154] [cursor=pointer]: + - generic [ref=e155]: + - img [ref=e157] + - treeitem "Navigation elements" [ref=e159] + - button [ref=e160] [cursor=pointer]: + - img [ref=e161] + - treeitem "Custom elements" [ref=e163]: + - button "Custom elements" [ref=e167] [cursor=pointer]: + - generic [ref=e168]: + - img [ref=e170] + - treeitem "Custom elements" [ref=e172] + - treeitem "Other elements" [ref=e173]: + - button "Other elements" [ref=e177] [cursor=pointer]: + - generic [ref=e178]: + - img [ref=e180] + - treeitem "Other elements" [ref=e182] + - treeitem "Forms" [ref=e183]: + - button "Forms" [ref=e187] [cursor=pointer]: + - generic [ref=e188]: + - img [ref=e190] + - treeitem "Forms" [ref=e192] + - treeitem "Blog" [expanded] [ref=e194]: + - generic [ref=e196]: + - button "Blog" [ref=e198] [cursor=pointer]: + - generic [ref=e199]: + - img [ref=e201] + - treeitem "Blog" [ref=e203] + - button [ref=e204] [cursor=pointer]: + - img [ref=e205] + - generic [ref=e207]: + - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e208]: + - button "Neos 8.0 \"Mad Hatter\" released" [ref=e212] [cursor=pointer]: + - generic [ref=e213]: + - img [ref=e215] + - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e217] + - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e218]: + - button "Neos 8.1 \"Frog–Footman\" released" [ref=e222] [cursor=pointer]: + - generic [ref=e223]: + - img [ref=e225] + - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e227] + - treeitem "Neos 8.2 released" [ref=e228]: + - button "Neos 8.2 released" [ref=e232] [cursor=pointer]: + - generic [ref=e233]: + - img [ref=e235] + - treeitem "Neos 8.2 released" [ref=e237] + - treeitem "Neos 8.3 released" [ref=e238]: + - button "Neos 8.3 released" [ref=e242] [cursor=pointer]: + - generic [ref=e243]: + - img [ref=e245] + - treeitem "Neos 8.3 released" [ref=e247] + - treeitem "Neos 8.4 released" [ref=e248]: + - button "Neos 8.4 released" [ref=e252] [cursor=pointer]: + - generic [ref=e253]: + - img [ref=e255] + - treeitem "Neos 8.4 released" [ref=e257] + - treeitem "Download" [expanded] [ref=e259]: + - button "Download" [ref=e263] [cursor=pointer]: + - generic [ref=e264]: + - img [ref=e266] + - treeitem "Download" [ref=e268] + - treeitem "Contribute" [expanded] [ref=e269]: + - button "Contribute" [ref=e273] [cursor=pointer]: + - generic [ref=e274]: + - img [ref=e276] + - treeitem "Contribute" [ref=e278] + - treeitem "Documentation & Support" [expanded] [ref=e279]: + - button "Documentation & Support" [ref=e283] [cursor=pointer]: + - generic [ref=e284]: + - img [ref=e286] + - treeitem "Documentation & Support" [ref=e288] + - treeitem "Not Found" [expanded] [ref=e289]: + - button "Not Found" [ref=e293] [cursor=pointer]: + - generic [ref=e294]: + - img [ref=e296] + - treeitem "Not Found" [ref=e298] + - generic [ref=e301]: + - button "Toggle content tree Content Tree" [ref=e303] [cursor=pointer]: + - button "Toggle content tree" [ref=e304]: + - img [ref=e305] + - text: Content Tree + - generic [ref=e308]: + - button "Create new" [ref=e309] [cursor=pointer]: + - img [ref=e310] + - button "Hide / Unhide" [ref=e312] [cursor=pointer]: + - img [ref=e313] + - button "Copy" [ref=e315] [cursor=pointer]: + - img [ref=e316] + - button "Cut" [ref=e318] [cursor=pointer]: + - img [ref=e319] + - button "Paste" [disabled] [ref=e321]: + - img [ref=e322] + - button "Delete" [ref=e324] [cursor=pointer]: + - img [ref=e325] + - button "Refresh" [ref=e327] [cursor=pointer]: + - img [ref=e328] + - form [ref=e330]: + - button "Toggle inspector" [ref=e331] [cursor=pointer]: + - img [ref=e332] + - generic [ref=e335]: + - generic [ref=e336]: + - heading "Selected element" [level=1] [ref=e337]: + - generic [ref=e339]: Selected element + - button "Home" [ref=e342] [cursor=pointer]: + - button "Home" [ref=e345]: + - generic [ref=e346]: + - img + - text: Home + - img + - tablist [ref=e347]: + - list [ref=e348]: + - tab "General" [selected] [ref=e349] [cursor=pointer]: + - img [ref=e350] + - tab "Site configuration" [ref=e352] [cursor=pointer]: + - img [ref=e353] + - tab "Metadata" [ref=e355] [cursor=pointer]: + - img [ref=e356] + - tab "SEO" [ref=e358] [cursor=pointer]: + - img [ref=e359] + - tabpanel [ref=e362]: + - generic [ref=e363]: + - generic [ref=e364]: + - button "Document" [expanded] [ref=e365]: + - heading "Document" [level=2] [ref=e366] [cursor=pointer]: + - img [ref=e368] + - text: Document + - button [ref=e370] [cursor=pointer]: + - img [ref=e371] + - generic [ref=e375]: + - generic [ref=e376]: + - generic [ref=e379] [cursor=pointer]: Title + - textbox "Title" [ref=e381]: Home + - generic [ref=e382]: + - generic [ref=e385] [cursor=pointer]: URL path segment + - generic [ref=e386]: + - textbox "URL path segment" [ref=e389]: home + - button "Syncronize with the title property" [ref=e391] [cursor=pointer]: + - img [ref=e392] + - generic [ref=e394]: + - generic [ref=e397] [cursor=pointer]: Title Override + - textbox "Title Override" [ref=e398]: + - /placeholder: Used in <title> tag, max. 60 chars + - generic [ref=e399]: + - button "Teaser image" [expanded] [ref=e400]: + - heading "Teaser image" [level=2] [ref=e401] [cursor=pointer]: + - img [ref=e403] + - text: Teaser image + - button [ref=e405] [cursor=pointer]: + - img [ref=e406] + - generic [ref=e411]: + - generic [ref=e414] [cursor=pointer]: Image + - generic [ref=e415]: + - button "image" [ref=e418] [cursor=pointer]: + - img "image" [ref=e420] + - generic [ref=e421]: + - generic [ref=e422]: + - button "Media" [ref=e423] [cursor=pointer]: + - img [ref=e424] + - button "Choose file" [ref=e426] [cursor=pointer]: + - img [ref=e427] + - button "Remove" [ref=e429] [cursor=pointer]: + - img [ref=e430] + - button "Crop" [ref=e432] [cursor=pointer]: + - img [ref=e433] + - generic [ref=e435]: + - button "Discard" [disabled] [ref=e436] + - button "Apply" [disabled] [ref=e437] +``` \ No newline at end of file diff --git a/Tests/E2E/test-results/enforce-for-provider-admin-97496--is-redirected-to-2FA-setup-chromium/error-context.md b/Tests/E2E/test-results/enforce-for-provider-admin-97496--is-redirected-to-2FA-setup-chromium/error-context.md new file mode 100644 index 0000000..018279c --- /dev/null +++ b/Tests/E2E/test-results/enforce-for-provider-admin-97496--is-redirected-to-2FA-setup-chromium/error-context.md @@ -0,0 +1,327 @@ +# Page snapshot + +```yaml +- generic [ref=e4]: + - generic [ref=e5]: + - generic [ref=e6]: + - button "Menu" [ref=e7] [cursor=pointer] + - img [ref=e11] + - generic [ref=e16]: + - button "In-Place" [ref=e19] [cursor=pointer]: + - img + - generic [ref=e20]: In-Place + - img + - button "English (US)" [ref=e23] [cursor=pointer]: + - button "English (US)" [ref=e26]: + - generic [ref=e27]: + - img + - link "English (US)" [ref=e28]: + - /url: http://localhost:8081/neos/content?node=%2Fsites%2Fneosdemo%3Blanguage%3Den_US + - img + - button "E2E Admin" [ref=e31] [cursor=pointer]: + - img + - text: E2E Admin + - img + - generic [ref=e32]: + - button "Published - live" [disabled] [ref=e33] + - button [ref=e34] [cursor=pointer]: + - img + - generic [ref=e36]: + - button "Display Keyboard Shortcuts" [ref=e37] [cursor=pointer]: + - img [ref=e38] + - link "Show preview" [ref=e40] [cursor=pointer]: + - /url: http://localhost:8081/neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eadmin%3Blanguage%3Den_US + - img [ref=e41] + - button "Activate Fullscreen edit mode" [ref=e43] [cursor=pointer]: + - img [ref=e44] + - iframe [ref=e48]: + - generic [active] [ref=f1e1]: + - banner [ref=f1e2]: + - generic [ref=f1e3]: + - link [ref=f1e4] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eadmin%3Blanguage%3Den_US + - img [ref=f1e5] + - navigation + - button "Open menu" [ref=f1e11] [cursor=pointer]: + - generic [ref=f1e12]: Open menu + - img [ref=f1e13] + - navigation [ref=f1e15]: + - button "English (US)" [ref=f1e16] [cursor=pointer]: + - text: English (US) + - img [ref=f1e17] + - heading "Welcome to the Neos CMS demo" [level=1] [ref=f1e22]: + - textbox "Rich Text Editor, main" [ref=f1e23] + - main [ref=f1e24]: + - generic [ref=f1e25]: + - generic [ref=f1e26]: + - generic [ref=f1e27]: + - heading "Imagine this..." [level=2] [ref=f1e28]: + - textbox "Rich Text Editor, main" [ref=f1e29] + - textbox "Rich Text Editor, main" [ref=f1e31]: + - paragraph [ref=f1e32]: In only a few years from now, the world will have entirely new cities. Cities where development has not even started. Cities that can draw from all we know about liveability and functionality. + - paragraph [ref=f1e33]: "The same goes for websites. Major websites will be built with the tools we’re constructing today. But we don't know how they will be like. Just think of how different sites were 3 years ago. For both cities and websites, one basic rule counts: Things are constantly changing and can never be fully anticipated." + - paragraph [ref=f1e34]: The inspiration from urban planning permeates our UX master plan, giving us crucial guidelines for structuring everything we know - and making room for all the future change we don’t know. + - generic [ref=f1e35]: + - heading "Power when you need it" [level=2] [ref=f1e36]: + - textbox "Rich Text Editor, main" [ref=f1e37] + - textbox "Rich Text Editor, main" [ref=f1e39]: + - paragraph [ref=f1e40]: The distribution of power in a city makes sure that everyone gets what they need. You don’t power a shed in the way you power a nuclear plant. The ultra-simple editing makes editors center their attention on content. If you need to add SEO data next to the content, Neos has an inspector that opens up for that kind of functionality. Just a bit more power, one step at a time... + - paragraph [ref=f1e41]: If you need more, the inspector will be expandable to the full backend screen providing the user with all the options. That’s not enough? Create custom backend modules. And if even that's not enough, underlying Neos, we have Flow - a first-class web framework that enables you to build web applications that integrate with your other systems. + - generic [ref=f1e42]: + - generic [ref=f1e43]: + - heading "Create an account and access the backend" [level=2] [ref=f1e44]: + - textbox "Rich Text Editor, main" [ref=f1e45]: + - generic [ref=f1e46]: Create an account and access the backend + - textbox "Rich Text Editor, main" [ref=f1e48]: + - paragraph [ref=f1e49]: Try out our latest development of the Neos user interface. Get a first impression of CKEditor, the content editor, and try out the simple publishing mechanism based on workspaces. + - paragraph [ref=f1e50]: The registration form is also an example of a "Plugin"-element. + - generic [ref=f1e53]: + - group [ref=f1e54]: + - generic [ref=f1e55]: + - generic [ref=f1e56]: Username + - textbox "Username" [ref=f1e57] + - generic [ref=f1e58]: (lowercase letters and numbers only) + - generic [ref=f1e59]: + - generic [ref=f1e60]: Password + - textbox "Password" [ref=f1e61] + - button "Submit" [ref=f1e62] [cursor=pointer] + - contentinfo [ref=f1e64]: + - navigation [ref=f1e65]: + - link "Contribute" [ref=f1e66] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fcontribute%40user-e2eadmin%3Blanguage%3Den_US + - link "Documentation & Support" [ref=f1e67] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdocumentation%40user-e2eadmin%3Blanguage%3Den_US + - link "Download" [ref=f1e68] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdownload%40user-e2eadmin%3Blanguage%3Den_US + - textbox "Rich Text Editor, main" [ref=f1e72]: + - paragraph [ref=f1e73]: Powered by Neos & Flow + - generic [ref=e53]: "UI version: 8.4.3" + - button "Document Tree" [ref=e54] [cursor=pointer]: + - button "Navigate" [ref=e55]: + - img [ref=e56] + - text: Document Tree + - generic [ref=e58]: + - generic [ref=e59]: + - generic [ref=e62]: + - button "Create new" [ref=e63] [cursor=pointer]: + - img [ref=e64] + - button "Hide / Unhide" [disabled] [ref=e66]: + - img [ref=e67] + - button "Copy" [ref=e69] [cursor=pointer]: + - img [ref=e70] + - button "Cut" [disabled] [ref=e72]: + - img [ref=e73] + - button "Paste" [disabled] [ref=e75]: + - img [ref=e76] + - button "Delete" [disabled] [ref=e78]: + - img [ref=e79] + - button "Refresh" [ref=e81] [cursor=pointer]: + - img [ref=e82] + - button [ref=e84] [cursor=pointer]: + - img [ref=e85] + - tree [ref=e87]: + - treeitem "Home" [ref=e88]: + - button "Collapse All" [ref=e89] [cursor=pointer]: + - img [ref=e90] + - treeitem "Home" [expanded] [ref=e92]: + - generic [ref=e94]: + - button "Home" [ref=e96] [cursor=pointer]: + - generic [ref=e97]: + - img [ref=e99] + - treeitem "Home" [ref=e101] + - button [ref=e102] [cursor=pointer]: + - img [ref=e103] + - generic [ref=e105]: + - treeitem "Home" [expanded] [ref=e106]: + - button "Home" [ref=e110] [cursor=pointer]: + - generic [ref=e111]: + - img [ref=e113] + - treeitem "Home" [ref=e115] + - treeitem "Features" [expanded] [ref=e116]: + - generic [ref=e118]: + - button "Features" [ref=e120] [cursor=pointer]: + - generic [ref=e121]: + - img [ref=e123] + - treeitem "Features" [ref=e125] + - button [ref=e126] [cursor=pointer]: + - img [ref=e127] + - generic [ref=e129]: + - treeitem "Multiple columns" [ref=e130]: + - button "Multiple columns" [ref=e134] [cursor=pointer]: + - generic [ref=e135]: + - img [ref=e137] + - treeitem "Multiple columns" [ref=e139] + - treeitem "Text & images" [ref=e140]: + - button "Text & images" [ref=e144] [cursor=pointer]: + - generic [ref=e145]: + - img [ref=e147] + - treeitem "Text & images" [ref=e149] + - treeitem "Navigation elements" [ref=e150]: + - generic [ref=e152]: + - button "Navigation elements" [ref=e154] [cursor=pointer]: + - generic [ref=e155]: + - img [ref=e157] + - treeitem "Navigation elements" [ref=e159] + - button [ref=e160] [cursor=pointer]: + - img [ref=e161] + - treeitem "Custom elements" [ref=e163]: + - button "Custom elements" [ref=e167] [cursor=pointer]: + - generic [ref=e168]: + - img [ref=e170] + - treeitem "Custom elements" [ref=e172] + - treeitem "Other elements" [ref=e173]: + - button "Other elements" [ref=e177] [cursor=pointer]: + - generic [ref=e178]: + - img [ref=e180] + - treeitem "Other elements" [ref=e182] + - treeitem "Forms" [ref=e183]: + - button "Forms" [ref=e187] [cursor=pointer]: + - generic [ref=e188]: + - img [ref=e190] + - treeitem "Forms" [ref=e192] + - treeitem "Blog" [expanded] [ref=e194]: + - generic [ref=e196]: + - button "Blog" [ref=e198] [cursor=pointer]: + - generic [ref=e199]: + - img [ref=e201] + - treeitem "Blog" [ref=e203] + - button [ref=e204] [cursor=pointer]: + - img [ref=e205] + - generic [ref=e207]: + - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e208]: + - button "Neos 8.0 \"Mad Hatter\" released" [ref=e212] [cursor=pointer]: + - generic [ref=e213]: + - img [ref=e215] + - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e217] + - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e218]: + - button "Neos 8.1 \"Frog–Footman\" released" [ref=e222] [cursor=pointer]: + - generic [ref=e223]: + - img [ref=e225] + - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e227] + - treeitem "Neos 8.2 released" [ref=e228]: + - button "Neos 8.2 released" [ref=e232] [cursor=pointer]: + - generic [ref=e233]: + - img [ref=e235] + - treeitem "Neos 8.2 released" [ref=e237] + - treeitem "Neos 8.3 released" [ref=e238]: + - button "Neos 8.3 released" [ref=e242] [cursor=pointer]: + - generic [ref=e243]: + - img [ref=e245] + - treeitem "Neos 8.3 released" [ref=e247] + - treeitem "Neos 8.4 released" [ref=e248]: + - button "Neos 8.4 released" [ref=e252] [cursor=pointer]: + - generic [ref=e253]: + - img [ref=e255] + - treeitem "Neos 8.4 released" [ref=e257] + - treeitem "Download" [expanded] [ref=e259]: + - button "Download" [ref=e263] [cursor=pointer]: + - generic [ref=e264]: + - img [ref=e266] + - treeitem "Download" [ref=e268] + - treeitem "Contribute" [expanded] [ref=e269]: + - button "Contribute" [ref=e273] [cursor=pointer]: + - generic [ref=e274]: + - img [ref=e276] + - treeitem "Contribute" [ref=e278] + - treeitem "Documentation & Support" [expanded] [ref=e279]: + - button "Documentation & Support" [ref=e283] [cursor=pointer]: + - generic [ref=e284]: + - img [ref=e286] + - treeitem "Documentation & Support" [ref=e288] + - treeitem "Not Found" [expanded] [ref=e289]: + - button "Not Found" [ref=e293] [cursor=pointer]: + - generic [ref=e294]: + - img [ref=e296] + - treeitem "Not Found" [ref=e298] + - generic [ref=e301]: + - button "Toggle content tree Content Tree" [ref=e303] [cursor=pointer]: + - button "Toggle content tree" [ref=e304]: + - img [ref=e305] + - text: Content Tree + - generic [ref=e308]: + - button "Create new" [ref=e309] [cursor=pointer]: + - img [ref=e310] + - button "Hide / Unhide" [ref=e312] [cursor=pointer]: + - img [ref=e313] + - button "Copy" [ref=e315] [cursor=pointer]: + - img [ref=e316] + - button "Cut" [ref=e318] [cursor=pointer]: + - img [ref=e319] + - button "Paste" [disabled] [ref=e321]: + - img [ref=e322] + - button "Delete" [ref=e324] [cursor=pointer]: + - img [ref=e325] + - button "Refresh" [ref=e327] [cursor=pointer]: + - img [ref=e328] + - form [ref=e330]: + - button "Toggle inspector" [ref=e331] [cursor=pointer]: + - img [ref=e332] + - generic [ref=e335]: + - generic [ref=e336]: + - heading "Selected element" [level=1] [ref=e337]: + - generic [ref=e339]: Selected element + - button "Home" [ref=e342] [cursor=pointer]: + - button "Home" [ref=e345]: + - generic [ref=e346]: + - img + - text: Home + - img + - tablist [ref=e347]: + - list [ref=e348]: + - tab "General" [selected] [ref=e349] [cursor=pointer]: + - img [ref=e350] + - tab "Site configuration" [ref=e352] [cursor=pointer]: + - img [ref=e353] + - tab "Metadata" [ref=e355] [cursor=pointer]: + - img [ref=e356] + - tab "SEO" [ref=e358] [cursor=pointer]: + - img [ref=e359] + - tabpanel [ref=e362]: + - generic [ref=e363]: + - generic [ref=e364]: + - button "Document" [expanded] [ref=e365]: + - heading "Document" [level=2] [ref=e366] [cursor=pointer]: + - img [ref=e368] + - text: Document + - button [ref=e370] [cursor=pointer]: + - img [ref=e371] + - generic [ref=e375]: + - generic [ref=e376]: + - generic [ref=e379] [cursor=pointer]: Title + - textbox "Title" [ref=e381]: Home + - generic [ref=e382]: + - generic [ref=e385] [cursor=pointer]: URL path segment + - generic [ref=e386]: + - textbox "URL path segment" [ref=e389]: home + - button "Syncronize with the title property" [ref=e391] [cursor=pointer]: + - img [ref=e392] + - generic [ref=e394]: + - generic [ref=e397] [cursor=pointer]: Title Override + - textbox "Title Override" [ref=e398]: + - /placeholder: Used in <title> tag, max. 60 chars + - generic [ref=e399]: + - button "Teaser image" [expanded] [ref=e400]: + - heading "Teaser image" [level=2] [ref=e401] [cursor=pointer]: + - img [ref=e403] + - text: Teaser image + - button [ref=e405] [cursor=pointer]: + - img [ref=e406] + - generic [ref=e411]: + - generic [ref=e414] [cursor=pointer]: Image + - generic [ref=e415]: + - button "image" [ref=e418] [cursor=pointer]: + - img "image" [ref=e420] + - generic [ref=e421]: + - generic [ref=e422]: + - button "Media" [ref=e423] [cursor=pointer]: + - img [ref=e424] + - button "Choose file" [ref=e426] [cursor=pointer]: + - img [ref=e427] + - button "Remove" [ref=e429] [cursor=pointer]: + - img [ref=e430] + - button "Crop" [ref=e432] [cursor=pointer]: + - img [ref=e433] + - generic [ref=e435]: + - button "Discard" [disabled] [ref=e436] + - button "Apply" [disabled] [ref=e437] +``` \ No newline at end of file diff --git a/Tests/E2E/test-results/enforce-for-provider-editor-is-redirected-to-2FA-setup-chromium/error-context.md b/Tests/E2E/test-results/enforce-for-provider-editor-is-redirected-to-2FA-setup-chromium/error-context.md new file mode 100644 index 0000000..ab3dd36 --- /dev/null +++ b/Tests/E2E/test-results/enforce-for-provider-editor-is-redirected-to-2FA-setup-chromium/error-context.md @@ -0,0 +1,327 @@ +# Page snapshot + +```yaml +- generic [ref=e4]: + - generic [ref=e5]: + - generic [ref=e6]: + - button "Menu" [ref=e7] [cursor=pointer] + - img [ref=e11] + - generic [ref=e16]: + - button "In-Place" [ref=e19] [cursor=pointer]: + - img + - generic [ref=e20]: In-Place + - img + - button "English (US)" [ref=e23] [cursor=pointer]: + - button "English (US)" [ref=e26]: + - generic [ref=e27]: + - img + - link "English (US)" [ref=e28]: + - /url: http://localhost:8081/neos/content?node=%2Fsites%2Fneosdemo%3Blanguage%3Den_US + - img + - button "E2E Editor" [ref=e31] [cursor=pointer]: + - img + - text: E2E Editor + - img + - generic [ref=e32]: + - button "Published - live" [disabled] [ref=e33] + - button [ref=e34] [cursor=pointer]: + - img + - generic [ref=e36]: + - button "Display Keyboard Shortcuts" [ref=e37] [cursor=pointer]: + - img [ref=e38] + - link "Show preview" [ref=e40] [cursor=pointer]: + - /url: http://localhost:8081/neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eeditor%3Blanguage%3Den_US + - img [ref=e41] + - button "Activate Fullscreen edit mode" [ref=e43] [cursor=pointer]: + - img [ref=e44] + - iframe [ref=e48]: + - generic [active] [ref=f1e1]: + - banner [ref=f1e2]: + - generic [ref=f1e3]: + - link [ref=f1e4] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eeditor%3Blanguage%3Den_US + - img [ref=f1e5] + - navigation + - button "Open menu" [ref=f1e11] [cursor=pointer]: + - generic [ref=f1e12]: Open menu + - img [ref=f1e13] + - navigation [ref=f1e15]: + - button "English (US)" [ref=f1e16] [cursor=pointer]: + - text: English (US) + - img [ref=f1e17] + - heading "Welcome to the Neos CMS demo" [level=1] [ref=f1e22]: + - textbox "Rich Text Editor, main" [ref=f1e23] + - main [ref=f1e24]: + - generic [ref=f1e25]: + - generic [ref=f1e26]: + - generic [ref=f1e27]: + - heading "Imagine this..." [level=2] [ref=f1e28]: + - textbox "Rich Text Editor, main" [ref=f1e29] + - textbox "Rich Text Editor, main" [ref=f1e31]: + - paragraph [ref=f1e32]: In only a few years from now, the world will have entirely new cities. Cities where development has not even started. Cities that can draw from all we know about liveability and functionality. + - paragraph [ref=f1e33]: "The same goes for websites. Major websites will be built with the tools we’re constructing today. But we don't know how they will be like. Just think of how different sites were 3 years ago. For both cities and websites, one basic rule counts: Things are constantly changing and can never be fully anticipated." + - paragraph [ref=f1e34]: The inspiration from urban planning permeates our UX master plan, giving us crucial guidelines for structuring everything we know - and making room for all the future change we don’t know. + - generic [ref=f1e35]: + - heading "Power when you need it" [level=2] [ref=f1e36]: + - textbox "Rich Text Editor, main" [ref=f1e37] + - textbox "Rich Text Editor, main" [ref=f1e39]: + - paragraph [ref=f1e40]: The distribution of power in a city makes sure that everyone gets what they need. You don’t power a shed in the way you power a nuclear plant. The ultra-simple editing makes editors center their attention on content. If you need to add SEO data next to the content, Neos has an inspector that opens up for that kind of functionality. Just a bit more power, one step at a time... + - paragraph [ref=f1e41]: If you need more, the inspector will be expandable to the full backend screen providing the user with all the options. That’s not enough? Create custom backend modules. And if even that's not enough, underlying Neos, we have Flow - a first-class web framework that enables you to build web applications that integrate with your other systems. + - generic [ref=f1e42]: + - generic [ref=f1e43]: + - heading "Create an account and access the backend" [level=2] [ref=f1e44]: + - textbox "Rich Text Editor, main" [ref=f1e45]: + - generic [ref=f1e46]: Create an account and access the backend + - textbox "Rich Text Editor, main" [ref=f1e48]: + - paragraph [ref=f1e49]: Try out our latest development of the Neos user interface. Get a first impression of CKEditor, the content editor, and try out the simple publishing mechanism based on workspaces. + - paragraph [ref=f1e50]: The registration form is also an example of a "Plugin"-element. + - generic [ref=f1e53]: + - group [ref=f1e54]: + - generic [ref=f1e55]: + - generic [ref=f1e56]: Username + - textbox "Username" [ref=f1e57] + - generic [ref=f1e58]: (lowercase letters and numbers only) + - generic [ref=f1e59]: + - generic [ref=f1e60]: Password + - textbox "Password" [ref=f1e61] + - button "Submit" [ref=f1e62] [cursor=pointer] + - contentinfo [ref=f1e64]: + - navigation [ref=f1e65]: + - link "Contribute" [ref=f1e66] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fcontribute%40user-e2eeditor%3Blanguage%3Den_US + - link "Documentation & Support" [ref=f1e67] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdocumentation%40user-e2eeditor%3Blanguage%3Den_US + - link "Download" [ref=f1e68] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdownload%40user-e2eeditor%3Blanguage%3Den_US + - textbox "Rich Text Editor, main" [ref=f1e72]: + - paragraph [ref=f1e73]: Powered by Neos & Flow + - generic [ref=e53]: "UI version: 8.4.3" + - button "Document Tree" [ref=e54] [cursor=pointer]: + - button "Navigate" [ref=e55]: + - img [ref=e56] + - text: Document Tree + - generic [ref=e58]: + - generic [ref=e59]: + - generic [ref=e62]: + - button "Create new" [ref=e63] [cursor=pointer]: + - img [ref=e64] + - button "Hide / Unhide" [disabled] [ref=e66]: + - img [ref=e67] + - button "Copy" [ref=e69] [cursor=pointer]: + - img [ref=e70] + - button "Cut" [disabled] [ref=e72]: + - img [ref=e73] + - button "Paste" [disabled] [ref=e75]: + - img [ref=e76] + - button "Delete" [disabled] [ref=e78]: + - img [ref=e79] + - button "Refresh" [ref=e81] [cursor=pointer]: + - img [ref=e82] + - button [ref=e84] [cursor=pointer]: + - img [ref=e85] + - tree [ref=e87]: + - treeitem "Home" [ref=e88]: + - button "Collapse All" [ref=e89] [cursor=pointer]: + - img [ref=e90] + - treeitem "Home" [expanded] [ref=e92]: + - generic [ref=e94]: + - button "Home" [ref=e96] [cursor=pointer]: + - generic [ref=e97]: + - img [ref=e99] + - treeitem "Home" [ref=e101] + - button [ref=e102] [cursor=pointer]: + - img [ref=e103] + - generic [ref=e105]: + - treeitem "Home" [expanded] [ref=e106]: + - button "Home" [ref=e110] [cursor=pointer]: + - generic [ref=e111]: + - img [ref=e113] + - treeitem "Home" [ref=e115] + - treeitem "Features" [expanded] [ref=e116]: + - generic [ref=e118]: + - button "Features" [ref=e120] [cursor=pointer]: + - generic [ref=e121]: + - img [ref=e123] + - treeitem "Features" [ref=e125] + - button [ref=e126] [cursor=pointer]: + - img [ref=e127] + - generic [ref=e129]: + - treeitem "Multiple columns" [ref=e130]: + - button "Multiple columns" [ref=e134] [cursor=pointer]: + - generic [ref=e135]: + - img [ref=e137] + - treeitem "Multiple columns" [ref=e139] + - treeitem "Text & images" [ref=e140]: + - button "Text & images" [ref=e144] [cursor=pointer]: + - generic [ref=e145]: + - img [ref=e147] + - treeitem "Text & images" [ref=e149] + - treeitem "Navigation elements" [ref=e150]: + - generic [ref=e152]: + - button "Navigation elements" [ref=e154] [cursor=pointer]: + - generic [ref=e155]: + - img [ref=e157] + - treeitem "Navigation elements" [ref=e159] + - button [ref=e160] [cursor=pointer]: + - img [ref=e161] + - treeitem "Custom elements" [ref=e163]: + - button "Custom elements" [ref=e167] [cursor=pointer]: + - generic [ref=e168]: + - img [ref=e170] + - treeitem "Custom elements" [ref=e172] + - treeitem "Other elements" [ref=e173]: + - button "Other elements" [ref=e177] [cursor=pointer]: + - generic [ref=e178]: + - img [ref=e180] + - treeitem "Other elements" [ref=e182] + - treeitem "Forms" [ref=e183]: + - button "Forms" [ref=e187] [cursor=pointer]: + - generic [ref=e188]: + - img [ref=e190] + - treeitem "Forms" [ref=e192] + - treeitem "Blog" [expanded] [ref=e194]: + - generic [ref=e196]: + - button "Blog" [ref=e198] [cursor=pointer]: + - generic [ref=e199]: + - img [ref=e201] + - treeitem "Blog" [ref=e203] + - button [ref=e204] [cursor=pointer]: + - img [ref=e205] + - generic [ref=e207]: + - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e208]: + - button "Neos 8.0 \"Mad Hatter\" released" [ref=e212] [cursor=pointer]: + - generic [ref=e213]: + - img [ref=e215] + - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e217] + - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e218]: + - button "Neos 8.1 \"Frog–Footman\" released" [ref=e222] [cursor=pointer]: + - generic [ref=e223]: + - img [ref=e225] + - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e227] + - treeitem "Neos 8.2 released" [ref=e228]: + - button "Neos 8.2 released" [ref=e232] [cursor=pointer]: + - generic [ref=e233]: + - img [ref=e235] + - treeitem "Neos 8.2 released" [ref=e237] + - treeitem "Neos 8.3 released" [ref=e238]: + - button "Neos 8.3 released" [ref=e242] [cursor=pointer]: + - generic [ref=e243]: + - img [ref=e245] + - treeitem "Neos 8.3 released" [ref=e247] + - treeitem "Neos 8.4 released" [ref=e248]: + - button "Neos 8.4 released" [ref=e252] [cursor=pointer]: + - generic [ref=e253]: + - img [ref=e255] + - treeitem "Neos 8.4 released" [ref=e257] + - treeitem "Download" [expanded] [ref=e259]: + - button "Download" [ref=e263] [cursor=pointer]: + - generic [ref=e264]: + - img [ref=e266] + - treeitem "Download" [ref=e268] + - treeitem "Contribute" [expanded] [ref=e269]: + - button "Contribute" [ref=e273] [cursor=pointer]: + - generic [ref=e274]: + - img [ref=e276] + - treeitem "Contribute" [ref=e278] + - treeitem "Documentation & Support" [expanded] [ref=e279]: + - button "Documentation & Support" [ref=e283] [cursor=pointer]: + - generic [ref=e284]: + - img [ref=e286] + - treeitem "Documentation & Support" [ref=e288] + - treeitem "Not Found" [expanded] [ref=e289]: + - button "Not Found" [ref=e293] [cursor=pointer]: + - generic [ref=e294]: + - img [ref=e296] + - treeitem "Not Found" [ref=e298] + - generic [ref=e301]: + - button "Toggle content tree Content Tree" [ref=e303] [cursor=pointer]: + - button "Toggle content tree" [ref=e304]: + - img [ref=e305] + - text: Content Tree + - generic [ref=e308]: + - button "Create new" [ref=e309] [cursor=pointer]: + - img [ref=e310] + - button "Hide / Unhide" [ref=e312] [cursor=pointer]: + - img [ref=e313] + - button "Copy" [ref=e315] [cursor=pointer]: + - img [ref=e316] + - button "Cut" [ref=e318] [cursor=pointer]: + - img [ref=e319] + - button "Paste" [disabled] [ref=e321]: + - img [ref=e322] + - button "Delete" [ref=e324] [cursor=pointer]: + - img [ref=e325] + - button "Refresh" [ref=e327] [cursor=pointer]: + - img [ref=e328] + - form [ref=e330]: + - button "Toggle inspector" [ref=e331] [cursor=pointer]: + - img [ref=e332] + - generic [ref=e335]: + - generic [ref=e336]: + - heading "Selected element" [level=1] [ref=e337]: + - generic [ref=e339]: Selected element + - button "Home" [ref=e342] [cursor=pointer]: + - button "Home" [ref=e345]: + - generic [ref=e346]: + - img + - text: Home + - img + - tablist [ref=e347]: + - list [ref=e348]: + - tab "General" [selected] [ref=e349] [cursor=pointer]: + - img [ref=e350] + - tab "Site configuration" [ref=e352] [cursor=pointer]: + - img [ref=e353] + - tab "Metadata" [ref=e355] [cursor=pointer]: + - img [ref=e356] + - tab "SEO" [ref=e358] [cursor=pointer]: + - img [ref=e359] + - tabpanel [ref=e362]: + - generic [ref=e363]: + - generic [ref=e364]: + - button "Document" [expanded] [ref=e365]: + - heading "Document" [level=2] [ref=e366] [cursor=pointer]: + - img [ref=e368] + - text: Document + - button [ref=e370] [cursor=pointer]: + - img [ref=e371] + - generic [ref=e375]: + - generic [ref=e376]: + - generic [ref=e379] [cursor=pointer]: Title + - textbox "Title" [ref=e381]: Home + - generic [ref=e382]: + - generic [ref=e385] [cursor=pointer]: URL path segment + - generic [ref=e386]: + - textbox "URL path segment" [ref=e389]: home + - button "Syncronize with the title property" [ref=e391] [cursor=pointer]: + - img [ref=e392] + - generic [ref=e394]: + - generic [ref=e397] [cursor=pointer]: Title Override + - textbox "Title Override" [ref=e398]: + - /placeholder: Used in <title> tag, max. 60 chars + - generic [ref=e399]: + - button "Teaser image" [expanded] [ref=e400]: + - heading "Teaser image" [level=2] [ref=e401] [cursor=pointer]: + - img [ref=e403] + - text: Teaser image + - button [ref=e405] [cursor=pointer]: + - img [ref=e406] + - generic [ref=e411]: + - generic [ref=e414] [cursor=pointer]: Image + - generic [ref=e415]: + - button "image" [ref=e418] [cursor=pointer]: + - img "image" [ref=e420] + - generic [ref=e421]: + - generic [ref=e422]: + - button "Media" [ref=e423] [cursor=pointer]: + - img [ref=e424] + - button "Choose file" [ref=e426] [cursor=pointer]: + - img [ref=e427] + - button "Remove" [ref=e429] [cursor=pointer]: + - img [ref=e430] + - button "Crop" [ref=e432] [cursor=pointer]: + - img [ref=e433] + - generic [ref=e435]: + - button "Discard" [disabled] [ref=e436] + - button "Apply" [disabled] [ref=e437] +``` \ No newline at end of file diff --git a/Tests/E2E/test-results/enforce-for-role-administrator-is-redirected-to-2FA-setup-chromium/error-context.md b/Tests/E2E/test-results/enforce-for-role-administrator-is-redirected-to-2FA-setup-chromium/error-context.md new file mode 100644 index 0000000..018279c --- /dev/null +++ b/Tests/E2E/test-results/enforce-for-role-administrator-is-redirected-to-2FA-setup-chromium/error-context.md @@ -0,0 +1,327 @@ +# Page snapshot + +```yaml +- generic [ref=e4]: + - generic [ref=e5]: + - generic [ref=e6]: + - button "Menu" [ref=e7] [cursor=pointer] + - img [ref=e11] + - generic [ref=e16]: + - button "In-Place" [ref=e19] [cursor=pointer]: + - img + - generic [ref=e20]: In-Place + - img + - button "English (US)" [ref=e23] [cursor=pointer]: + - button "English (US)" [ref=e26]: + - generic [ref=e27]: + - img + - link "English (US)" [ref=e28]: + - /url: http://localhost:8081/neos/content?node=%2Fsites%2Fneosdemo%3Blanguage%3Den_US + - img + - button "E2E Admin" [ref=e31] [cursor=pointer]: + - img + - text: E2E Admin + - img + - generic [ref=e32]: + - button "Published - live" [disabled] [ref=e33] + - button [ref=e34] [cursor=pointer]: + - img + - generic [ref=e36]: + - button "Display Keyboard Shortcuts" [ref=e37] [cursor=pointer]: + - img [ref=e38] + - link "Show preview" [ref=e40] [cursor=pointer]: + - /url: http://localhost:8081/neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eadmin%3Blanguage%3Den_US + - img [ref=e41] + - button "Activate Fullscreen edit mode" [ref=e43] [cursor=pointer]: + - img [ref=e44] + - iframe [ref=e48]: + - generic [active] [ref=f1e1]: + - banner [ref=f1e2]: + - generic [ref=f1e3]: + - link [ref=f1e4] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eadmin%3Blanguage%3Den_US + - img [ref=f1e5] + - navigation + - button "Open menu" [ref=f1e11] [cursor=pointer]: + - generic [ref=f1e12]: Open menu + - img [ref=f1e13] + - navigation [ref=f1e15]: + - button "English (US)" [ref=f1e16] [cursor=pointer]: + - text: English (US) + - img [ref=f1e17] + - heading "Welcome to the Neos CMS demo" [level=1] [ref=f1e22]: + - textbox "Rich Text Editor, main" [ref=f1e23] + - main [ref=f1e24]: + - generic [ref=f1e25]: + - generic [ref=f1e26]: + - generic [ref=f1e27]: + - heading "Imagine this..." [level=2] [ref=f1e28]: + - textbox "Rich Text Editor, main" [ref=f1e29] + - textbox "Rich Text Editor, main" [ref=f1e31]: + - paragraph [ref=f1e32]: In only a few years from now, the world will have entirely new cities. Cities where development has not even started. Cities that can draw from all we know about liveability and functionality. + - paragraph [ref=f1e33]: "The same goes for websites. Major websites will be built with the tools we’re constructing today. But we don't know how they will be like. Just think of how different sites were 3 years ago. For both cities and websites, one basic rule counts: Things are constantly changing and can never be fully anticipated." + - paragraph [ref=f1e34]: The inspiration from urban planning permeates our UX master plan, giving us crucial guidelines for structuring everything we know - and making room for all the future change we don’t know. + - generic [ref=f1e35]: + - heading "Power when you need it" [level=2] [ref=f1e36]: + - textbox "Rich Text Editor, main" [ref=f1e37] + - textbox "Rich Text Editor, main" [ref=f1e39]: + - paragraph [ref=f1e40]: The distribution of power in a city makes sure that everyone gets what they need. You don’t power a shed in the way you power a nuclear plant. The ultra-simple editing makes editors center their attention on content. If you need to add SEO data next to the content, Neos has an inspector that opens up for that kind of functionality. Just a bit more power, one step at a time... + - paragraph [ref=f1e41]: If you need more, the inspector will be expandable to the full backend screen providing the user with all the options. That’s not enough? Create custom backend modules. And if even that's not enough, underlying Neos, we have Flow - a first-class web framework that enables you to build web applications that integrate with your other systems. + - generic [ref=f1e42]: + - generic [ref=f1e43]: + - heading "Create an account and access the backend" [level=2] [ref=f1e44]: + - textbox "Rich Text Editor, main" [ref=f1e45]: + - generic [ref=f1e46]: Create an account and access the backend + - textbox "Rich Text Editor, main" [ref=f1e48]: + - paragraph [ref=f1e49]: Try out our latest development of the Neos user interface. Get a first impression of CKEditor, the content editor, and try out the simple publishing mechanism based on workspaces. + - paragraph [ref=f1e50]: The registration form is also an example of a "Plugin"-element. + - generic [ref=f1e53]: + - group [ref=f1e54]: + - generic [ref=f1e55]: + - generic [ref=f1e56]: Username + - textbox "Username" [ref=f1e57] + - generic [ref=f1e58]: (lowercase letters and numbers only) + - generic [ref=f1e59]: + - generic [ref=f1e60]: Password + - textbox "Password" [ref=f1e61] + - button "Submit" [ref=f1e62] [cursor=pointer] + - contentinfo [ref=f1e64]: + - navigation [ref=f1e65]: + - link "Contribute" [ref=f1e66] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fcontribute%40user-e2eadmin%3Blanguage%3Den_US + - link "Documentation & Support" [ref=f1e67] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdocumentation%40user-e2eadmin%3Blanguage%3Den_US + - link "Download" [ref=f1e68] [cursor=pointer]: + - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdownload%40user-e2eadmin%3Blanguage%3Den_US + - textbox "Rich Text Editor, main" [ref=f1e72]: + - paragraph [ref=f1e73]: Powered by Neos & Flow + - generic [ref=e53]: "UI version: 8.4.3" + - button "Document Tree" [ref=e54] [cursor=pointer]: + - button "Navigate" [ref=e55]: + - img [ref=e56] + - text: Document Tree + - generic [ref=e58]: + - generic [ref=e59]: + - generic [ref=e62]: + - button "Create new" [ref=e63] [cursor=pointer]: + - img [ref=e64] + - button "Hide / Unhide" [disabled] [ref=e66]: + - img [ref=e67] + - button "Copy" [ref=e69] [cursor=pointer]: + - img [ref=e70] + - button "Cut" [disabled] [ref=e72]: + - img [ref=e73] + - button "Paste" [disabled] [ref=e75]: + - img [ref=e76] + - button "Delete" [disabled] [ref=e78]: + - img [ref=e79] + - button "Refresh" [ref=e81] [cursor=pointer]: + - img [ref=e82] + - button [ref=e84] [cursor=pointer]: + - img [ref=e85] + - tree [ref=e87]: + - treeitem "Home" [ref=e88]: + - button "Collapse All" [ref=e89] [cursor=pointer]: + - img [ref=e90] + - treeitem "Home" [expanded] [ref=e92]: + - generic [ref=e94]: + - button "Home" [ref=e96] [cursor=pointer]: + - generic [ref=e97]: + - img [ref=e99] + - treeitem "Home" [ref=e101] + - button [ref=e102] [cursor=pointer]: + - img [ref=e103] + - generic [ref=e105]: + - treeitem "Home" [expanded] [ref=e106]: + - button "Home" [ref=e110] [cursor=pointer]: + - generic [ref=e111]: + - img [ref=e113] + - treeitem "Home" [ref=e115] + - treeitem "Features" [expanded] [ref=e116]: + - generic [ref=e118]: + - button "Features" [ref=e120] [cursor=pointer]: + - generic [ref=e121]: + - img [ref=e123] + - treeitem "Features" [ref=e125] + - button [ref=e126] [cursor=pointer]: + - img [ref=e127] + - generic [ref=e129]: + - treeitem "Multiple columns" [ref=e130]: + - button "Multiple columns" [ref=e134] [cursor=pointer]: + - generic [ref=e135]: + - img [ref=e137] + - treeitem "Multiple columns" [ref=e139] + - treeitem "Text & images" [ref=e140]: + - button "Text & images" [ref=e144] [cursor=pointer]: + - generic [ref=e145]: + - img [ref=e147] + - treeitem "Text & images" [ref=e149] + - treeitem "Navigation elements" [ref=e150]: + - generic [ref=e152]: + - button "Navigation elements" [ref=e154] [cursor=pointer]: + - generic [ref=e155]: + - img [ref=e157] + - treeitem "Navigation elements" [ref=e159] + - button [ref=e160] [cursor=pointer]: + - img [ref=e161] + - treeitem "Custom elements" [ref=e163]: + - button "Custom elements" [ref=e167] [cursor=pointer]: + - generic [ref=e168]: + - img [ref=e170] + - treeitem "Custom elements" [ref=e172] + - treeitem "Other elements" [ref=e173]: + - button "Other elements" [ref=e177] [cursor=pointer]: + - generic [ref=e178]: + - img [ref=e180] + - treeitem "Other elements" [ref=e182] + - treeitem "Forms" [ref=e183]: + - button "Forms" [ref=e187] [cursor=pointer]: + - generic [ref=e188]: + - img [ref=e190] + - treeitem "Forms" [ref=e192] + - treeitem "Blog" [expanded] [ref=e194]: + - generic [ref=e196]: + - button "Blog" [ref=e198] [cursor=pointer]: + - generic [ref=e199]: + - img [ref=e201] + - treeitem "Blog" [ref=e203] + - button [ref=e204] [cursor=pointer]: + - img [ref=e205] + - generic [ref=e207]: + - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e208]: + - button "Neos 8.0 \"Mad Hatter\" released" [ref=e212] [cursor=pointer]: + - generic [ref=e213]: + - img [ref=e215] + - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e217] + - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e218]: + - button "Neos 8.1 \"Frog–Footman\" released" [ref=e222] [cursor=pointer]: + - generic [ref=e223]: + - img [ref=e225] + - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e227] + - treeitem "Neos 8.2 released" [ref=e228]: + - button "Neos 8.2 released" [ref=e232] [cursor=pointer]: + - generic [ref=e233]: + - img [ref=e235] + - treeitem "Neos 8.2 released" [ref=e237] + - treeitem "Neos 8.3 released" [ref=e238]: + - button "Neos 8.3 released" [ref=e242] [cursor=pointer]: + - generic [ref=e243]: + - img [ref=e245] + - treeitem "Neos 8.3 released" [ref=e247] + - treeitem "Neos 8.4 released" [ref=e248]: + - button "Neos 8.4 released" [ref=e252] [cursor=pointer]: + - generic [ref=e253]: + - img [ref=e255] + - treeitem "Neos 8.4 released" [ref=e257] + - treeitem "Download" [expanded] [ref=e259]: + - button "Download" [ref=e263] [cursor=pointer]: + - generic [ref=e264]: + - img [ref=e266] + - treeitem "Download" [ref=e268] + - treeitem "Contribute" [expanded] [ref=e269]: + - button "Contribute" [ref=e273] [cursor=pointer]: + - generic [ref=e274]: + - img [ref=e276] + - treeitem "Contribute" [ref=e278] + - treeitem "Documentation & Support" [expanded] [ref=e279]: + - button "Documentation & Support" [ref=e283] [cursor=pointer]: + - generic [ref=e284]: + - img [ref=e286] + - treeitem "Documentation & Support" [ref=e288] + - treeitem "Not Found" [expanded] [ref=e289]: + - button "Not Found" [ref=e293] [cursor=pointer]: + - generic [ref=e294]: + - img [ref=e296] + - treeitem "Not Found" [ref=e298] + - generic [ref=e301]: + - button "Toggle content tree Content Tree" [ref=e303] [cursor=pointer]: + - button "Toggle content tree" [ref=e304]: + - img [ref=e305] + - text: Content Tree + - generic [ref=e308]: + - button "Create new" [ref=e309] [cursor=pointer]: + - img [ref=e310] + - button "Hide / Unhide" [ref=e312] [cursor=pointer]: + - img [ref=e313] + - button "Copy" [ref=e315] [cursor=pointer]: + - img [ref=e316] + - button "Cut" [ref=e318] [cursor=pointer]: + - img [ref=e319] + - button "Paste" [disabled] [ref=e321]: + - img [ref=e322] + - button "Delete" [ref=e324] [cursor=pointer]: + - img [ref=e325] + - button "Refresh" [ref=e327] [cursor=pointer]: + - img [ref=e328] + - form [ref=e330]: + - button "Toggle inspector" [ref=e331] [cursor=pointer]: + - img [ref=e332] + - generic [ref=e335]: + - generic [ref=e336]: + - heading "Selected element" [level=1] [ref=e337]: + - generic [ref=e339]: Selected element + - button "Home" [ref=e342] [cursor=pointer]: + - button "Home" [ref=e345]: + - generic [ref=e346]: + - img + - text: Home + - img + - tablist [ref=e347]: + - list [ref=e348]: + - tab "General" [selected] [ref=e349] [cursor=pointer]: + - img [ref=e350] + - tab "Site configuration" [ref=e352] [cursor=pointer]: + - img [ref=e353] + - tab "Metadata" [ref=e355] [cursor=pointer]: + - img [ref=e356] + - tab "SEO" [ref=e358] [cursor=pointer]: + - img [ref=e359] + - tabpanel [ref=e362]: + - generic [ref=e363]: + - generic [ref=e364]: + - button "Document" [expanded] [ref=e365]: + - heading "Document" [level=2] [ref=e366] [cursor=pointer]: + - img [ref=e368] + - text: Document + - button [ref=e370] [cursor=pointer]: + - img [ref=e371] + - generic [ref=e375]: + - generic [ref=e376]: + - generic [ref=e379] [cursor=pointer]: Title + - textbox "Title" [ref=e381]: Home + - generic [ref=e382]: + - generic [ref=e385] [cursor=pointer]: URL path segment + - generic [ref=e386]: + - textbox "URL path segment" [ref=e389]: home + - button "Syncronize with the title property" [ref=e391] [cursor=pointer]: + - img [ref=e392] + - generic [ref=e394]: + - generic [ref=e397] [cursor=pointer]: Title Override + - textbox "Title Override" [ref=e398]: + - /placeholder: Used in <title> tag, max. 60 chars + - generic [ref=e399]: + - button "Teaser image" [expanded] [ref=e400]: + - heading "Teaser image" [level=2] [ref=e401] [cursor=pointer]: + - img [ref=e403] + - text: Teaser image + - button [ref=e405] [cursor=pointer]: + - img [ref=e406] + - generic [ref=e411]: + - generic [ref=e414] [cursor=pointer]: Image + - generic [ref=e415]: + - button "image" [ref=e418] [cursor=pointer]: + - img "image" [ref=e420] + - generic [ref=e421]: + - generic [ref=e422]: + - button "Media" [ref=e423] [cursor=pointer]: + - img [ref=e424] + - button "Choose file" [ref=e426] [cursor=pointer]: + - img [ref=e427] + - button "Remove" [ref=e429] [cursor=pointer]: + - img [ref=e430] + - button "Crop" [ref=e432] [cursor=pointer]: + - img [ref=e433] + - generic [ref=e435]: + - button "Discard" [disabled] [ref=e436] + - button "Apply" [disabled] [ref=e437] +``` \ No newline at end of file diff --git a/Tests/E2E/test-results/issuer-name-change-2FA-set-63185-with-the-custom-issuer-name-chromium/error-context.md b/Tests/E2E/test-results/issuer-name-change-2FA-set-63185-with-the-custom-issuer-name-chromium/error-context.md new file mode 100644 index 0000000..d21e3c5 --- /dev/null +++ b/Tests/E2E/test-results/issuer-name-change-2FA-set-63185-with-the-custom-issuer-name-chromium/error-context.md @@ -0,0 +1,25 @@ +# Page snapshot + +```yaml +- generic [ref=e2]: + - main [ref=e3]: + - generic [ref=e4]: + - figure [ref=e5]: + - img "Neos Logo" [ref=e6] + - heading "Login to Neos Demo Site" [level=1] [ref=e7]: + - text: Login to + - strong [ref=e8]: Neos Demo Site + - generic [ref=e10]: + - img [ref=e12] + - button "Show Code" [ref=e15] [cursor=pointer] + - textbox "OTP from App" [ref=e17] + - button "Register new OTP" [ref=e19] [cursor=pointer] + - contentinfo [ref=e20]: + - paragraph [ref=e21]: + - link "Neos" [ref=e22] [cursor=pointer]: + - /url: http://neos.io + - text: – © 2006-2026 This is free software, licensed under GPL3 or higher, and you are welcome to redistribute it under certain conditions; Neos comes with ABSOLUTELY NO WARRANTY; See + - link "neos.io" [ref=e23] [cursor=pointer]: + - /url: http://neos.io + - text: for more details. Obstructing the appearance of this notice is prohibited by law. +``` \ No newline at end of file diff --git a/Tests/E2E/test-results/second-factor-login-login--8650e-en-a-2FA-device-is-enrolled-chromium/error-context.md b/Tests/E2E/test-results/second-factor-login-login--8650e-en-a-2FA-device-is-enrolled-chromium/error-context.md new file mode 100644 index 0000000..d21e3c5 --- /dev/null +++ b/Tests/E2E/test-results/second-factor-login-login--8650e-en-a-2FA-device-is-enrolled-chromium/error-context.md @@ -0,0 +1,25 @@ +# Page snapshot + +```yaml +- generic [ref=e2]: + - main [ref=e3]: + - generic [ref=e4]: + - figure [ref=e5]: + - img "Neos Logo" [ref=e6] + - heading "Login to Neos Demo Site" [level=1] [ref=e7]: + - text: Login to + - strong [ref=e8]: Neos Demo Site + - generic [ref=e10]: + - img [ref=e12] + - button "Show Code" [ref=e15] [cursor=pointer] + - textbox "OTP from App" [ref=e17] + - button "Register new OTP" [ref=e19] [cursor=pointer] + - contentinfo [ref=e20]: + - paragraph [ref=e21]: + - link "Neos" [ref=e22] [cursor=pointer]: + - /url: http://neos.io + - text: – © 2006-2026 This is free software, licensed under GPL3 or higher, and you are welcome to redistribute it under certain conditions; Neos comes with ABSOLUTELY NO WARRANTY; See + - link "neos.io" [ref=e23] [cursor=pointer]: + - /url: http://neos.io + - text: for more details. Obstructing the appearance of this notice is prohibited by law. +``` \ No newline at end of file diff --git a/Tests/E2E/test-results/second-factor-setup-comple-4d756-ts-away-from-the-setup-page-chromium/error-context.md b/Tests/E2E/test-results/second-factor-setup-comple-4d756-ts-away-from-the-setup-page-chromium/error-context.md new file mode 100644 index 0000000..d21e3c5 --- /dev/null +++ b/Tests/E2E/test-results/second-factor-setup-comple-4d756-ts-away-from-the-setup-page-chromium/error-context.md @@ -0,0 +1,25 @@ +# Page snapshot + +```yaml +- generic [ref=e2]: + - main [ref=e3]: + - generic [ref=e4]: + - figure [ref=e5]: + - img "Neos Logo" [ref=e6] + - heading "Login to Neos Demo Site" [level=1] [ref=e7]: + - text: Login to + - strong [ref=e8]: Neos Demo Site + - generic [ref=e10]: + - img [ref=e12] + - button "Show Code" [ref=e15] [cursor=pointer] + - textbox "OTP from App" [ref=e17] + - button "Register new OTP" [ref=e19] [cursor=pointer] + - contentinfo [ref=e20]: + - paragraph [ref=e21]: + - link "Neos" [ref=e22] [cursor=pointer]: + - /url: http://neos.io + - text: – © 2006-2026 This is free software, licensed under GPL3 or higher, and you are welcome to redistribute it under certain conditions; Neos comes with ABSOLUTELY NO WARRANTY; See + - link "neos.io" [ref=e23] [cursor=pointer]: + - /url: http://neos.io + - text: for more details. Obstructing the appearance of this notice is prohibited by law. +``` \ No newline at end of file diff --git a/Tests/E2E/tests/backend-module.spec.ts b/Tests/E2E/tests/backend-module.spec.ts new file mode 100644 index 0000000..f75b2ec --- /dev/null +++ b/Tests/E2E/tests/backend-module.spec.ts @@ -0,0 +1,74 @@ +import { test, expect } from '@playwright/test'; +import { execSync } from 'node:child_process'; +import { NeosLoginPage, SecondFactorLoginPage, SecondFactorSetupPage, BackendModulePage } from '../helpers/pages'; +import { generateOtp } from '../helpers/totp'; + +/** + * Tests the Neos backend module for managing 2FA devices. + * A device is enrolled in beforeAll by navigating to the setup page directly. + * After all tests, any remaining devices are removed via the Flow CLI command. + */ + +const CONTAINER = `${process.env.SUT || 'neos8'}-neos-1`; + +let enrolledSecret: string; + +test.beforeAll(async ({ browser }) => { + const page = await browser.newPage(); + const loginPage = new NeosLoginPage(page); + const setupPage = new SecondFactorSetupPage(page); + + await loginPage.goto(); + await loginPage.login('e2eadmin', 'password123'); + await page.goto('/neos/second-factor-setup'); + await setupPage.waitForPage(); + + enrolledSecret = await setupPage.getSecret(); + await setupPage.submitOtp(enrolledSecret); + + await page.close(); +}); + +test.afterAll(() => { + execSync( + `docker exec -u www-data -w /app ${CONTAINER} ./flow secondFactor:deleteForAccount --username e2eadmin`, + { stdio: 'inherit' } + ); +}); + +async function loginWithOtp(page: any) { + const loginPage = new NeosLoginPage(page); + const otpPage = new SecondFactorLoginPage(page); + + await loginPage.goto(); + await loginPage.login('e2eadmin', 'password123'); + + await otpPage.waitForPage(); + await otpPage.enterOtp(generateOtp(enrolledSecret)); +} + +test('backend module shows enrolled 2FA devices', async ({ page }) => { + await loginWithOtp(page); + + const modulePage = new BackendModulePage(page); + await modulePage.goto(); + await modulePage.waitForPage(); + + const deviceCount = await modulePage.getDeviceCount(); + expect(deviceCount).toBeGreaterThan(0); +}); + +test('backend module allows deleting a 2FA device', async ({ page }) => { + await loginWithOtp(page); + + const modulePage = new BackendModulePage(page); + await modulePage.goto(); + await modulePage.waitForPage(); + + const before = await modulePage.getDeviceCount(); + await modulePage.deleteFirstDevice(); + + await page.waitForLoadState('networkidle'); + const after = await modulePage.getDeviceCount(); + expect(after).toBe(before - 1); +}); diff --git a/Tests/E2E/tests/enforce-for-all.spec.ts b/Tests/E2E/tests/enforce-for-all.spec.ts new file mode 100644 index 0000000..e837087 --- /dev/null +++ b/Tests/E2E/tests/enforce-for-all.spec.ts @@ -0,0 +1,24 @@ +/** + * Tests for FLOW_CONTEXT=Production/E2E-SUT/EnforceForAll + * Config: enforceTwoFactorAuthentication: true + * + * All backend users must set up 2FA before accessing the backend. + */ +import { test, expect } from '@playwright/test'; +import { NeosLoginPage } from '../helpers/pages'; + +test('administrator is redirected to 2FA setup', async ({ page }) => { + const loginPage = new NeosLoginPage(page); + await loginPage.goto(); + await loginPage.login('e2eadmin', 'password123'); + + await expect(page).toHaveURL(/second-factor-setup/); +}); + +test('editor is redirected to 2FA setup', async ({ page }) => { + const loginPage = new NeosLoginPage(page); + await loginPage.goto(); + await loginPage.login('e2eeditor', 'password123'); + + await expect(page).toHaveURL(/second-factor-setup/); +}); diff --git a/Tests/E2E/tests/enforce-for-provider.spec.ts b/Tests/E2E/tests/enforce-for-provider.spec.ts new file mode 100644 index 0000000..1e97d34 --- /dev/null +++ b/Tests/E2E/tests/enforce-for-provider.spec.ts @@ -0,0 +1,25 @@ +/** + * Tests for FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider + * Config: enforce2FAForAuthenticationProviders: ['Neos.Neos:Backend'] + * + * All users authenticating via the Neos.Neos:Backend provider must set up 2FA. + * In this SUT all backend users (admin and editor) use that provider. + */ +import { test, expect } from '@playwright/test'; +import { NeosLoginPage } from '../helpers/pages'; + +test('administrator is redirected to 2FA setup', async ({ page }) => { + const loginPage = new NeosLoginPage(page); + await loginPage.goto(); + await loginPage.login('e2eadmin', 'password123'); + + await expect(page).toHaveURL(/second-factor-setup/); +}); + +test('editor is redirected to 2FA setup', async ({ page }) => { + const loginPage = new NeosLoginPage(page); + await loginPage.goto(); + await loginPage.login('e2eeditor', 'password123'); + + await expect(page).toHaveURL(/second-factor-setup/); +}); diff --git a/Tests/E2E/tests/enforce-for-role.spec.ts b/Tests/E2E/tests/enforce-for-role.spec.ts new file mode 100644 index 0000000..119e17a --- /dev/null +++ b/Tests/E2E/tests/enforce-for-role.spec.ts @@ -0,0 +1,26 @@ +/** + * Tests for FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole + * Config: enforce2FAForRoles: ['Neos.Neos:Administrator'] + * + * Only users with the Administrator role are required to set up 2FA. + * Users with only the Editor role must be able to log in without it. + */ +import { test, expect } from '@playwright/test'; +import { NeosLoginPage } from '../helpers/pages'; + +test('administrator is redirected to 2FA setup', async ({ page }) => { + const loginPage = new NeosLoginPage(page); + await loginPage.goto(); + await loginPage.login('e2eadmin', 'password123'); + + await expect(page).toHaveURL(/second-factor-setup/); +}); + +test('editor is not redirected and accesses backend directly', async ({ page }) => { + const loginPage = new NeosLoginPage(page); + await loginPage.goto(); + await loginPage.login('e2eeditor', 'password123'); + + await expect(page).not.toHaveURL(/second-factor/); + await expect(page).toHaveURL(/neos/); +}); diff --git a/Tests/E2E/tests/issuer-name-change.spec.ts b/Tests/E2E/tests/issuer-name-change.spec.ts new file mode 100644 index 0000000..b1f4350 --- /dev/null +++ b/Tests/E2E/tests/issuer-name-change.spec.ts @@ -0,0 +1,47 @@ +/** + * Tests for FLOW_CONTEXT=Production/E2E-SUT/IssuerNameChange + * Config: issuerName: 'Test Issuer' + * + * 2FA is not enforced. The setup page is accessed directly after login. + * Tests verify that the changed issuer name does not break the 2FA setup flow. + */ +import { test, expect } from '@playwright/test'; +import { execSync } from 'node:child_process'; +import { NeosLoginPage, SecondFactorSetupPage } from '../helpers/pages'; + +const CONTAINER = `${process.env.SUT || 'neos8'}-neos-1`; + +test.afterEach(() => { + execSync( + `docker exec -u www-data -w /app ${CONTAINER} ./flow secondFactor:deleteForAccount --username e2eadmin`, + { stdio: 'inherit' } + ); +}); + +test('setup page is reachable after login', async ({ page }) => { + const loginPage = new NeosLoginPage(page); + await loginPage.goto(); + await loginPage.login('e2eadmin', 'password123'); + + await page.goto('/neos/second-factor-setup'); + + await expect(page).toHaveURL(/second-factor-setup/); + await expect(page.locator('img[style*="max-width"]')).toBeVisible(); + await expect(page.locator('input#secret')).toBeAttached(); +}); + +test('2FA setup can be completed with the custom issuer name', async ({ page }) => { + const loginPage = new NeosLoginPage(page); + const setupPage = new SecondFactorSetupPage(page); + + await loginPage.goto(); + await loginPage.login('e2eadmin', 'password123'); + + await page.goto('/neos/second-factor-setup'); + await setupPage.waitForPage(); + + const secret = await setupPage.getSecret(); + await setupPage.submitOtp(secret); + + await expect(page).not.toHaveURL(/second-factor-setup/); +}); diff --git a/Tests/E2E/tests/login-without-2fa.spec.ts b/Tests/E2E/tests/login-without-2fa.spec.ts new file mode 100644 index 0000000..ccee0db --- /dev/null +++ b/Tests/E2E/tests/login-without-2fa.spec.ts @@ -0,0 +1,12 @@ +import { test, expect } from '@playwright/test'; +import { NeosLoginPage } from '../helpers/pages'; + +test('admin can log in without 2FA configured', async ({ page }) => { + const loginPage = new NeosLoginPage(page); + await loginPage.goto(); + await loginPage.login('e2eadmin', 'password123'); + + // Should land in the Neos backend, not on a 2FA page + await expect(page).not.toHaveURL(/second-factor/); + await expect(page).toHaveURL(/neos/); +}); diff --git a/Tests/E2E/tests/second-factor-login.spec.ts b/Tests/E2E/tests/second-factor-login.spec.ts new file mode 100644 index 0000000..82c35db --- /dev/null +++ b/Tests/E2E/tests/second-factor-login.spec.ts @@ -0,0 +1,77 @@ +import { test, expect } from '@playwright/test'; +import { execSync } from 'node:child_process'; +import { NeosLoginPage, SecondFactorLoginPage, SecondFactorSetupPage } from '../helpers/pages'; +import { generateOtp } from '../helpers/totp'; + +/** + * Tests the 2FA OTP login flow. + * A device is enrolled in beforeAll by navigating to the setup page directly. + * After all tests, the device is removed via the Flow CLI command. + */ + +const CONTAINER = `${process.env.SUT || 'neos8'}-neos-1`; + +let enrolledSecret: string; + +test.beforeAll(async ({ browser }) => { + const page = await browser.newPage(); + const loginPage = new NeosLoginPage(page); + const setupPage = new SecondFactorSetupPage(page); + + await loginPage.goto(); + await loginPage.login('e2eadmin', 'password123'); + await page.goto('/neos/second-factor-setup'); + await setupPage.waitForPage(); + + enrolledSecret = await setupPage.getSecret(); + await setupPage.submitOtp(enrolledSecret); + + await page.close(); +}); + +test.afterAll(() => { + execSync( + `docker exec -u www-data -w /app ${CONTAINER} ./flow secondFactor:deleteForAccount --username e2eadmin`, + { stdio: 'inherit' } + ); +}); + +test('login redirects to OTP page when a 2FA device is enrolled', async ({ page }) => { + const loginPage = new NeosLoginPage(page); + const otpPage = new SecondFactorLoginPage(page); + + await loginPage.goto(); + await loginPage.login('e2eadmin', 'password123'); + + await otpPage.waitForPage(); + await expect(page).toHaveURL(/second-factor-login/); +}); + +test('entering a valid OTP grants backend access', async ({ page }) => { + const loginPage = new NeosLoginPage(page); + const otpPage = new SecondFactorLoginPage(page); + + await loginPage.goto(); + await loginPage.login('e2eadmin', 'password123'); + + await otpPage.waitForPage(); + await otpPage.enterOtp(generateOtp(enrolledSecret)); + + await expect(page).not.toHaveURL(/second-factor/); + await expect(page).toHaveURL(/neos/); +}); + +test('entering an invalid OTP shows an error and stays on the OTP page', async ({ page }) => { + const loginPage = new NeosLoginPage(page); + const otpPage = new SecondFactorLoginPage(page); + + await loginPage.goto(); + await loginPage.login('e2eadmin', 'password123'); + + await otpPage.waitForPage(); + await otpPage.enterOtp('000000'); + + await expect(page).toHaveURL(/second-factor-login/); + const error = await otpPage.getErrorMessage(); + expect(error).toBeTruthy(); +}); diff --git a/Tests/E2E/tests/second-factor-setup.spec.ts b/Tests/E2E/tests/second-factor-setup.spec.ts new file mode 100644 index 0000000..9f81ddd --- /dev/null +++ b/Tests/E2E/tests/second-factor-setup.spec.ts @@ -0,0 +1,48 @@ +import { test, expect } from '@playwright/test'; +import { execSync } from 'node:child_process'; +import { NeosLoginPage, SecondFactorSetupPage } from '../helpers/pages'; + +/** + * Tests the 2FA setup flow. + * The setup page is accessible to any authenticated user regardless of enforcement, + * so tests navigate to it directly after login. + */ + +const CONTAINER = `${process.env.SUT || 'neos8'}-neos-1`; + +function deleteSecondFactors(username: string) { + execSync( + `docker exec -u www-data -w /app ${CONTAINER} ./flow secondFactor:deleteForAccount --username ${username}`, + { stdio: 'inherit' } + ); +} + +test.afterEach(async () => { + deleteSecondFactors('e2eadmin'); +}); + +test('setup page is accessible to authenticated users', async ({ page }) => { + const loginPage = new NeosLoginPage(page); + await loginPage.goto(); + await loginPage.login('e2eadmin', 'password123'); + + await page.goto('/neos/second-factor-setup'); + + await expect(page).toHaveURL(/second-factor-setup/); + await expect(page.locator('input#secret')).toBeAttached(); +}); + +test('completing setup with a valid OTP redirects away from the setup page', async ({ page }) => { + const loginPage = new NeosLoginPage(page); + const setupPage = new SecondFactorSetupPage(page); + + await loginPage.goto(); + await loginPage.login('e2eadmin', 'password123'); + + await page.goto('/neos/second-factor-setup'); + + const secret = await setupPage.getSecret(); + await setupPage.submitOtp(secret); + + await expect(page).not.toHaveURL(/second-factor-setup/); +}); diff --git a/Tests/E2E/tsconfig.json b/Tests/E2E/tsconfig.json new file mode 100644 index 0000000..304009c --- /dev/null +++ b/Tests/E2E/tsconfig.json @@ -0,0 +1,43 @@ +{ + // Visit https://aka.ms/tsconfig to read more about this file + "compilerOptions": { + // File Layout + // "rootDir": "./src", + // "outDir": "./dist", + + // Environment Settings + // See also https://aka.ms/tsconfig/module + "module": "nodenext", + "target": "ESNext", + // For nodejs: + "lib": ["esnext"], + "types": ["node"], + // and npm install -D @types/node + + // Other Outputs + "sourceMap": true, + "declaration": true, + "declarationMap": true, + + // Stricter Typechecking Options + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + + // Style Options + // "noImplicitReturns": true, + // "noImplicitOverride": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noFallthroughCasesInSwitch": true, + // "noPropertyAccessFromIndexSignature": true, + + // Recommended Options + "strict": true, + "jsx": "react-jsx", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "noUncheckedSideEffectImports": true, + "moduleDetection": "force", + "skipLibCheck": true, + } +} diff --git a/Tests/sytem_under_test/neos8/Dockerfile b/Tests/sytem_under_test/neos8/Dockerfile index 1747563..57298be 100644 --- a/Tests/sytem_under_test/neos8/Dockerfile +++ b/Tests/sytem_under_test/neos8/Dockerfile @@ -14,7 +14,8 @@ RUN install-php-extensions \ vips \ redis -RUN apt update && apt install -y git unzip mariadb \ +RUN apt update \ + && apt install -y git unzip mariadb-client \ && apt clean \ && rm -rf /var/lib/apt/lists/* @@ -42,8 +43,17 @@ RUN --mount=type=cache,target=/root/.composer rm -rf /app \ RUN --mount=type=cache,target=/root/.composer composer require rokka/imagine-vips:0.* +# Copy local 2FA package source (Tests/ excluded via .dockerignore) +COPY . /tmp/sandstorm-2fa/ + +# Register local path repository and require local package +RUN --mount=type=cache,target=/root/.composer \ + composer config repositories.sandstorm-2fa \ + '{"type":"path","url":"/tmp/sandstorm-2fa","options":{"symlink":false}}' \ + && composer require sandstorm/neostwofactorauthentication:@dev + # Add config files -ADD sut-files/ / +COPY Tests/sytem_under_test/neos8/sut-files/ / # chown for neos data folder and Resources ONLY RUN mkdir -p /app/Data /app/Web/_Resources \ diff --git a/Tests/sytem_under_test/neos8/docker-compose.yaml b/Tests/sytem_under_test/neos8/docker-compose.yaml index fb84d0c..3b1a5eb 100644 --- a/Tests/sytem_under_test/neos8/docker-compose.yaml +++ b/Tests/sytem_under_test/neos8/docker-compose.yaml @@ -2,10 +2,10 @@ services: neos: user: www-data:www-data build: - context: . - dockerfile: Dockerfile + context: ../../../ + dockerfile: Tests/sytem_under_test/neos8/Dockerfile environment: - FLOW_CONTEXT: 'Production/E2E-SUT' + FLOW_CONTEXT: '${FLOW_CONTEXT:-Production/E2E-SUT}' # DB connection DB_NEOS_HOST: 'db' DB_NEOS_PORT: 3306 diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForAll/Settings.2FA.yaml b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForAll/Settings.2FA.yaml new file mode 100644 index 0000000..fcc6568 --- /dev/null +++ b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForAll/Settings.2FA.yaml @@ -0,0 +1,4 @@ +Sandstorm: + NeosTwoFactorAuthentication: + # enforce 2FA for all users + enforceTwoFactorAuthentication: true diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForProvider/Settings.2FA.yaml b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForProvider/Settings.2FA.yaml new file mode 100644 index 0000000..1921041 --- /dev/null +++ b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForProvider/Settings.2FA.yaml @@ -0,0 +1,4 @@ +Sandstorm: + NeosTwoFactorAuthentication: + # enforce 2FA for specific authentication providers (e.g. Neos.Neos:Backend) + enforce2FAForAuthenticationProviders : ['Neos.Neos:Backend'] diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml new file mode 100644 index 0000000..bb69cc9 --- /dev/null +++ b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml @@ -0,0 +1,4 @@ +Sandstorm: + NeosTwoFactorAuthentication: + # enforce 2FA for specific roles (e.g. Neos.Neos:Administrator) + enforce2FAForRoles: ['Neos.Neos:Administrator'] diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/IssuerNameChange/Settings.2FA.yaml b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/IssuerNameChange/Settings.2FA.yaml new file mode 100644 index 0000000..44336f1 --- /dev/null +++ b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/IssuerNameChange/Settings.2FA.yaml @@ -0,0 +1,4 @@ +Sandstorm: + NeosTwoFactorAuthentication: + # (optional) if set this will be used as a naming convention for the TOTP. If empty the Site name will be used + issuerName: 'Test Issuer' diff --git a/Tests/sytem_under_test/neos8/sut-files/entrypoint.sh b/Tests/sytem_under_test/neos8/sut-files/entrypoint.sh index 8e6130c..95d57de 100755 --- a/Tests/sytem_under_test/neos8/sut-files/entrypoint.sh +++ b/Tests/sytem_under_test/neos8/sut-files/entrypoint.sh @@ -18,5 +18,8 @@ yes y | ./flow resource:clean || true ./flow resource:publish --collection static +./flow user:create --roles Neos.Neos:Administrator e2eadmin password123 E2E Admin +./flow user:create --roles Neos.Neos:Editor e2eeditor password123 E2E Editor + # 2. We now can start caretakerd, which will run the remaining steps in parallel and restart the container if they fail. /usr/bin/caretakerd run diff --git a/Tests/sytem_under_test/neos9/Dockerfile b/Tests/sytem_under_test/neos9/Dockerfile index 76929e9..44ea46b 100644 --- a/Tests/sytem_under_test/neos9/Dockerfile +++ b/Tests/sytem_under_test/neos9/Dockerfile @@ -43,8 +43,17 @@ RUN --mount=type=cache,target=/root/.composer rm -rf /app \ RUN --mount=type=cache,target=/root/.composer composer require rokka/imagine-vips:0.* +# Copy local 2FA package source (Tests/ excluded via .dockerignore) +COPY . /tmp/sandstorm-2fa/ + +# Register local path repository and require local package +RUN --mount=type=cache,target=/root/.composer \ + composer config repositories.sandstorm-2fa \ + '{"type":"path","url":"/tmp/sandstorm-2fa","options":{"symlink":false}}' \ + && composer require sandstorm/neostwofactorauthentication:@dev + # Add config files -ADD sut-files/ / +ADD Tests/sytem_under_test/neos9/sut-files/ / # chown for neos data folder and Resources ONLY RUN mkdir -p /app/Data /app/Web/_Resources \ diff --git a/Tests/sytem_under_test/neos9/docker-compose.yaml b/Tests/sytem_under_test/neos9/docker-compose.yaml index 346753b..1bd366e 100644 --- a/Tests/sytem_under_test/neos9/docker-compose.yaml +++ b/Tests/sytem_under_test/neos9/docker-compose.yaml @@ -2,10 +2,10 @@ services: neos: user: www-data:www-data build: - context: . - dockerfile: Dockerfile + context: ../../../ + dockerfile: Tests/sytem_under_test/neos9/Dockerfile environment: - FLOW_CONTEXT: 'Production/E2E-SUT' + FLOW_CONTEXT: '${FLOW_CONTEXT:-Production/E2E-SUT}' # DB connection DB_NEOS_HOST: 'db' DB_NEOS_PORT: 3306 diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForAll/Settings.2FA.yaml b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForAll/Settings.2FA.yaml new file mode 100644 index 0000000..fcc6568 --- /dev/null +++ b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForAll/Settings.2FA.yaml @@ -0,0 +1,4 @@ +Sandstorm: + NeosTwoFactorAuthentication: + # enforce 2FA for all users + enforceTwoFactorAuthentication: true diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForProvider/Settings.2FA.yaml b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForProvider/Settings.2FA.yaml new file mode 100644 index 0000000..1921041 --- /dev/null +++ b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForProvider/Settings.2FA.yaml @@ -0,0 +1,4 @@ +Sandstorm: + NeosTwoFactorAuthentication: + # enforce 2FA for specific authentication providers (e.g. Neos.Neos:Backend) + enforce2FAForAuthenticationProviders : ['Neos.Neos:Backend'] diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml new file mode 100644 index 0000000..bb69cc9 --- /dev/null +++ b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml @@ -0,0 +1,4 @@ +Sandstorm: + NeosTwoFactorAuthentication: + # enforce 2FA for specific roles (e.g. Neos.Neos:Administrator) + enforce2FAForRoles: ['Neos.Neos:Administrator'] diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/IssuerNameChange/Settings.2FA.yaml b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/IssuerNameChange/Settings.2FA.yaml new file mode 100644 index 0000000..44336f1 --- /dev/null +++ b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/IssuerNameChange/Settings.2FA.yaml @@ -0,0 +1,4 @@ +Sandstorm: + NeosTwoFactorAuthentication: + # (optional) if set this will be used as a naming convention for the TOTP. If empty the Site name will be used + issuerName: 'Test Issuer' diff --git a/Tests/sytem_under_test/neos9/sut-files/entrypoint.sh b/Tests/sytem_under_test/neos9/sut-files/entrypoint.sh index 53d1b8a..2e5ed1a 100755 --- a/Tests/sytem_under_test/neos9/sut-files/entrypoint.sh +++ b/Tests/sytem_under_test/neos9/sut-files/entrypoint.sh @@ -21,5 +21,8 @@ yes y | ./flow resource:clean || true ./flow resource:publish --collection static +./flow user:create --roles Neos.Neos:Administrator e2eadmin password123 E2E Admin +./flow user:create --roles Neos.Neos:Editor e2eeditor password123 E2E Editor + # 2. We now can start caretakerd, which will run the remaining steps in parallel and restart the container if they fail. /usr/bin/caretakerd run From 4a2a8d04b47d1b657dac1a00719328dbff67efb6 Mon Sep 17 00:00:00 2001 From: Robert Baruck <robert.baruck@sandstorm.de> Date: Thu, 26 Mar 2026 09:59:11 +0100 Subject: [PATCH 03/12] WIP tests passing --- .gitignore | 1 - .../Command/SecondFactorCommandController.php | 16 + Classes/Controller/BackendController.php | 4 +- Classes/Controller/LoginController.php | 5 +- Classes/Domain/Model/SecondFactor.php | 21 + .../Repository/SecondFactorRepository.php | 3 +- Migrations/Mysql/Version20260325141345.php | 41 ++ .../Integration/Controller/Backend/New.fusion | 10 +- .../Components/SecondFactorList.fusion | 8 +- .../Pages/SetupSecondFactorPage.fusion | 7 + Resources/Private/Translations/de/Backend.xlf | 10 +- Resources/Private/Translations/de/Main.xlf | 4 + Resources/Private/Translations/en/Backend.xlf | 7 +- Resources/Private/Translations/en/Main.xlf | 3 + Tests/E2E/.gitignore | 6 + .../features/default/backend-module.feature | 46 ++ Tests/E2E/features/default/login.feature | 31 + .../enforce-for-all/backend-module.feature | 52 ++ .../features/enforce-for-all/login.feature | 21 + Tests/E2E/global-teardown.ts | 3 +- Tests/E2E/helpers/pages.ts | 48 +- Tests/E2E/helpers/state.ts | 3 + Tests/E2E/helpers/system.ts | 29 + Tests/E2E/helpers/totp.ts | 4 +- Tests/E2E/package-lock.json | 616 +++++++++++++++++- Tests/E2E/package.json | 31 +- Tests/E2E/playwright.config.ts | 21 +- Tests/E2E/steps/backend-module.steps.ts | 49 ++ Tests/E2E/steps/hooks.ts | 16 + Tests/E2E/steps/login.steps.ts | 104 +++ Tests/E2E/test-results/.last-run.json | 14 - .../error-context.md | 25 - .../error-context.md | 327 ---------- .../error-context.md | 327 ---------- .../error-context.md | 327 ---------- .../error-context.md | 327 ---------- .../error-context.md | 327 ---------- .../error-context.md | 25 - .../error-context.md | 25 - .../error-context.md | 25 - Tests/E2E/tests/backend-module.spec.ts | 74 --- Tests/E2E/tests/enforce-for-all.spec.ts | 24 - Tests/E2E/tests/enforce-for-provider.spec.ts | 25 - Tests/E2E/tests/enforce-for-role.spec.ts | 26 - Tests/E2E/tests/issuer-name-change.spec.ts | 47 -- Tests/E2E/tests/login-without-2fa.spec.ts | 12 - Tests/E2E/tests/second-factor-login.spec.ts | 77 --- Tests/E2E/tests/second-factor-setup.spec.ts | 48 -- Tests/E2E/tsconfig.json | 3 +- .../neos8/docker-compose.yaml | 4 +- .../neos8/sut-files/entrypoint.sh | 3 - .../neos8/sut-files/etc/frankenphp/Caddyfile | 2 +- .../neos9/docker-compose.yaml | 4 +- .../neos9/sut-files/entrypoint.sh | 3 - .../neos9/sut-files/etc/frankenphp/Caddyfile | 2 +- composer.json | 5 + 56 files changed, 1176 insertions(+), 2152 deletions(-) create mode 100644 Migrations/Mysql/Version20260325141345.php create mode 100644 Tests/E2E/.gitignore create mode 100644 Tests/E2E/features/default/backend-module.feature create mode 100644 Tests/E2E/features/default/login.feature create mode 100644 Tests/E2E/features/enforce-for-all/backend-module.feature create mode 100644 Tests/E2E/features/enforce-for-all/login.feature create mode 100644 Tests/E2E/helpers/state.ts create mode 100644 Tests/E2E/helpers/system.ts create mode 100644 Tests/E2E/steps/backend-module.steps.ts create mode 100644 Tests/E2E/steps/hooks.ts create mode 100644 Tests/E2E/steps/login.steps.ts delete mode 100644 Tests/E2E/test-results/.last-run.json delete mode 100644 Tests/E2E/test-results/backend-module-backend-module-shows-enrolled-2FA-devices-chromium/error-context.md delete mode 100644 Tests/E2E/test-results/enforce-for-all-administrator-is-redirected-to-2FA-setup-chromium/error-context.md delete mode 100644 Tests/E2E/test-results/enforce-for-all-editor-is-redirected-to-2FA-setup-chromium/error-context.md delete mode 100644 Tests/E2E/test-results/enforce-for-provider-admin-97496--is-redirected-to-2FA-setup-chromium/error-context.md delete mode 100644 Tests/E2E/test-results/enforce-for-provider-editor-is-redirected-to-2FA-setup-chromium/error-context.md delete mode 100644 Tests/E2E/test-results/enforce-for-role-administrator-is-redirected-to-2FA-setup-chromium/error-context.md delete mode 100644 Tests/E2E/test-results/issuer-name-change-2FA-set-63185-with-the-custom-issuer-name-chromium/error-context.md delete mode 100644 Tests/E2E/test-results/second-factor-login-login--8650e-en-a-2FA-device-is-enrolled-chromium/error-context.md delete mode 100644 Tests/E2E/test-results/second-factor-setup-comple-4d756-ts-away-from-the-setup-page-chromium/error-context.md delete mode 100644 Tests/E2E/tests/backend-module.spec.ts delete mode 100644 Tests/E2E/tests/enforce-for-all.spec.ts delete mode 100644 Tests/E2E/tests/enforce-for-provider.spec.ts delete mode 100644 Tests/E2E/tests/enforce-for-role.spec.ts delete mode 100644 Tests/E2E/tests/issuer-name-change.spec.ts delete mode 100644 Tests/E2E/tests/login-without-2fa.spec.ts delete mode 100644 Tests/E2E/tests/second-factor-login.spec.ts delete mode 100644 Tests/E2E/tests/second-factor-setup.spec.ts diff --git a/.gitignore b/.gitignore index e9a196e..e6abe13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # 3rd party sources Packages/ vendor/ -node_modules/ # composer composer.lock diff --git a/Classes/Command/SecondFactorCommandController.php b/Classes/Command/SecondFactorCommandController.php index 660ad48..280e60f 100644 --- a/Classes/Command/SecondFactorCommandController.php +++ b/Classes/Command/SecondFactorCommandController.php @@ -39,4 +39,20 @@ public function deleteForAccountCommand(string $username): void $this->outputLine('Deleted %d second factor(s) for account "%s".', [$count, $username]); } + + /** + * Delete all second factors for all accounts + */ + public function deleteAllCommand(): void + { + $factors = $this->secondFactorRepository->findAll(); + $count = 0; + foreach ($factors as $factor) { + $this->secondFactorRepository->remove($factor); + $count++; + } + $this->persistenceManager->persistAll(); + + $this->outputLine('Deleted %d second factor(s).', [$count]); + } } diff --git a/Classes/Controller/BackendController.php b/Classes/Controller/BackendController.php index 5e99390..0ba7c48 100644 --- a/Classes/Controller/BackendController.php +++ b/Classes/Controller/BackendController.php @@ -137,7 +137,7 @@ public function newAction(): void * @throws IllegalObjectTypeException * @throws StopActionException */ - public function createAction(string $secret, string $secondFactorFromApp): void + public function createAction(string $secret, string $secondFactorFromApp, string $name = ''): void { $isValid = TOTPService::checkIfOtpIsValid($secret, $secondFactorFromApp); @@ -157,7 +157,7 @@ public function createAction(string $secret, string $secondFactorFromApp): void $this->redirect('new'); } - $this->secondFactorRepository->createSecondFactorForAccount($secret, $this->securityContext->getAccount()); + $this->secondFactorRepository->createSecondFactorForAccount($secret, $this->securityContext->getAccount(), $name); $this->secondFactorSessionStorageService->setAuthenticationStatus(AuthenticationStatus::AUTHENTICATED); diff --git a/Classes/Controller/LoginController.php b/Classes/Controller/LoginController.php index 33c7ebc..2a59f6c 100644 --- a/Classes/Controller/LoginController.php +++ b/Classes/Controller/LoginController.php @@ -177,12 +177,13 @@ public function setupSecondFactorAction(?string $username = null): void /** * @param string $secret * @param string $secondFactorFromApp + * @param string $name * @return void * @throws IllegalObjectTypeException * @throws SessionNotStartedException * @throws StopActionException */ - public function createSecondFactorAction(string $secret, string $secondFactorFromApp): void + public function createSecondFactorAction(string $secret, string $secondFactorFromApp, string $name = ''): void { $isValid = TOTPService::checkIfOtpIsValid($secret, $secondFactorFromApp); @@ -204,7 +205,7 @@ public function createSecondFactorAction(string $secret, string $secondFactorFro $account = $this->securityContext->getAccount(); - $this->secondFactorRepository->createSecondFactorForAccount($secret, $account); + $this->secondFactorRepository->createSecondFactorForAccount($secret, $account, $name); $this->addFlashMessage( $this->translator->translateById( diff --git a/Classes/Domain/Model/SecondFactor.php b/Classes/Domain/Model/SecondFactor.php index 569d9d4..bbde9d5 100644 --- a/Classes/Domain/Model/SecondFactor.php +++ b/Classes/Domain/Model/SecondFactor.php @@ -8,6 +8,8 @@ use Doctrine\ORM\Mapping as ORM; use Neos\Flow\Annotations as Flow; +// TODO: refactor to PHP8 code + /** * Store the secrets needed for two factor authentication * @@ -39,6 +41,11 @@ class SecondFactor */ protected string $secret; + /** + * @var string + */ + protected string $name; + /** * Introduced with version 1.4.0 * Nullable for backwards compatibility. Null values will be shown as '-' in backend module. @@ -73,6 +80,7 @@ public function getType(): int } /** + * Used in Fusion rendering * @return string */ public function getTypeAsName(): string @@ -104,6 +112,19 @@ public function setSecret(string $secret): void $this->secret = $secret; } + public function setName(string $name): void + { + $this->name = $name; + } + + public function getName(): string + { + return $this->name; + } + + /** + * Used in Fusion rendering + */ public function getCreationDate(): DateTime|null { return $this->creationDate; diff --git a/Classes/Domain/Repository/SecondFactorRepository.php b/Classes/Domain/Repository/SecondFactorRepository.php index 08aa1fe..8e076b4 100644 --- a/Classes/Domain/Repository/SecondFactorRepository.php +++ b/Classes/Domain/Repository/SecondFactorRepository.php @@ -24,12 +24,13 @@ class SecondFactorRepository extends Repository /** * @throws IllegalObjectTypeException */ - public function createSecondFactorForAccount(string $secret, Account $account): void + public function createSecondFactorForAccount(string $secret, Account $account, string $name): void { $secondFactor = new SecondFactor(); $secondFactor->setAccount($account); $secondFactor->setSecret($secret); $secondFactor->setType(SecondFactor::TYPE_TOTP); + $secondFactor->setName($name); $secondFactor->setCreationDate(new \DateTime()); $this->add($secondFactor); $this->persistenceManager->persistAll(); diff --git a/Migrations/Mysql/Version20260325141345.php b/Migrations/Mysql/Version20260325141345.php new file mode 100644 index 0000000..121dabf --- /dev/null +++ b/Migrations/Mysql/Version20260325141345.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +namespace Neos\Flow\Persistence\Doctrine\Migrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20260325141345 extends AbstractMigration +{ + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->abortIf( + !$this->connection->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\MySqlPlatform, + "Migration can only be executed safely on '\Doctrine\DBAL\Platforms\MySqlPlatform,'." + ); + + $this->addSql('ALTER TABLE sandstorm_neostwofactorauthentication_domain_model_secondfactor ADD name VARCHAR(255) NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->abortIf( + !$this->connection->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\MySqlPlatform, + "Migration can only be executed safely on '\Doctrine\DBAL\Platforms\MySqlPlatform,'." + ); + + $this->addSql('ALTER TABLE sandstorm_neostwofactorauthentication_domain_model_secondfactor DROP name'); + } +} diff --git a/Resources/Private/Fusion/Integration/Controller/Backend/New.fusion b/Resources/Private/Fusion/Integration/Controller/Backend/New.fusion index 16ed82a..e573c51 100644 --- a/Resources/Private/Fusion/Integration/Controller/Backend/New.fusion +++ b/Resources/Private/Fusion/Integration/Controller/Backend/New.fusion @@ -87,10 +87,18 @@ Sandstorm.NeosTwoFactorAuthentication.BackendController.new = Sandstorm.NeosTwoF attributes.aria-label={I18n.id('otp-placeholder').package('Sandstorm.NeosTwoFactorAuthentication')} attributes.autocomplete="off" /> + <Neos.Fusion.Form:Input + field.name="name" + attributes.id="name" + attributes.placeholder={I18n.id('form.name.label').package('Sandstorm.NeosTwoFactorAuthentication')} + attributes.aria-label={I18n.id('form.name.label').package('Sandstorm.NeosTwoFactorAuthentication')} + /> </div> <div class="neos-control-group"> - <Neos.Fusion.Form:Button> + <Neos.Fusion.Form:Button + attributes.data-test-id="create-second-factor-submit-button" + > {I18n.id('module.new.submit-otp').package('Sandstorm.NeosTwoFactorAuthentication').source('Backend').translate()} </Neos.Fusion.Form:Button> </div> diff --git a/Resources/Private/Fusion/Presentation/Components/SecondFactorList.fusion b/Resources/Private/Fusion/Presentation/Components/SecondFactorList.fusion index f7275d2..f3f65a5 100644 --- a/Resources/Private/Fusion/Presentation/Components/SecondFactorList.fusion +++ b/Resources/Private/Fusion/Presentation/Components/SecondFactorList.fusion @@ -5,8 +5,9 @@ prototype(Sandstorm.NeosTwoFactorAuthentication:Component.SecondFactorList) < pr <table class="neos-table"> <thead> <tr> - <th>{I18n.id('module.index.list.header.name').package('Sandstorm.NeosTwoFactorAuthentication').source('Backend').translate()}</th> + <th>{I18n.id('module.index.list.header.username').package('Sandstorm.NeosTwoFactorAuthentication').source('Backend').translate()}</th> <th>{I18n.id('module.index.list.header.type').package('Sandstorm.NeosTwoFactorAuthentication').source('Backend').translate()}</th> + <th>{I18n.id('module.index.list.header.name').package('Sandstorm.NeosTwoFactorAuthentication').source('Backend').translate()}</th> <th>{I18n.id('module.index.list.header.creationDate').package('Sandstorm.NeosTwoFactorAuthentication').source('Backend').translate()}</th> <th> </th> </tr> @@ -43,9 +44,10 @@ prototype(Sandstorm.NeosTwoFactorAuthentication:Component.SecondFactorList.Entry <tr> <td>{props.factorAndPerson.user.name.fullName} ({props.factorAndPerson.secondFactor.account.accountIdentifier})</td> <td>{props.factorAndPerson.secondFactor.typeAsName}</td> + <td>{props.factorAndPerson.secondFactor.name == null ? '-' : props.factorAndPerson.secondFactor.name}</td> <td>{props.factorAndPerson.secondFactor.creationDate == null ? '-' : Date.format(props.factorAndPerson.secondFactor.creationDate, 'Y-m-d H:i')}</td> <td> - <button class="neos-button neos-button-danger" data-toggle="modal" + <button class="neos-button neos-button-danger" data-toggle="modal" data-test-id="delete-second-factor-button" href={'#user-' + props.iterator.index} title={I18n.id('module.index.list.action.delete').package('Sandstorm.NeosTwoFactorAuthentication').source('Backend').translate()} data-neos-toggle="tooltip"> <i class="fas fa-trash-alt icon-white"></i> </button> @@ -71,7 +73,7 @@ prototype(Sandstorm.NeosTwoFactorAuthentication:Component.SecondFactorList.Entry {I18n.id('module.index.delete.cancel').package('Sandstorm.NeosTwoFactorAuthentication').source('Backend').translate()} </a> <Neos.Fusion.Form:Form form.target.action="delete" attributes.class="neos-inline" form.target.arguments={{secondFactor: props.factorAndPerson.secondFactor}}> - <Neos.Fusion.Form:Button attributes.class="neos-button neos-button-danger"> + <Neos.Fusion.Form:Button attributes.class="neos-button neos-button-danger" attributes.data-test-id="confirm-delete"> {I18n.id('module.index.delete.confirm').package('Sandstorm.NeosTwoFactorAuthentication').source('Backend').translate()} </Neos.Fusion.Form:Button> </Neos.Fusion.Form:Form> diff --git a/Resources/Private/Fusion/Presentation/Pages/SetupSecondFactorPage.fusion b/Resources/Private/Fusion/Presentation/Pages/SetupSecondFactorPage.fusion index 33f7c09..dd6259d 100644 --- a/Resources/Private/Fusion/Presentation/Pages/SetupSecondFactorPage.fusion +++ b/Resources/Private/Fusion/Presentation/Pages/SetupSecondFactorPage.fusion @@ -156,6 +156,13 @@ prototype(Sandstorm.NeosTwoFactorAuthentication:Page.SetupSecondFactorPage) < pr attributes.aria-label={I18n.id('otp-placeholder').package('Sandstorm.NeosTwoFactorAuthentication')} attributes.autocomplete="off" /> + <Neos.Fusion.Form:Input + field.name="name" + attributes.id="name" + attributes.placeholder={I18n.id('form.name.label').package('Sandstorm.NeosTwoFactorAuthentication')} + attributes.class="neos-span5" + attributes.aria-label={I18n.id('form.name.label').package('Sandstorm.NeosTwoFactorAuthentication')} + /> </div> <div class="neos-control-group neos-actions"> diff --git a/Resources/Private/Translations/de/Backend.xlf b/Resources/Private/Translations/de/Backend.xlf index 9ba96b8..29881a1 100644 --- a/Resources/Private/Translations/de/Backend.xlf +++ b/Resources/Private/Translations/de/Backend.xlf @@ -15,14 +15,18 @@ <source>List of all registered second factors</source> <target>Liste aller registrierten zweiten Faktoren</target> </trans-unit> - <trans-unit id="module.index.list.header.name" xml:space="preserve"> - <source>Name</source> - <target>Name</target> + <trans-unit id="module.index.list.header.username" xml:space="preserve"> + <source>User</source> + <target>Nutzer</target> </trans-unit> <trans-unit id="module.index.list.header.type" xml:space="preserve"> <source>Type</source> <target>Typ</target> </trans-unit> + <trans-unit id="module.index.list.header.name" xml:space="preserve"> + <source>Name</source> + <target>Name</target> + </trans-unit> <trans-unit id="module.index.list.header.creationDate" xml:space="preserve"> <source>Creation Date</source> <target>Erstellungsdatum</target> diff --git a/Resources/Private/Translations/de/Main.xlf b/Resources/Private/Translations/de/Main.xlf index ece9013..e515f73 100644 --- a/Resources/Private/Translations/de/Main.xlf +++ b/Resources/Private/Translations/de/Main.xlf @@ -34,6 +34,10 @@ <source>Close</source> <target>Schließen</target> </trans-unit> + <trans-unit id="form.name.label" xml:space="preserve"> + <source>Name (optional)</source> + <target>Name (optional)</target> + </trans-unit> </body> </file> </xliff> diff --git a/Resources/Private/Translations/en/Backend.xlf b/Resources/Private/Translations/en/Backend.xlf index 4c0f5e4..e3f2ba1 100644 --- a/Resources/Private/Translations/en/Backend.xlf +++ b/Resources/Private/Translations/en/Backend.xlf @@ -12,12 +12,15 @@ <trans-unit id="module.index.title" xml:space="preserve"> <source>List of all registered second factors</source> </trans-unit> - <trans-unit id="module.index.list.header.name" xml:space="preserve"> - <source>Name</source> + <trans-unit id="module.index.list.header.username" xml:space="preserve"> + <source>User</source> </trans-unit> <trans-unit id="module.index.list.header.type" xml:space="preserve"> <source>Type</source> </trans-unit> + <trans-unit id="module.index.list.header.name" xml:space="preserve"> + <source>Name</source> + </trans-unit> <trans-unit id="module.index.list.header.creationDate" xml:space="preserve"> <source>Creation Date</source> </trans-unit> diff --git a/Resources/Private/Translations/en/Main.xlf b/Resources/Private/Translations/en/Main.xlf index 6a1a249..63654b7 100644 --- a/Resources/Private/Translations/en/Main.xlf +++ b/Resources/Private/Translations/en/Main.xlf @@ -26,6 +26,9 @@ <trans-unit id="form.secret.close" xml:space="preserve"> <source>Close</source> </trans-unit> + <trans-unit id="form.name.label" xml:space="preserve"> + <source>Name (optional)</source> + </trans-unit> </body> </file> </xliff> diff --git a/Tests/E2E/.gitignore b/Tests/E2E/.gitignore new file mode 100644 index 0000000..896c157 --- /dev/null +++ b/Tests/E2E/.gitignore @@ -0,0 +1,6 @@ +# 3rd party sources +node_modules/ + +# transient test files +.features-gen/ +test-results/ diff --git a/Tests/E2E/features/default/backend-module.feature b/Tests/E2E/features/default/backend-module.feature new file mode 100644 index 0000000..6e96e07 --- /dev/null +++ b/Tests/E2E/features/default/backend-module.feature @@ -0,0 +1,46 @@ +@default-context +Feature: Backend module for two-factor authentication management with default settings + + Background: + Given A user with username "admin", password "password" and role "Neos.Neos:Administrator" exists + And A user with username "editor", password "password" and role "Neos.Neos:Editor" exists + + Scenario: Admin user can add a 2FA in backend module + When I log in with username "admin" and password "password" + And I navigate to the 2FA management page + And I add a new TOTP 2FA device with name "Admin Test Device" + Then There should be 1 enrolled 2FA device + And There should be a 2FA device with the name "Admin Test Device" + + Scenario: Admin user can remove his own, last 2FA when 2FA is not enforced + When I log in with username "admin" and password "password" + And I navigate to the 2FA management page + And I add a new TOTP 2FA device with name "Admin Test Device" + And I remove the 2FA device with the name "Admin Test Device" + Then There should be 0 enrolled 2FA devices + And There should be no 2FA device with the name "Admin Test Device" + + Scenario: Editor user can add a 2FA in backend module + When I log in with username "editor" and password "password" + And I navigate to the 2FA management page + And I add a new TOTP 2FA device with name "Editor Test Device" + Then There should be 1 enrolled 2FA device + And There should be a 2FA device with the name "Editor Test Device" + + Scenario: Editor user can remove his own, last 2FA when 2FA is not enforced + When I log in with username "editor" and password "password" + And I navigate to the 2FA management page + And I add a new TOTP 2FA device with name "Editor Test Device" + And I remove the 2FA device with the name "Editor Test Device" + Then There should be 0 enrolled 2FA devices + And There should be no 2FA device with the name "Editor Test Device" + + Scenario: Admin user can remove another user's 2FA when 2FA is not enforced + When I log in with username "editor" and password "password" + And I navigate to the 2FA management page + And I add a new TOTP 2FA device with name "Editor Test Device" + And I log out + And I log in with username "admin" and password "password" + And I navigate to the 2FA management page + And I remove the 2FA device with the name "Editor Test Device" + Then There should be no 2FA device with the name "Editor Test Device" diff --git a/Tests/E2E/features/default/login.feature b/Tests/E2E/features/default/login.feature new file mode 100644 index 0000000..12f8440 --- /dev/null +++ b/Tests/E2E/features/default/login.feature @@ -0,0 +1,31 @@ +@default-context +Feature: Login flow with default settings + + Background: + Given A user with username "admin", password "password" and role "Neos.Neos:Administrator" exists + And A user with username "editor", password "password" and role "Neos.Neos:Editor" exists + + Scenario: Admin user can log in without 2FA when 2FA is not enforced + When I log in with username "admin" and password "password" + Then I should see the Neos content page + + Scenario: Editor user can log in without 2FA when 2FA is not enforced + When I log in with username "editor" and password "password" + Then I should see the Neos content page + + Scenario: User has to enter 2FA code when a TOTP 2FA device is added to his account + When I log in with username "admin" and password "password" + And I navigate to the 2FA management page + And I add a new TOTP 2FA device with name "Admin Test Device" + And I log out + And I log in with username "admin" and password "password" + Then I should see the 2FA verification page + + Scenario: User can log in with 2FA when a TOTP device is added to his account + When I log in with username "admin" and password "password" + And I navigate to the 2FA management page + And I add a new TOTP 2FA device with name "Admin Test Device" + And I log out + And I log in with username "admin" and password "password" + And I enter a valid TOTP for device "Admin Test Device" + Then I should see the Neos content page diff --git a/Tests/E2E/features/enforce-for-all/backend-module.feature b/Tests/E2E/features/enforce-for-all/backend-module.feature new file mode 100644 index 0000000..fb0de4e --- /dev/null +++ b/Tests/E2E/features/enforce-for-all/backend-module.feature @@ -0,0 +1,52 @@ +@enforce-for-all +Feature: Backend module for two-factor authentication management with default settings + + # Requires FLOW_CONTEXT=Production/E2E-SUT/EnforceForAll + # Config: enforceTwoFactorAuthentication: true + + Background: + Given A user with username "admin", password "password" and role "Neos.Neos:Administrator" with enrolled 2FA device with name "Admin Initial Device" exists + And A user with username "editor", password "password" and role "Neos.Neos:Editor" with enrolled 2FA device with name "Editor Initial Device" exists + + Scenario: Admin user can add a 2FA in backend module + When I log in with username "admin" and password "password" + And I enter a valid TOTP for device "Admin Initial Device" + And I navigate to the 2FA management page + And I add a new TOTP 2FA device with name "Admin Test Device" + Then There should be a 2FA device with the name "Admin Test Device" + And There should be a 2FA device with the name "Admin Initial Device" + + Scenario: Admin user can remove a 2FA device when there are more than 1 enrolled devices + When I log in with username "admin" and password "password" + And I enter a valid TOTP for device "Admin Initial Device" + And I navigate to the 2FA management page + And I add a new TOTP 2FA device with name "Admin Test Device" + And I remove the 2FA device with the name "Admin Initial Device" + And There should be a 2FA device with the name "Admin Test Device" + And There should be no 2FA device with the name "Admin Initial Device" + + Scenario: Editor user can add a 2FA in backend module + When I log in with username "editor" and password "password" + And I enter a valid TOTP for device "Editor Initial Device" + And I navigate to the 2FA management page + And I add a new TOTP 2FA device with name "Editor Test Device" + Then There should be 2 enrolled 2FA device + And There should be a 2FA device with the name "Editor Test Device" + And There should be a 2FA device with the name "Editor Initial Device" + + Scenario: Editor user can remove a 2FA device when there are more than 1 enrolled devices + When I log in with username "editor" and password "password" + And I enter a valid TOTP for device "Editor Initial Device" + And I navigate to the 2FA management page + And I add a new TOTP 2FA device with name "Editor Test Device" + And I remove the 2FA device with the name "Editor Initial Device" + Then There should be 1 enrolled 2FA devices + And There should be a 2FA device with the name "Editor Test Device" + And There should be no 2FA device with the name "Editor Initial Device" + + Scenario: Admin user can remove another user's last 2FA, even when 2FA is enforced + When I log in with username "admin" and password "password" + And I enter a valid TOTP for device "Admin Initial Device" + And I navigate to the 2FA management page + And I remove the 2FA device with the name "Editor Initial Device" + Then There should be no 2FA device with the name "Editor Initial Device" diff --git a/Tests/E2E/features/enforce-for-all/login.feature b/Tests/E2E/features/enforce-for-all/login.feature new file mode 100644 index 0000000..27da716 --- /dev/null +++ b/Tests/E2E/features/enforce-for-all/login.feature @@ -0,0 +1,21 @@ +@enforce-for-all +Feature: Login flow with 2FA enforced for all users + + Background: + Given A user with username "admin", password "password" and role "Neos.Neos:Administrator" exists + And A user with username "editor", password "password" and role "Neos.Neos:Editor" exists + + Scenario: Admin has to enter 2FA code when 2FA is enforced for all users, even without a device + When I log in with username "admin" and password "password" + Then I should see the 2FA setup page + And I cannot access the Neos content page + + Scenario: Editor has to enter 2FA code when 2FA is enforced for all users, even without a device + When I log in with username "editor" and password "password" + Then I should see the 2FA setup page + And I cannot access the Neos content page + + Scenario: User can log in when after setting up a 2FA device + When I log in with username "editor" and password "password" + And I set up a 2FA device with name "Editor Test Device" + Then I should see the Neos content page diff --git a/Tests/E2E/global-teardown.ts b/Tests/E2E/global-teardown.ts index 5c9378e..1e66205 100644 --- a/Tests/E2E/global-teardown.ts +++ b/Tests/E2E/global-teardown.ts @@ -1,4 +1,5 @@ import { execSync } from 'node:child_process'; +import { dirname } from 'node:path'; export default async function globalTeardown() { if (process.env.REUSE_SUT) { @@ -8,6 +9,6 @@ export default async function globalTeardown() { const flowContext = process.env.FLOW_CONTEXT || 'Production/E2E-SUT'; execSync( `FLOW_CONTEXT=${flowContext} docker compose -f ../sytem_under_test/${sut}/docker-compose.yaml down -v`, - { stdio: 'inherit', cwd: __dirname } + { stdio: 'inherit', cwd: dirname('.') } ); } diff --git a/Tests/E2E/helpers/pages.ts b/Tests/E2E/helpers/pages.ts index b1db9d4..e5c7308 100644 --- a/Tests/E2E/helpers/pages.ts +++ b/Tests/E2E/helpers/pages.ts @@ -1,8 +1,8 @@ -import { Page, expect } from '@playwright/test'; -import { generateOtp } from './totp'; +import type { Page } from '@playwright/test'; +import { generateOtp } from './totp.js'; export class NeosLoginPage { - constructor(private page: Page) {} + constructor(private readonly page: Page) {} async goto() { await this.page.goto('/neos/login'); @@ -16,7 +16,7 @@ export class NeosLoginPage { } export class SecondFactorLoginPage { - constructor(private page: Page) {} + constructor(private readonly page: Page) {} async waitForPage() { await this.page.waitForURL('**/neos/second-factor-login'); @@ -35,7 +35,7 @@ export class SecondFactorLoginPage { } export class SecondFactorSetupPage { - constructor(private page: Page) {} + constructor(private readonly page: Page) {} async waitForPage() { await this.page.waitForURL('**/neos/second-factor-setup'); @@ -47,7 +47,10 @@ export class SecondFactorSetupPage { return secret; } - async submitOtp(secret: string) { + async submitOtp(secret: string, name?: string) { + if (name) { + await this.page.fill('input#name', name); + } const otp = generateOtp(secret); await this.page.locator('input#secondFactorFromApp').fill(otp); await this.page.locator('button[type="submit"]').click(); @@ -55,7 +58,7 @@ export class SecondFactorSetupPage { } export class BackendModulePage { - constructor(private page: Page) {} + constructor(private readonly page: Page) {} async goto() { await this.page.goto('/neos/management/twoFactorAuthentication'); @@ -74,4 +77,35 @@ export class BackendModulePage { // Confirm in modal await this.page.locator('.neos-modal-footer button.neos-button-danger').click(); } + + /** Navigate to the new-device form, complete setup, and return the TOTP secret. */ + async addDevice(name: string): Promise<string> { + await this.page.goto('/neos/management/twoFactorAuthentication/new'); + + const secretInput = this.page.locator('input#secret'); + const secret = await secretInput.getAttribute('value'); + if (!secret) throw new Error('TOTP secret not found on new-device page'); + + await this.page.fill('input#name', name); + await this.page.fill('input#secondFactorFromApp', generateOtp(secret)); + await this.page.locator('button[data-test-id="create-second-factor-submit-button"]').click(); + + // Wait for redirect back to the index (table appears) + await this.page.locator('.neos-table').waitFor(); + + return secret; + } + + /** Find the table row for the named device and click the delete button, then confirm. */ + async deleteDeviceByName(name: string): Promise<void> { + const row = this.locatorForDeviceRow(name); + await row.locator('button[data-test-id="delete-second-factor-button"]').click(); + await this.page.locator('button[data-test-id="confirm-delete"]:visible').click(); + await this.page.waitForLoadState('networkidle'); + } + + /** Locator for the table row matching a device name (for assertions). */ + locatorForDeviceRow(name: string) { + return this.page.locator('.neos-table tbody tr').filter({ hasText: name }); + } } diff --git a/Tests/E2E/helpers/state.ts b/Tests/E2E/helpers/state.ts new file mode 100644 index 0000000..17a3266 --- /dev/null +++ b/Tests/E2E/helpers/state.ts @@ -0,0 +1,3 @@ +export const state = { + deviceNameSecretMap: new Map<string, string>(), +} diff --git a/Tests/E2E/helpers/system.ts b/Tests/E2E/helpers/system.ts new file mode 100644 index 0000000..23d2011 --- /dev/null +++ b/Tests/E2E/helpers/system.ts @@ -0,0 +1,29 @@ +import { execSync } from 'node:child_process'; +import { dirname } from 'node:path'; +import { type Page } from "@playwright/test"; + +const CONTAINER = `${process.env.SUT || 'neos8'}-neos-1`; + +export function createUser(name: string, password: string, roles: string[]) { + execSync( + `docker exec -u www-data -w /app ${CONTAINER} bash -c "./flow user:create ${name} ${password} Test${name} User${name} --roles ${roles.join(',')}"`, + { stdio: 'inherit', cwd: dirname('.') } + ) +} + +export function removeAllUsers() { + execSync( + `docker exec -u www-data -w /app ${CONTAINER} bash -c "./flow user:delete --assume-yes '*'"`, + { stdio: 'inherit', cwd: dirname('.') } + ) +} + +export function removeAllSecondFactors() { + execSync( + `docker exec -u www-data -w /app ${CONTAINER} bash -c "./flow secondFactor:deleteAll"`, + { stdio: 'inherit', cwd: dirname('.') }) +} + +export async function logout(page: Page) { + await page.context().request.post('/neos/logout'); +} diff --git a/Tests/E2E/helpers/totp.ts b/Tests/E2E/helpers/totp.ts index a970783..4436e28 100644 --- a/Tests/E2E/helpers/totp.ts +++ b/Tests/E2E/helpers/totp.ts @@ -1,5 +1,5 @@ -import { authenticator } from 'otplib'; +import { generateSync } from 'otplib'; export function generateOtp(secret: string): string { - return authenticator.generate(secret); + return generateSync({ secret, strategy: 'totp' }); } diff --git a/Tests/E2E/package-lock.json b/Tests/E2E/package-lock.json index dc9f57c..7d8a918 100644 --- a/Tests/E2E/package-lock.json +++ b/Tests/E2E/package-lock.json @@ -5,18 +5,166 @@ "packages": { "": { "name": "sandstorm-neostwofactorauthentication-e2e", - "devDependencies": { + "dependencies": { "@playwright/test": "^1.58.2", - "@types/node": "^25.5.0", "otplib": "^13.4.0", + "playwright-bdd": "^8.0.0" + }, + "devDependencies": { + "@types/node": "^25.5.0", "typescript": "^6.0.2" } }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cucumber/cucumber-expressions": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@cucumber/cucumber-expressions/-/cucumber-expressions-18.0.1.tgz", + "integrity": "sha512-NSid6bI+7UlgMywl5octojY5NXnxR9uq+JisjOrO52VbFsQM6gTWuQFE8syI10KnIBEdPzuEUSVEeZ0VFzRnZA==", + "license": "MIT", + "dependencies": { + "regexp-match-indices": "1.0.2" + } + }, + "node_modules/@cucumber/gherkin": { + "version": "32.2.0", + "resolved": "https://registry.npmjs.org/@cucumber/gherkin/-/gherkin-32.2.0.tgz", + "integrity": "sha512-X8xuVhSIqlUjxSRifRJ7t0TycVWyX58fygJH3wDNmHINLg9sYEkvQT0SO2G5YlRZnYc11TIFr4YPenscvdlBIw==", + "license": "MIT", + "dependencies": { + "@cucumber/messages": ">=19.1.4 <28" + } + }, + "node_modules/@cucumber/gherkin-utils": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@cucumber/gherkin-utils/-/gherkin-utils-9.2.0.tgz", + "integrity": "sha512-3nmRbG1bUAZP3fAaUBNmqWO0z0OSkykZZotfLjyhc8KWwDSOrOmMJlBTd474lpA8EWh4JFLAX3iXgynBqBvKzw==", + "license": "MIT", + "dependencies": { + "@cucumber/gherkin": "^31.0.0", + "@cucumber/messages": "^27.0.0", + "@teppeis/multimaps": "3.0.0", + "commander": "13.1.0", + "source-map-support": "^0.5.21" + }, + "bin": { + "gherkin-utils": "bin/gherkin-utils" + } + }, + "node_modules/@cucumber/gherkin-utils/node_modules/@cucumber/gherkin": { + "version": "31.0.0", + "resolved": "https://registry.npmjs.org/@cucumber/gherkin/-/gherkin-31.0.0.tgz", + "integrity": "sha512-wlZfdPif7JpBWJdqvHk1Mkr21L5vl4EfxVUOS4JinWGf3FLRV6IKUekBv5bb5VX79fkDcfDvESzcQ8WQc07Wgw==", + "license": "MIT", + "dependencies": { + "@cucumber/messages": ">=19.1.4 <=26" + } + }, + "node_modules/@cucumber/gherkin-utils/node_modules/@cucumber/gherkin/node_modules/@cucumber/messages": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@cucumber/messages/-/messages-26.0.1.tgz", + "integrity": "sha512-DIxSg+ZGariumO+Lq6bn4kOUIUET83A4umrnWmidjGFl8XxkBieUZtsmNbLYgH/gnsmP07EfxxdTr0hOchV1Sg==", + "license": "MIT", + "dependencies": { + "@types/uuid": "10.0.0", + "class-transformer": "0.5.1", + "reflect-metadata": "0.2.2", + "uuid": "10.0.0" + } + }, + "node_modules/@cucumber/gherkin-utils/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@cucumber/html-formatter": { + "version": "21.15.1", + "resolved": "https://registry.npmjs.org/@cucumber/html-formatter/-/html-formatter-21.15.1.tgz", + "integrity": "sha512-tjxEpP161sQ7xc3VREc94v1ymwIckR3ySViy7lTvfi1jUpyqy2Hd/p4oE3YT1kQ9fFDvUflPwu5ugK5mA7BQLA==", + "license": "MIT", + "peerDependencies": { + "@cucumber/messages": ">=18" + } + }, + "node_modules/@cucumber/junit-xml-formatter": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@cucumber/junit-xml-formatter/-/junit-xml-formatter-0.7.1.tgz", + "integrity": "sha512-AzhX+xFE/3zfoYeqkT7DNq68wAQfBcx4Dk9qS/ocXM2v5tBv6eFQ+w8zaSfsktCjYzu4oYRH/jh4USD1CYHfaQ==", + "license": "MIT", + "dependencies": { + "@cucumber/query": "^13.0.2", + "@teppeis/multimaps": "^3.0.0", + "luxon": "^3.5.0", + "xmlbuilder": "^15.1.1" + }, + "peerDependencies": { + "@cucumber/messages": "*" + } + }, + "node_modules/@cucumber/messages": { + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/@cucumber/messages/-/messages-27.2.0.tgz", + "integrity": "sha512-f2o/HqKHgsqzFLdq6fAhfG1FNOQPdBdyMGpKwhb7hZqg0yZtx9BVqkTyuoNk83Fcvk3wjMVfouFXXHNEk4nddA==", + "license": "MIT", + "dependencies": { + "@types/uuid": "10.0.0", + "class-transformer": "0.5.1", + "reflect-metadata": "0.2.2", + "uuid": "11.0.5" + } + }, + "node_modules/@cucumber/messages/node_modules/uuid": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz", + "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/@cucumber/query": { + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/@cucumber/query/-/query-13.6.0.tgz", + "integrity": "sha512-tiDneuD5MoWsJ9VKPBmQok31mSX9Ybl+U4wqDoXeZgsXHDURqzM3rnpWVV3bC34y9W6vuFxrlwF/m7HdOxwqRw==", + "license": "MIT", + "dependencies": { + "@teppeis/multimaps": "3.0.0", + "lodash.sortby": "^4.7.0" + }, + "peerDependencies": { + "@cucumber/messages": "*" + } + }, + "node_modules/@cucumber/tag-expressions": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@cucumber/tag-expressions/-/tag-expressions-6.2.0.tgz", + "integrity": "sha512-KIF0eLcafHbWOuSDWFw0lMmgJOLdDRWjEL1kfXEWrqHmx2119HxVAr35WuEd9z542d3Yyg+XNqSr+81rIKqEdg==", + "license": "MIT" + }, "node_modules/@noble/hashes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -25,18 +173,51 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@otplib/core": { "version": "13.4.0", "resolved": "https://registry.npmjs.org/@otplib/core/-/core-13.4.0.tgz", "integrity": "sha512-JqOGcvZQi2wIkEQo8f3/iAjstavpXy6gouIDMHygjNuH6Q0FjbHOiXMdcE94RwfgDNMABhzwUmvaPsxvgm9NYw==", - "dev": true, "license": "MIT" }, "node_modules/@otplib/hotp": { "version": "13.4.0", "resolved": "https://registry.npmjs.org/@otplib/hotp/-/hotp-13.4.0.tgz", "integrity": "sha512-MJjE0x06mn2ptymz5qZmQveb+vWFuaIftqE0b5/TZZqUOK7l97cV8lRTmid5BpAQMwJDNLW6RnYxGeCRiNdekw==", - "dev": true, "license": "MIT", "dependencies": { "@otplib/core": "13.4.0", @@ -47,7 +228,6 @@ "version": "13.4.0", "resolved": "https://registry.npmjs.org/@otplib/plugin-base32-scure/-/plugin-base32-scure-13.4.0.tgz", "integrity": "sha512-/t9YWJmMbB8bF5z8mXrBZc2FXBe8B/3hG5FhWr9K8cFwFhyxScbPysmZe8s1UTzSA6N+s8Uv8aIfCtVXPNjJWw==", - "dev": true, "license": "MIT", "dependencies": { "@otplib/core": "13.4.0", @@ -58,7 +238,6 @@ "version": "13.4.0", "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto-noble/-/plugin-crypto-noble-13.4.0.tgz", "integrity": "sha512-KrvE4m7Zv+TT1944HzgqFJWJpKb6AyoxDbvhPStmBqdMlv5Gekb80d66cuFRL08kkPgJ5gXUSb5SFpYeB+bACg==", - "dev": true, "license": "MIT", "dependencies": { "@noble/hashes": "^2.0.1", @@ -69,7 +248,6 @@ "version": "13.4.0", "resolved": "https://registry.npmjs.org/@otplib/totp/-/totp-13.4.0.tgz", "integrity": "sha512-dK+vl0f0ekzf6mCENRI9AKS2NJUC7OjI3+X8e7QSnhQ2WM7I+i4PGpb3QxKi5hxjTtwVuoZwXR2CFtXdcRtNdQ==", - "dev": true, "license": "MIT", "dependencies": { "@otplib/core": "13.4.0", @@ -81,7 +259,6 @@ "version": "13.4.0", "resolved": "https://registry.npmjs.org/@otplib/uri/-/uri-13.4.0.tgz", "integrity": "sha512-x1ozBa5bPbdZCrrTL/HK21qchiK7jYElTu+0ft22abeEhiLYgH1+SIULvOcVk3CK8YwF4kdcidvkq4ciejucJA==", - "dev": true, "license": "MIT", "dependencies": { "@otplib/core": "13.4.0" @@ -91,7 +268,6 @@ "version": "1.58.2", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", - "dev": true, "license": "Apache-2.0", "dependencies": { "playwright": "1.58.2" @@ -107,12 +283,20 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.0.0.tgz", "integrity": "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==", - "dev": true, "license": "MIT", "funding": { "url": "https://paulmillr.com/funding/" } }, + "node_modules/@teppeis/multimaps": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@teppeis/multimaps/-/multimaps-3.0.0.tgz", + "integrity": "sha512-ID7fosbc50TbT0MK0EG12O+gAP3W3Aa/Pz4DaTtQtEvlc9Odaqi0de+xuZ7Li2GtK4HzEX7IuRWS/JmZLksR3Q==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/@types/node": { "version": "25.5.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", @@ -123,11 +307,116 @@ "undici-types": "~7.18.0" } }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "license": "MIT" + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -138,11 +427,123 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "license": "MIT" + }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/otplib": { "version": "13.4.0", "resolved": "https://registry.npmjs.org/otplib/-/otplib-13.4.0.tgz", "integrity": "sha512-RUcYcRMCgRWhUE/XabRppXpUwCwaWBNHe5iPXhdvP8wwDGpGpsIf/kxX/ec3zFsOaM1Oq8lEhUqDwk6W7DHkwg==", - "dev": true, "license": "MIT", "dependencies": { "@otplib/core": "13.4.0", @@ -153,11 +554,22 @@ "@otplib/uri": "13.4.0" } }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/playwright": { "version": "1.58.2", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", - "dev": true, "license": "Apache-2.0", "dependencies": { "playwright-core": "1.58.2" @@ -172,11 +584,42 @@ "fsevents": "2.3.2" } }, + "node_modules/playwright-bdd": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/playwright-bdd/-/playwright-bdd-8.5.0.tgz", + "integrity": "sha512-w/Bd5C1d6Xe5e1oREsbt2rDN0/Mcp+J2OjQwSl49/mroa2K6UZU33P7v91pLQPl1otDitMwJOaOeJsqaj7WU7w==", + "license": "MIT", + "dependencies": { + "@cucumber/cucumber-expressions": "18.0.1", + "@cucumber/gherkin": "^32.1.2", + "@cucumber/gherkin-utils": "^9.2.0", + "@cucumber/html-formatter": "^21.11.0", + "@cucumber/junit-xml-formatter": "^0.7.1", + "@cucumber/messages": "^27.2.0", + "@cucumber/tag-expressions": "^6.2.0", + "cli-table3": "0.6.5", + "commander": "^13.1.0", + "fast-glob": "^3.3.3", + "mime-types": "^3.0.2", + "xmlbuilder": "15.1.1" + }, + "bin": { + "bddgen": "dist/cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/vitalets" + }, + "peerDependencies": { + "@playwright/test": ">=1.44" + } + }, "node_modules/playwright-core": { "version": "1.58.2", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", - "dev": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -185,6 +628,140 @@ "node": ">=18" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/regexp-match-indices": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regexp-match-indices/-/regexp-match-indices-1.0.2.tgz", + "integrity": "sha512-DwZuAkt8NF5mKwGGER1EGh2PRqyvhRhhLviH+R8y8dIuaQROlUfXjt4s9ZTXstIsSkptf06BSvwcEmmfheJJWQ==", + "license": "Apache-2.0", + "dependencies": { + "regexp-tree": "^0.1.11" + } + }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "license": "MIT", + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/typescript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", @@ -205,6 +782,15 @@ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "license": "MIT", + "engines": { + "node": ">=8.0" + } } } } diff --git a/Tests/E2E/package.json b/Tests/E2E/package.json index 5a4cb43..d21652e 100644 --- a/Tests/E2E/package.json +++ b/Tests/E2E/package.json @@ -1,23 +1,28 @@ { "name": "sandstorm-neostwofactorauthentication-e2e", "private": true, + "type": "module", "scripts": { - "test:neos8": "SUT=neos8 npx playwright test --headed", - "test:neos8:enforce-all": "SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForAll npx playwright test tests/enforce-for-all.spec.ts", - "test:neos8:enforce-role": "SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole npx playwright test tests/enforce-for-role.spec.ts", - "test:neos8:enforce-provider": "SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider npx playwright test tests/enforce-for-provider.spec.ts", - "test:neos8:issuer-name": "SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/IssuerNameChange npx playwright test tests/issuer-name-change.spec.ts", - "test:neos9": "SUT=neos9 npx playwright test", - "test:neos9:enforce-all": "SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForAll npx playwright test tests/enforce-for-all.spec.ts", - "test:neos9:enforce-role": "SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole npx playwright test tests/enforce-for-role.spec.ts", - "test:neos9:enforce-provider": "SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider npx playwright test tests/enforce-for-provider.spec.ts", - "test:neos9:issuer-name": "SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/IssuerNameChange npx playwright test tests/issuer-name-change.spec.ts", - "test": "npm run test:neos8 && npm run test:neos8:enforce-all && npm run test:neos8:enforce-role && npm run test:neos8:enforce-provider && npm run test:neos8:issuer-name && npm run test:neos9 && npm run test:neos9:enforce-all && npm run test:neos9:enforce-role && npm run test:neos9:enforce-provider && npm run test:neos9:issuer-name" + "generate-tests": "npx bddgen", + "test:neos8:defaults": "npm run generate-tests; SUT=neos8 npx playwright test --grep @default-context", + "test:neos8:enforce-all": "npm run generate-tests; SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForAll npx playwright test --grep @enforce-for-all", + "test:neos8:enforce-role": "npm run generate-tests; SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole npx playwright test --grep @enforce-for-role", + "test:neos8:enforce-provider": "npm run generate-tests; SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider npx playwright test --grep @enforce-for-provider", + "test:neos8:issuer-name": "npm run generate-tests; SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/IssuerNameChange npx playwright test --grep @issuer-name-change", + "test:neos9:defaults": "npm run generate-tests; SUT=neos9 npx playwright test --grep @default-context", + "test:neos9:enforce-all": "npm run generate-tests; SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForAll npx playwright test --grep @enforce-for-all", + "test:neos9:enforce-role": "npm run generate-tests; SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole npx playwright test --grep @enforce-for-role", + "test:neos9:enforce-provider": "npm run generate-tests; SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider npx playwright test --grep @enforce-for-provider", + "test:neos9:issuer-name": "npm run generate-tests; SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/IssuerNameChange npx playwright test --grep @issuer-name-change", + "test": "npm run generate-tests; npm run test:neos8 && npm run test:neos8:enforce-all && npm run test:neos8:enforce-role && npm run test:neos8:enforce-provider && npm run test:neos8:issuer-name && npm run test:neos9 && npm run test:neos9:enforce-all && npm run test:neos9:enforce-role && npm run test:neos9:enforce-provider && npm run test:neos9:issuer-name" }, "devDependencies": { - "@playwright/test": "^1.58.2", "@types/node": "^25.5.0", - "otplib": "^13.4.0", "typescript": "^6.0.2" + }, + "dependencies": { + "@playwright/test": "^1.58.2", + "playwright-bdd": "^8.0.0", + "otplib": "^13.4.0" } } diff --git a/Tests/E2E/playwright.config.ts b/Tests/E2E/playwright.config.ts index 1f27434..ba52d5a 100644 --- a/Tests/E2E/playwright.config.ts +++ b/Tests/E2E/playwright.config.ts @@ -1,12 +1,24 @@ import { defineConfig, devices } from '@playwright/test'; +import { defineBddConfig } from 'playwright-bdd'; const SUT = process.env.SUT || 'neos8'; const FLOW_CONTEXT = process.env.FLOW_CONTEXT || 'Production/E2E-SUT'; const HEADLESS = process.env.HEADLESS !== 'false'; +const REUSE_SUT = process.env.REUSE_SUT == null ? true : !!process.env.REUSE_SUT; const sutDir = `../sytem_under_test/${SUT}`; +console.log('### Loading config with env') +console.table({ + SUT, FLOW_CONTEXT, HEADLESS, REUSE_SUT +}) + +const testDir = defineBddConfig({ + features: 'features/**/*.feature', + steps: 'steps/**/*.ts', +}); + export default defineConfig({ - testDir: './tests', + testDir, fullyParallel: false, workers: 1, retries: 0, @@ -14,13 +26,14 @@ export default defineConfig({ baseURL: 'http://localhost:8081', headless: HEADLESS, trace: 'on-first-retry', + screenshot: "only-on-failure", }, globalTeardown: './global-teardown.ts', webServer: { command: `FLOW_CONTEXT=${FLOW_CONTEXT} docker compose -f ${sutDir}/docker-compose.yaml up --build`, - url: 'http://localhost:8081/neos/login', - reuseExistingServer: !!process.env.REUSE_SUT, - timeout: 600_000, + url: 'http://localhost:8081/', + reuseExistingServer: REUSE_SUT, + timeout: 120_000, stdout: 'pipe', stderr: 'pipe', }, diff --git a/Tests/E2E/steps/backend-module.steps.ts b/Tests/E2E/steps/backend-module.steps.ts new file mode 100644 index 0000000..e6d8129 --- /dev/null +++ b/Tests/E2E/steps/backend-module.steps.ts @@ -0,0 +1,49 @@ +import { expect } from '@playwright/test'; +import { createBdd } from 'playwright-bdd'; +import { BackendModulePage } from '../helpers/pages.js'; +import { state } from '../helpers/state.js'; + +const { When, Then } = createBdd(); + +When('I navigate to the 2FA management page', async ({ page }) => { + const modulePage = new BackendModulePage(page); + await modulePage.goto(); + await modulePage.waitForPage(); +}); + +When('I add a new TOTP 2FA device with name {string}', + async ({ page }, deviceName: string) => { + const modulePage = new BackendModulePage(page); + const secret = await modulePage.addDevice(deviceName); + + state.deviceNameSecretMap.set(deviceName, secret); + }, +); + +// "with name" and "with the name" are both used in feature files +When('I remove the 2FA device with the name {string}', + async ({ page }, name: string) => { + const modulePage = new BackendModulePage(page); + await modulePage.deleteDeviceByName(name); + }, +); + +Then('There should be {int} enrolled 2FA device(s)', + async ({ page }, countStr: string) => { + await expect(page.locator('.neos-table tbody tr')).toHaveCount(parseInt(countStr, 10)); + }, +); + +Then('There should be a 2FA device with the name {string}', + async ({ page }, name: string) => { + const modulePage = new BackendModulePage(page); + await expect(modulePage.locatorForDeviceRow(name)).toBeVisible(); + }, +); + +Then('There should be no 2FA device with the name {string}', + async ({ page }, name: string) => { + const modulePage = new BackendModulePage(page); + await expect(modulePage.locatorForDeviceRow(name)).toHaveCount(0); + }, +); diff --git a/Tests/E2E/steps/hooks.ts b/Tests/E2E/steps/hooks.ts new file mode 100644 index 0000000..e5fabae --- /dev/null +++ b/Tests/E2E/steps/hooks.ts @@ -0,0 +1,16 @@ +import { expect } from '@playwright/test'; +import { createBdd } from 'playwright-bdd'; +import { state } from "../helpers/state.ts"; +import { logout, removeAllUsers } from "../helpers/system.ts"; + +const { AfterScenario } = createBdd(); + +// reset db after each scenario +AfterScenario(async ({ page }) => { + await logout(page) + + // clear state + state.deviceNameSecretMap.clear(); + + removeAllUsers(); +}); diff --git a/Tests/E2E/steps/login.steps.ts b/Tests/E2E/steps/login.steps.ts new file mode 100644 index 0000000..77af921 --- /dev/null +++ b/Tests/E2E/steps/login.steps.ts @@ -0,0 +1,104 @@ +import { expect } from '@playwright/test'; +import { createBdd } from 'playwright-bdd'; +import { BackendModulePage, NeosLoginPage, SecondFactorLoginPage, SecondFactorSetupPage } from '../helpers/pages.ts'; +import { generateOtp } from '../helpers/totp.ts'; +import { createUser, logout } from '../helpers/system.ts'; +import { state } from "../helpers/state.ts"; + +const { Given, When, Then } = createBdd(); + +// ── Background / Given ──────────────────────────────────────────────────────── + +Given('A user with username {string}, password {string} and role {string} exists', + async ({}, username: string, password: string, role: string) => { + createUser(username, password, [role]); + }, +); + +Given('A user with username {string}, password {string} and role {string} with enrolled 2FA device with name {string} exists', + async ({ page }, username: string, password: string, role: string, deviceName: string) => { + createUser(username, password, [role]); + + const loginPage = new NeosLoginPage(page); + await loginPage.goto(); + await loginPage.login(username, password); + await page.waitForLoadState('networkidle'); + + let secret: string; + + if (page.url().includes('second-factor-setup')) { + const setupPage = new SecondFactorSetupPage(page); + secret = await setupPage.getSecret(); + await setupPage.submitOtp(secret, deviceName); + await page.waitForLoadState('networkidle'); + } else { + const modulePage = new BackendModulePage(page); + await modulePage.goto(); + await modulePage.waitForPage(); + secret = await modulePage.addDevice(deviceName); + } + + state.deviceNameSecretMap.set(deviceName, secret); + + await logout(page); + }, +); + +// ── When ────────────────────────────────────────────────────────────────────── + +When('I log in with username {string} and password {string}', + async ({ page }, username: string, password: string) => { + const loginPage = new NeosLoginPage(page); + await loginPage.goto(); + await loginPage.login(username, password); + await page.waitForLoadState('networkidle'); + }, +); + +When('I log out', async ({ page }) => { + await page.pause(); + await logout(page); +}); + +When('I set up a 2FA device with name {string}', async ({ page }, deviceName: string) => { + const setupPage = new SecondFactorSetupPage(page); + await setupPage.waitForPage(); + const secret = await setupPage.getSecret(); + await setupPage.submitOtp(secret, deviceName); + + state.deviceNameSecretMap.set(deviceName, secret); + + await page.waitForLoadState('networkidle'); +}); + +When('I enter a valid TOTP for device {string}', async ({ page }, deviceName: string) => { + const secret = state.deviceNameSecretMap.get(deviceName); + if (!secret) throw new Error(`No enrolled TOTP secret found for device "${deviceName}"`); + + const otpPage = new SecondFactorLoginPage(page); + await otpPage.waitForPage(); + await otpPage.enterOtp(generateOtp(secret)); + + await page.waitForLoadState('networkidle'); +}); + +// ── Then ────────────────────────────────────────────────────────────────────── + +Then('I should see the Neos content page', async ({ page }) => { + await expect(page).not.toHaveURL(/neos\/login/); + await expect(page).not.toHaveURL(/second-factor/); +}); + +Then('I should see the 2FA verification page', async ({ page }) => { + await expect(page).toHaveURL(/second-factor-login/); +}); + +Then('I should see the 2FA setup page', async ({ page }) => { + await expect(page).toHaveURL(/second-factor-setup/); +}); + +Then('I cannot access the Neos content page', async ({ page }) => { + await page.goto('/neos/content'); + + await expect(page).not.toHaveURL(/neos\/content/); +}); diff --git a/Tests/E2E/test-results/.last-run.json b/Tests/E2E/test-results/.last-run.json deleted file mode 100644 index 738842c..0000000 --- a/Tests/E2E/test-results/.last-run.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "status": "failed", - "failedTests": [ - "f3be858742d2a3b00c66-d143c8f7852f60650513", - "5f59ffc4d5796e3b8b4e-3d928260d0e69b8582bf", - "5f59ffc4d5796e3b8b4e-f5fb17f37c442b4e0719", - "d891987aa24c5b614103-45830fca0237deef430e", - "d891987aa24c5b614103-a5b74a87c56b90155e44", - "6a4a1a14e2f074902f6e-b6275a7c4cf69acc0089", - "871a0b6f817d5ed73bdd-5af3010376f9d40029af", - "498ffde57e01a71f086f-d2349f8fa3fc72faa303", - "c90b11560d4b8aa219dc-e07dbcf29cd8b53b3353" - ] -} \ No newline at end of file diff --git a/Tests/E2E/test-results/backend-module-backend-module-shows-enrolled-2FA-devices-chromium/error-context.md b/Tests/E2E/test-results/backend-module-backend-module-shows-enrolled-2FA-devices-chromium/error-context.md deleted file mode 100644 index d21e3c5..0000000 --- a/Tests/E2E/test-results/backend-module-backend-module-shows-enrolled-2FA-devices-chromium/error-context.md +++ /dev/null @@ -1,25 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e2]: - - main [ref=e3]: - - generic [ref=e4]: - - figure [ref=e5]: - - img "Neos Logo" [ref=e6] - - heading "Login to Neos Demo Site" [level=1] [ref=e7]: - - text: Login to - - strong [ref=e8]: Neos Demo Site - - generic [ref=e10]: - - img [ref=e12] - - button "Show Code" [ref=e15] [cursor=pointer] - - textbox "OTP from App" [ref=e17] - - button "Register new OTP" [ref=e19] [cursor=pointer] - - contentinfo [ref=e20]: - - paragraph [ref=e21]: - - link "Neos" [ref=e22] [cursor=pointer]: - - /url: http://neos.io - - text: – © 2006-2026 This is free software, licensed under GPL3 or higher, and you are welcome to redistribute it under certain conditions; Neos comes with ABSOLUTELY NO WARRANTY; See - - link "neos.io" [ref=e23] [cursor=pointer]: - - /url: http://neos.io - - text: for more details. Obstructing the appearance of this notice is prohibited by law. -``` \ No newline at end of file diff --git a/Tests/E2E/test-results/enforce-for-all-administrator-is-redirected-to-2FA-setup-chromium/error-context.md b/Tests/E2E/test-results/enforce-for-all-administrator-is-redirected-to-2FA-setup-chromium/error-context.md deleted file mode 100644 index 018279c..0000000 --- a/Tests/E2E/test-results/enforce-for-all-administrator-is-redirected-to-2FA-setup-chromium/error-context.md +++ /dev/null @@ -1,327 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e4]: - - generic [ref=e5]: - - generic [ref=e6]: - - button "Menu" [ref=e7] [cursor=pointer] - - img [ref=e11] - - generic [ref=e16]: - - button "In-Place" [ref=e19] [cursor=pointer]: - - img - - generic [ref=e20]: In-Place - - img - - button "English (US)" [ref=e23] [cursor=pointer]: - - button "English (US)" [ref=e26]: - - generic [ref=e27]: - - img - - link "English (US)" [ref=e28]: - - /url: http://localhost:8081/neos/content?node=%2Fsites%2Fneosdemo%3Blanguage%3Den_US - - img - - button "E2E Admin" [ref=e31] [cursor=pointer]: - - img - - text: E2E Admin - - img - - generic [ref=e32]: - - button "Published - live" [disabled] [ref=e33] - - button [ref=e34] [cursor=pointer]: - - img - - generic [ref=e36]: - - button "Display Keyboard Shortcuts" [ref=e37] [cursor=pointer]: - - img [ref=e38] - - link "Show preview" [ref=e40] [cursor=pointer]: - - /url: http://localhost:8081/neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eadmin%3Blanguage%3Den_US - - img [ref=e41] - - button "Activate Fullscreen edit mode" [ref=e43] [cursor=pointer]: - - img [ref=e44] - - iframe [ref=e48]: - - generic [active] [ref=f1e1]: - - banner [ref=f1e2]: - - generic [ref=f1e3]: - - link [ref=f1e4] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eadmin%3Blanguage%3Den_US - - img [ref=f1e5] - - navigation - - button "Open menu" [ref=f1e11] [cursor=pointer]: - - generic [ref=f1e12]: Open menu - - img [ref=f1e13] - - navigation [ref=f1e15]: - - button "English (US)" [ref=f1e16] [cursor=pointer]: - - text: English (US) - - img [ref=f1e17] - - heading "Welcome to the Neos CMS demo" [level=1] [ref=f1e22]: - - textbox "Rich Text Editor, main" [ref=f1e23] - - main [ref=f1e24]: - - generic [ref=f1e25]: - - generic [ref=f1e26]: - - generic [ref=f1e27]: - - heading "Imagine this..." [level=2] [ref=f1e28]: - - textbox "Rich Text Editor, main" [ref=f1e29] - - textbox "Rich Text Editor, main" [ref=f1e31]: - - paragraph [ref=f1e32]: In only a few years from now, the world will have entirely new cities. Cities where development has not even started. Cities that can draw from all we know about liveability and functionality. - - paragraph [ref=f1e33]: "The same goes for websites. Major websites will be built with the tools we’re constructing today. But we don't know how they will be like. Just think of how different sites were 3 years ago. For both cities and websites, one basic rule counts: Things are constantly changing and can never be fully anticipated." - - paragraph [ref=f1e34]: The inspiration from urban planning permeates our UX master plan, giving us crucial guidelines for structuring everything we know - and making room for all the future change we don’t know. - - generic [ref=f1e35]: - - heading "Power when you need it" [level=2] [ref=f1e36]: - - textbox "Rich Text Editor, main" [ref=f1e37] - - textbox "Rich Text Editor, main" [ref=f1e39]: - - paragraph [ref=f1e40]: The distribution of power in a city makes sure that everyone gets what they need. You don’t power a shed in the way you power a nuclear plant. The ultra-simple editing makes editors center their attention on content. If you need to add SEO data next to the content, Neos has an inspector that opens up for that kind of functionality. Just a bit more power, one step at a time... - - paragraph [ref=f1e41]: If you need more, the inspector will be expandable to the full backend screen providing the user with all the options. That’s not enough? Create custom backend modules. And if even that's not enough, underlying Neos, we have Flow - a first-class web framework that enables you to build web applications that integrate with your other systems. - - generic [ref=f1e42]: - - generic [ref=f1e43]: - - heading "Create an account and access the backend" [level=2] [ref=f1e44]: - - textbox "Rich Text Editor, main" [ref=f1e45]: - - generic [ref=f1e46]: Create an account and access the backend - - textbox "Rich Text Editor, main" [ref=f1e48]: - - paragraph [ref=f1e49]: Try out our latest development of the Neos user interface. Get a first impression of CKEditor, the content editor, and try out the simple publishing mechanism based on workspaces. - - paragraph [ref=f1e50]: The registration form is also an example of a "Plugin"-element. - - generic [ref=f1e53]: - - group [ref=f1e54]: - - generic [ref=f1e55]: - - generic [ref=f1e56]: Username - - textbox "Username" [ref=f1e57] - - generic [ref=f1e58]: (lowercase letters and numbers only) - - generic [ref=f1e59]: - - generic [ref=f1e60]: Password - - textbox "Password" [ref=f1e61] - - button "Submit" [ref=f1e62] [cursor=pointer] - - contentinfo [ref=f1e64]: - - navigation [ref=f1e65]: - - link "Contribute" [ref=f1e66] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fcontribute%40user-e2eadmin%3Blanguage%3Den_US - - link "Documentation & Support" [ref=f1e67] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdocumentation%40user-e2eadmin%3Blanguage%3Den_US - - link "Download" [ref=f1e68] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdownload%40user-e2eadmin%3Blanguage%3Den_US - - textbox "Rich Text Editor, main" [ref=f1e72]: - - paragraph [ref=f1e73]: Powered by Neos & Flow - - generic [ref=e53]: "UI version: 8.4.3" - - button "Document Tree" [ref=e54] [cursor=pointer]: - - button "Navigate" [ref=e55]: - - img [ref=e56] - - text: Document Tree - - generic [ref=e58]: - - generic [ref=e59]: - - generic [ref=e62]: - - button "Create new" [ref=e63] [cursor=pointer]: - - img [ref=e64] - - button "Hide / Unhide" [disabled] [ref=e66]: - - img [ref=e67] - - button "Copy" [ref=e69] [cursor=pointer]: - - img [ref=e70] - - button "Cut" [disabled] [ref=e72]: - - img [ref=e73] - - button "Paste" [disabled] [ref=e75]: - - img [ref=e76] - - button "Delete" [disabled] [ref=e78]: - - img [ref=e79] - - button "Refresh" [ref=e81] [cursor=pointer]: - - img [ref=e82] - - button [ref=e84] [cursor=pointer]: - - img [ref=e85] - - tree [ref=e87]: - - treeitem "Home" [ref=e88]: - - button "Collapse All" [ref=e89] [cursor=pointer]: - - img [ref=e90] - - treeitem "Home" [expanded] [ref=e92]: - - generic [ref=e94]: - - button "Home" [ref=e96] [cursor=pointer]: - - generic [ref=e97]: - - img [ref=e99] - - treeitem "Home" [ref=e101] - - button [ref=e102] [cursor=pointer]: - - img [ref=e103] - - generic [ref=e105]: - - treeitem "Home" [expanded] [ref=e106]: - - button "Home" [ref=e110] [cursor=pointer]: - - generic [ref=e111]: - - img [ref=e113] - - treeitem "Home" [ref=e115] - - treeitem "Features" [expanded] [ref=e116]: - - generic [ref=e118]: - - button "Features" [ref=e120] [cursor=pointer]: - - generic [ref=e121]: - - img [ref=e123] - - treeitem "Features" [ref=e125] - - button [ref=e126] [cursor=pointer]: - - img [ref=e127] - - generic [ref=e129]: - - treeitem "Multiple columns" [ref=e130]: - - button "Multiple columns" [ref=e134] [cursor=pointer]: - - generic [ref=e135]: - - img [ref=e137] - - treeitem "Multiple columns" [ref=e139] - - treeitem "Text & images" [ref=e140]: - - button "Text & images" [ref=e144] [cursor=pointer]: - - generic [ref=e145]: - - img [ref=e147] - - treeitem "Text & images" [ref=e149] - - treeitem "Navigation elements" [ref=e150]: - - generic [ref=e152]: - - button "Navigation elements" [ref=e154] [cursor=pointer]: - - generic [ref=e155]: - - img [ref=e157] - - treeitem "Navigation elements" [ref=e159] - - button [ref=e160] [cursor=pointer]: - - img [ref=e161] - - treeitem "Custom elements" [ref=e163]: - - button "Custom elements" [ref=e167] [cursor=pointer]: - - generic [ref=e168]: - - img [ref=e170] - - treeitem "Custom elements" [ref=e172] - - treeitem "Other elements" [ref=e173]: - - button "Other elements" [ref=e177] [cursor=pointer]: - - generic [ref=e178]: - - img [ref=e180] - - treeitem "Other elements" [ref=e182] - - treeitem "Forms" [ref=e183]: - - button "Forms" [ref=e187] [cursor=pointer]: - - generic [ref=e188]: - - img [ref=e190] - - treeitem "Forms" [ref=e192] - - treeitem "Blog" [expanded] [ref=e194]: - - generic [ref=e196]: - - button "Blog" [ref=e198] [cursor=pointer]: - - generic [ref=e199]: - - img [ref=e201] - - treeitem "Blog" [ref=e203] - - button [ref=e204] [cursor=pointer]: - - img [ref=e205] - - generic [ref=e207]: - - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e208]: - - button "Neos 8.0 \"Mad Hatter\" released" [ref=e212] [cursor=pointer]: - - generic [ref=e213]: - - img [ref=e215] - - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e217] - - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e218]: - - button "Neos 8.1 \"Frog–Footman\" released" [ref=e222] [cursor=pointer]: - - generic [ref=e223]: - - img [ref=e225] - - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e227] - - treeitem "Neos 8.2 released" [ref=e228]: - - button "Neos 8.2 released" [ref=e232] [cursor=pointer]: - - generic [ref=e233]: - - img [ref=e235] - - treeitem "Neos 8.2 released" [ref=e237] - - treeitem "Neos 8.3 released" [ref=e238]: - - button "Neos 8.3 released" [ref=e242] [cursor=pointer]: - - generic [ref=e243]: - - img [ref=e245] - - treeitem "Neos 8.3 released" [ref=e247] - - treeitem "Neos 8.4 released" [ref=e248]: - - button "Neos 8.4 released" [ref=e252] [cursor=pointer]: - - generic [ref=e253]: - - img [ref=e255] - - treeitem "Neos 8.4 released" [ref=e257] - - treeitem "Download" [expanded] [ref=e259]: - - button "Download" [ref=e263] [cursor=pointer]: - - generic [ref=e264]: - - img [ref=e266] - - treeitem "Download" [ref=e268] - - treeitem "Contribute" [expanded] [ref=e269]: - - button "Contribute" [ref=e273] [cursor=pointer]: - - generic [ref=e274]: - - img [ref=e276] - - treeitem "Contribute" [ref=e278] - - treeitem "Documentation & Support" [expanded] [ref=e279]: - - button "Documentation & Support" [ref=e283] [cursor=pointer]: - - generic [ref=e284]: - - img [ref=e286] - - treeitem "Documentation & Support" [ref=e288] - - treeitem "Not Found" [expanded] [ref=e289]: - - button "Not Found" [ref=e293] [cursor=pointer]: - - generic [ref=e294]: - - img [ref=e296] - - treeitem "Not Found" [ref=e298] - - generic [ref=e301]: - - button "Toggle content tree Content Tree" [ref=e303] [cursor=pointer]: - - button "Toggle content tree" [ref=e304]: - - img [ref=e305] - - text: Content Tree - - generic [ref=e308]: - - button "Create new" [ref=e309] [cursor=pointer]: - - img [ref=e310] - - button "Hide / Unhide" [ref=e312] [cursor=pointer]: - - img [ref=e313] - - button "Copy" [ref=e315] [cursor=pointer]: - - img [ref=e316] - - button "Cut" [ref=e318] [cursor=pointer]: - - img [ref=e319] - - button "Paste" [disabled] [ref=e321]: - - img [ref=e322] - - button "Delete" [ref=e324] [cursor=pointer]: - - img [ref=e325] - - button "Refresh" [ref=e327] [cursor=pointer]: - - img [ref=e328] - - form [ref=e330]: - - button "Toggle inspector" [ref=e331] [cursor=pointer]: - - img [ref=e332] - - generic [ref=e335]: - - generic [ref=e336]: - - heading "Selected element" [level=1] [ref=e337]: - - generic [ref=e339]: Selected element - - button "Home" [ref=e342] [cursor=pointer]: - - button "Home" [ref=e345]: - - generic [ref=e346]: - - img - - text: Home - - img - - tablist [ref=e347]: - - list [ref=e348]: - - tab "General" [selected] [ref=e349] [cursor=pointer]: - - img [ref=e350] - - tab "Site configuration" [ref=e352] [cursor=pointer]: - - img [ref=e353] - - tab "Metadata" [ref=e355] [cursor=pointer]: - - img [ref=e356] - - tab "SEO" [ref=e358] [cursor=pointer]: - - img [ref=e359] - - tabpanel [ref=e362]: - - generic [ref=e363]: - - generic [ref=e364]: - - button "Document" [expanded] [ref=e365]: - - heading "Document" [level=2] [ref=e366] [cursor=pointer]: - - img [ref=e368] - - text: Document - - button [ref=e370] [cursor=pointer]: - - img [ref=e371] - - generic [ref=e375]: - - generic [ref=e376]: - - generic [ref=e379] [cursor=pointer]: Title - - textbox "Title" [ref=e381]: Home - - generic [ref=e382]: - - generic [ref=e385] [cursor=pointer]: URL path segment - - generic [ref=e386]: - - textbox "URL path segment" [ref=e389]: home - - button "Syncronize with the title property" [ref=e391] [cursor=pointer]: - - img [ref=e392] - - generic [ref=e394]: - - generic [ref=e397] [cursor=pointer]: Title Override - - textbox "Title Override" [ref=e398]: - - /placeholder: Used in <title> tag, max. 60 chars - - generic [ref=e399]: - - button "Teaser image" [expanded] [ref=e400]: - - heading "Teaser image" [level=2] [ref=e401] [cursor=pointer]: - - img [ref=e403] - - text: Teaser image - - button [ref=e405] [cursor=pointer]: - - img [ref=e406] - - generic [ref=e411]: - - generic [ref=e414] [cursor=pointer]: Image - - generic [ref=e415]: - - button "image" [ref=e418] [cursor=pointer]: - - img "image" [ref=e420] - - generic [ref=e421]: - - generic [ref=e422]: - - button "Media" [ref=e423] [cursor=pointer]: - - img [ref=e424] - - button "Choose file" [ref=e426] [cursor=pointer]: - - img [ref=e427] - - button "Remove" [ref=e429] [cursor=pointer]: - - img [ref=e430] - - button "Crop" [ref=e432] [cursor=pointer]: - - img [ref=e433] - - generic [ref=e435]: - - button "Discard" [disabled] [ref=e436] - - button "Apply" [disabled] [ref=e437] -``` \ No newline at end of file diff --git a/Tests/E2E/test-results/enforce-for-all-editor-is-redirected-to-2FA-setup-chromium/error-context.md b/Tests/E2E/test-results/enforce-for-all-editor-is-redirected-to-2FA-setup-chromium/error-context.md deleted file mode 100644 index ab3dd36..0000000 --- a/Tests/E2E/test-results/enforce-for-all-editor-is-redirected-to-2FA-setup-chromium/error-context.md +++ /dev/null @@ -1,327 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e4]: - - generic [ref=e5]: - - generic [ref=e6]: - - button "Menu" [ref=e7] [cursor=pointer] - - img [ref=e11] - - generic [ref=e16]: - - button "In-Place" [ref=e19] [cursor=pointer]: - - img - - generic [ref=e20]: In-Place - - img - - button "English (US)" [ref=e23] [cursor=pointer]: - - button "English (US)" [ref=e26]: - - generic [ref=e27]: - - img - - link "English (US)" [ref=e28]: - - /url: http://localhost:8081/neos/content?node=%2Fsites%2Fneosdemo%3Blanguage%3Den_US - - img - - button "E2E Editor" [ref=e31] [cursor=pointer]: - - img - - text: E2E Editor - - img - - generic [ref=e32]: - - button "Published - live" [disabled] [ref=e33] - - button [ref=e34] [cursor=pointer]: - - img - - generic [ref=e36]: - - button "Display Keyboard Shortcuts" [ref=e37] [cursor=pointer]: - - img [ref=e38] - - link "Show preview" [ref=e40] [cursor=pointer]: - - /url: http://localhost:8081/neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eeditor%3Blanguage%3Den_US - - img [ref=e41] - - button "Activate Fullscreen edit mode" [ref=e43] [cursor=pointer]: - - img [ref=e44] - - iframe [ref=e48]: - - generic [active] [ref=f1e1]: - - banner [ref=f1e2]: - - generic [ref=f1e3]: - - link [ref=f1e4] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eeditor%3Blanguage%3Den_US - - img [ref=f1e5] - - navigation - - button "Open menu" [ref=f1e11] [cursor=pointer]: - - generic [ref=f1e12]: Open menu - - img [ref=f1e13] - - navigation [ref=f1e15]: - - button "English (US)" [ref=f1e16] [cursor=pointer]: - - text: English (US) - - img [ref=f1e17] - - heading "Welcome to the Neos CMS demo" [level=1] [ref=f1e22]: - - textbox "Rich Text Editor, main" [ref=f1e23] - - main [ref=f1e24]: - - generic [ref=f1e25]: - - generic [ref=f1e26]: - - generic [ref=f1e27]: - - heading "Imagine this..." [level=2] [ref=f1e28]: - - textbox "Rich Text Editor, main" [ref=f1e29] - - textbox "Rich Text Editor, main" [ref=f1e31]: - - paragraph [ref=f1e32]: In only a few years from now, the world will have entirely new cities. Cities where development has not even started. Cities that can draw from all we know about liveability and functionality. - - paragraph [ref=f1e33]: "The same goes for websites. Major websites will be built with the tools we’re constructing today. But we don't know how they will be like. Just think of how different sites were 3 years ago. For both cities and websites, one basic rule counts: Things are constantly changing and can never be fully anticipated." - - paragraph [ref=f1e34]: The inspiration from urban planning permeates our UX master plan, giving us crucial guidelines for structuring everything we know - and making room for all the future change we don’t know. - - generic [ref=f1e35]: - - heading "Power when you need it" [level=2] [ref=f1e36]: - - textbox "Rich Text Editor, main" [ref=f1e37] - - textbox "Rich Text Editor, main" [ref=f1e39]: - - paragraph [ref=f1e40]: The distribution of power in a city makes sure that everyone gets what they need. You don’t power a shed in the way you power a nuclear plant. The ultra-simple editing makes editors center their attention on content. If you need to add SEO data next to the content, Neos has an inspector that opens up for that kind of functionality. Just a bit more power, one step at a time... - - paragraph [ref=f1e41]: If you need more, the inspector will be expandable to the full backend screen providing the user with all the options. That’s not enough? Create custom backend modules. And if even that's not enough, underlying Neos, we have Flow - a first-class web framework that enables you to build web applications that integrate with your other systems. - - generic [ref=f1e42]: - - generic [ref=f1e43]: - - heading "Create an account and access the backend" [level=2] [ref=f1e44]: - - textbox "Rich Text Editor, main" [ref=f1e45]: - - generic [ref=f1e46]: Create an account and access the backend - - textbox "Rich Text Editor, main" [ref=f1e48]: - - paragraph [ref=f1e49]: Try out our latest development of the Neos user interface. Get a first impression of CKEditor, the content editor, and try out the simple publishing mechanism based on workspaces. - - paragraph [ref=f1e50]: The registration form is also an example of a "Plugin"-element. - - generic [ref=f1e53]: - - group [ref=f1e54]: - - generic [ref=f1e55]: - - generic [ref=f1e56]: Username - - textbox "Username" [ref=f1e57] - - generic [ref=f1e58]: (lowercase letters and numbers only) - - generic [ref=f1e59]: - - generic [ref=f1e60]: Password - - textbox "Password" [ref=f1e61] - - button "Submit" [ref=f1e62] [cursor=pointer] - - contentinfo [ref=f1e64]: - - navigation [ref=f1e65]: - - link "Contribute" [ref=f1e66] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fcontribute%40user-e2eeditor%3Blanguage%3Den_US - - link "Documentation & Support" [ref=f1e67] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdocumentation%40user-e2eeditor%3Blanguage%3Den_US - - link "Download" [ref=f1e68] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdownload%40user-e2eeditor%3Blanguage%3Den_US - - textbox "Rich Text Editor, main" [ref=f1e72]: - - paragraph [ref=f1e73]: Powered by Neos & Flow - - generic [ref=e53]: "UI version: 8.4.3" - - button "Document Tree" [ref=e54] [cursor=pointer]: - - button "Navigate" [ref=e55]: - - img [ref=e56] - - text: Document Tree - - generic [ref=e58]: - - generic [ref=e59]: - - generic [ref=e62]: - - button "Create new" [ref=e63] [cursor=pointer]: - - img [ref=e64] - - button "Hide / Unhide" [disabled] [ref=e66]: - - img [ref=e67] - - button "Copy" [ref=e69] [cursor=pointer]: - - img [ref=e70] - - button "Cut" [disabled] [ref=e72]: - - img [ref=e73] - - button "Paste" [disabled] [ref=e75]: - - img [ref=e76] - - button "Delete" [disabled] [ref=e78]: - - img [ref=e79] - - button "Refresh" [ref=e81] [cursor=pointer]: - - img [ref=e82] - - button [ref=e84] [cursor=pointer]: - - img [ref=e85] - - tree [ref=e87]: - - treeitem "Home" [ref=e88]: - - button "Collapse All" [ref=e89] [cursor=pointer]: - - img [ref=e90] - - treeitem "Home" [expanded] [ref=e92]: - - generic [ref=e94]: - - button "Home" [ref=e96] [cursor=pointer]: - - generic [ref=e97]: - - img [ref=e99] - - treeitem "Home" [ref=e101] - - button [ref=e102] [cursor=pointer]: - - img [ref=e103] - - generic [ref=e105]: - - treeitem "Home" [expanded] [ref=e106]: - - button "Home" [ref=e110] [cursor=pointer]: - - generic [ref=e111]: - - img [ref=e113] - - treeitem "Home" [ref=e115] - - treeitem "Features" [expanded] [ref=e116]: - - generic [ref=e118]: - - button "Features" [ref=e120] [cursor=pointer]: - - generic [ref=e121]: - - img [ref=e123] - - treeitem "Features" [ref=e125] - - button [ref=e126] [cursor=pointer]: - - img [ref=e127] - - generic [ref=e129]: - - treeitem "Multiple columns" [ref=e130]: - - button "Multiple columns" [ref=e134] [cursor=pointer]: - - generic [ref=e135]: - - img [ref=e137] - - treeitem "Multiple columns" [ref=e139] - - treeitem "Text & images" [ref=e140]: - - button "Text & images" [ref=e144] [cursor=pointer]: - - generic [ref=e145]: - - img [ref=e147] - - treeitem "Text & images" [ref=e149] - - treeitem "Navigation elements" [ref=e150]: - - generic [ref=e152]: - - button "Navigation elements" [ref=e154] [cursor=pointer]: - - generic [ref=e155]: - - img [ref=e157] - - treeitem "Navigation elements" [ref=e159] - - button [ref=e160] [cursor=pointer]: - - img [ref=e161] - - treeitem "Custom elements" [ref=e163]: - - button "Custom elements" [ref=e167] [cursor=pointer]: - - generic [ref=e168]: - - img [ref=e170] - - treeitem "Custom elements" [ref=e172] - - treeitem "Other elements" [ref=e173]: - - button "Other elements" [ref=e177] [cursor=pointer]: - - generic [ref=e178]: - - img [ref=e180] - - treeitem "Other elements" [ref=e182] - - treeitem "Forms" [ref=e183]: - - button "Forms" [ref=e187] [cursor=pointer]: - - generic [ref=e188]: - - img [ref=e190] - - treeitem "Forms" [ref=e192] - - treeitem "Blog" [expanded] [ref=e194]: - - generic [ref=e196]: - - button "Blog" [ref=e198] [cursor=pointer]: - - generic [ref=e199]: - - img [ref=e201] - - treeitem "Blog" [ref=e203] - - button [ref=e204] [cursor=pointer]: - - img [ref=e205] - - generic [ref=e207]: - - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e208]: - - button "Neos 8.0 \"Mad Hatter\" released" [ref=e212] [cursor=pointer]: - - generic [ref=e213]: - - img [ref=e215] - - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e217] - - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e218]: - - button "Neos 8.1 \"Frog–Footman\" released" [ref=e222] [cursor=pointer]: - - generic [ref=e223]: - - img [ref=e225] - - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e227] - - treeitem "Neos 8.2 released" [ref=e228]: - - button "Neos 8.2 released" [ref=e232] [cursor=pointer]: - - generic [ref=e233]: - - img [ref=e235] - - treeitem "Neos 8.2 released" [ref=e237] - - treeitem "Neos 8.3 released" [ref=e238]: - - button "Neos 8.3 released" [ref=e242] [cursor=pointer]: - - generic [ref=e243]: - - img [ref=e245] - - treeitem "Neos 8.3 released" [ref=e247] - - treeitem "Neos 8.4 released" [ref=e248]: - - button "Neos 8.4 released" [ref=e252] [cursor=pointer]: - - generic [ref=e253]: - - img [ref=e255] - - treeitem "Neos 8.4 released" [ref=e257] - - treeitem "Download" [expanded] [ref=e259]: - - button "Download" [ref=e263] [cursor=pointer]: - - generic [ref=e264]: - - img [ref=e266] - - treeitem "Download" [ref=e268] - - treeitem "Contribute" [expanded] [ref=e269]: - - button "Contribute" [ref=e273] [cursor=pointer]: - - generic [ref=e274]: - - img [ref=e276] - - treeitem "Contribute" [ref=e278] - - treeitem "Documentation & Support" [expanded] [ref=e279]: - - button "Documentation & Support" [ref=e283] [cursor=pointer]: - - generic [ref=e284]: - - img [ref=e286] - - treeitem "Documentation & Support" [ref=e288] - - treeitem "Not Found" [expanded] [ref=e289]: - - button "Not Found" [ref=e293] [cursor=pointer]: - - generic [ref=e294]: - - img [ref=e296] - - treeitem "Not Found" [ref=e298] - - generic [ref=e301]: - - button "Toggle content tree Content Tree" [ref=e303] [cursor=pointer]: - - button "Toggle content tree" [ref=e304]: - - img [ref=e305] - - text: Content Tree - - generic [ref=e308]: - - button "Create new" [ref=e309] [cursor=pointer]: - - img [ref=e310] - - button "Hide / Unhide" [ref=e312] [cursor=pointer]: - - img [ref=e313] - - button "Copy" [ref=e315] [cursor=pointer]: - - img [ref=e316] - - button "Cut" [ref=e318] [cursor=pointer]: - - img [ref=e319] - - button "Paste" [disabled] [ref=e321]: - - img [ref=e322] - - button "Delete" [ref=e324] [cursor=pointer]: - - img [ref=e325] - - button "Refresh" [ref=e327] [cursor=pointer]: - - img [ref=e328] - - form [ref=e330]: - - button "Toggle inspector" [ref=e331] [cursor=pointer]: - - img [ref=e332] - - generic [ref=e335]: - - generic [ref=e336]: - - heading "Selected element" [level=1] [ref=e337]: - - generic [ref=e339]: Selected element - - button "Home" [ref=e342] [cursor=pointer]: - - button "Home" [ref=e345]: - - generic [ref=e346]: - - img - - text: Home - - img - - tablist [ref=e347]: - - list [ref=e348]: - - tab "General" [selected] [ref=e349] [cursor=pointer]: - - img [ref=e350] - - tab "Site configuration" [ref=e352] [cursor=pointer]: - - img [ref=e353] - - tab "Metadata" [ref=e355] [cursor=pointer]: - - img [ref=e356] - - tab "SEO" [ref=e358] [cursor=pointer]: - - img [ref=e359] - - tabpanel [ref=e362]: - - generic [ref=e363]: - - generic [ref=e364]: - - button "Document" [expanded] [ref=e365]: - - heading "Document" [level=2] [ref=e366] [cursor=pointer]: - - img [ref=e368] - - text: Document - - button [ref=e370] [cursor=pointer]: - - img [ref=e371] - - generic [ref=e375]: - - generic [ref=e376]: - - generic [ref=e379] [cursor=pointer]: Title - - textbox "Title" [ref=e381]: Home - - generic [ref=e382]: - - generic [ref=e385] [cursor=pointer]: URL path segment - - generic [ref=e386]: - - textbox "URL path segment" [ref=e389]: home - - button "Syncronize with the title property" [ref=e391] [cursor=pointer]: - - img [ref=e392] - - generic [ref=e394]: - - generic [ref=e397] [cursor=pointer]: Title Override - - textbox "Title Override" [ref=e398]: - - /placeholder: Used in <title> tag, max. 60 chars - - generic [ref=e399]: - - button "Teaser image" [expanded] [ref=e400]: - - heading "Teaser image" [level=2] [ref=e401] [cursor=pointer]: - - img [ref=e403] - - text: Teaser image - - button [ref=e405] [cursor=pointer]: - - img [ref=e406] - - generic [ref=e411]: - - generic [ref=e414] [cursor=pointer]: Image - - generic [ref=e415]: - - button "image" [ref=e418] [cursor=pointer]: - - img "image" [ref=e420] - - generic [ref=e421]: - - generic [ref=e422]: - - button "Media" [ref=e423] [cursor=pointer]: - - img [ref=e424] - - button "Choose file" [ref=e426] [cursor=pointer]: - - img [ref=e427] - - button "Remove" [ref=e429] [cursor=pointer]: - - img [ref=e430] - - button "Crop" [ref=e432] [cursor=pointer]: - - img [ref=e433] - - generic [ref=e435]: - - button "Discard" [disabled] [ref=e436] - - button "Apply" [disabled] [ref=e437] -``` \ No newline at end of file diff --git a/Tests/E2E/test-results/enforce-for-provider-admin-97496--is-redirected-to-2FA-setup-chromium/error-context.md b/Tests/E2E/test-results/enforce-for-provider-admin-97496--is-redirected-to-2FA-setup-chromium/error-context.md deleted file mode 100644 index 018279c..0000000 --- a/Tests/E2E/test-results/enforce-for-provider-admin-97496--is-redirected-to-2FA-setup-chromium/error-context.md +++ /dev/null @@ -1,327 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e4]: - - generic [ref=e5]: - - generic [ref=e6]: - - button "Menu" [ref=e7] [cursor=pointer] - - img [ref=e11] - - generic [ref=e16]: - - button "In-Place" [ref=e19] [cursor=pointer]: - - img - - generic [ref=e20]: In-Place - - img - - button "English (US)" [ref=e23] [cursor=pointer]: - - button "English (US)" [ref=e26]: - - generic [ref=e27]: - - img - - link "English (US)" [ref=e28]: - - /url: http://localhost:8081/neos/content?node=%2Fsites%2Fneosdemo%3Blanguage%3Den_US - - img - - button "E2E Admin" [ref=e31] [cursor=pointer]: - - img - - text: E2E Admin - - img - - generic [ref=e32]: - - button "Published - live" [disabled] [ref=e33] - - button [ref=e34] [cursor=pointer]: - - img - - generic [ref=e36]: - - button "Display Keyboard Shortcuts" [ref=e37] [cursor=pointer]: - - img [ref=e38] - - link "Show preview" [ref=e40] [cursor=pointer]: - - /url: http://localhost:8081/neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eadmin%3Blanguage%3Den_US - - img [ref=e41] - - button "Activate Fullscreen edit mode" [ref=e43] [cursor=pointer]: - - img [ref=e44] - - iframe [ref=e48]: - - generic [active] [ref=f1e1]: - - banner [ref=f1e2]: - - generic [ref=f1e3]: - - link [ref=f1e4] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eadmin%3Blanguage%3Den_US - - img [ref=f1e5] - - navigation - - button "Open menu" [ref=f1e11] [cursor=pointer]: - - generic [ref=f1e12]: Open menu - - img [ref=f1e13] - - navigation [ref=f1e15]: - - button "English (US)" [ref=f1e16] [cursor=pointer]: - - text: English (US) - - img [ref=f1e17] - - heading "Welcome to the Neos CMS demo" [level=1] [ref=f1e22]: - - textbox "Rich Text Editor, main" [ref=f1e23] - - main [ref=f1e24]: - - generic [ref=f1e25]: - - generic [ref=f1e26]: - - generic [ref=f1e27]: - - heading "Imagine this..." [level=2] [ref=f1e28]: - - textbox "Rich Text Editor, main" [ref=f1e29] - - textbox "Rich Text Editor, main" [ref=f1e31]: - - paragraph [ref=f1e32]: In only a few years from now, the world will have entirely new cities. Cities where development has not even started. Cities that can draw from all we know about liveability and functionality. - - paragraph [ref=f1e33]: "The same goes for websites. Major websites will be built with the tools we’re constructing today. But we don't know how they will be like. Just think of how different sites were 3 years ago. For both cities and websites, one basic rule counts: Things are constantly changing and can never be fully anticipated." - - paragraph [ref=f1e34]: The inspiration from urban planning permeates our UX master plan, giving us crucial guidelines for structuring everything we know - and making room for all the future change we don’t know. - - generic [ref=f1e35]: - - heading "Power when you need it" [level=2] [ref=f1e36]: - - textbox "Rich Text Editor, main" [ref=f1e37] - - textbox "Rich Text Editor, main" [ref=f1e39]: - - paragraph [ref=f1e40]: The distribution of power in a city makes sure that everyone gets what they need. You don’t power a shed in the way you power a nuclear plant. The ultra-simple editing makes editors center their attention on content. If you need to add SEO data next to the content, Neos has an inspector that opens up for that kind of functionality. Just a bit more power, one step at a time... - - paragraph [ref=f1e41]: If you need more, the inspector will be expandable to the full backend screen providing the user with all the options. That’s not enough? Create custom backend modules. And if even that's not enough, underlying Neos, we have Flow - a first-class web framework that enables you to build web applications that integrate with your other systems. - - generic [ref=f1e42]: - - generic [ref=f1e43]: - - heading "Create an account and access the backend" [level=2] [ref=f1e44]: - - textbox "Rich Text Editor, main" [ref=f1e45]: - - generic [ref=f1e46]: Create an account and access the backend - - textbox "Rich Text Editor, main" [ref=f1e48]: - - paragraph [ref=f1e49]: Try out our latest development of the Neos user interface. Get a first impression of CKEditor, the content editor, and try out the simple publishing mechanism based on workspaces. - - paragraph [ref=f1e50]: The registration form is also an example of a "Plugin"-element. - - generic [ref=f1e53]: - - group [ref=f1e54]: - - generic [ref=f1e55]: - - generic [ref=f1e56]: Username - - textbox "Username" [ref=f1e57] - - generic [ref=f1e58]: (lowercase letters and numbers only) - - generic [ref=f1e59]: - - generic [ref=f1e60]: Password - - textbox "Password" [ref=f1e61] - - button "Submit" [ref=f1e62] [cursor=pointer] - - contentinfo [ref=f1e64]: - - navigation [ref=f1e65]: - - link "Contribute" [ref=f1e66] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fcontribute%40user-e2eadmin%3Blanguage%3Den_US - - link "Documentation & Support" [ref=f1e67] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdocumentation%40user-e2eadmin%3Blanguage%3Den_US - - link "Download" [ref=f1e68] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdownload%40user-e2eadmin%3Blanguage%3Den_US - - textbox "Rich Text Editor, main" [ref=f1e72]: - - paragraph [ref=f1e73]: Powered by Neos & Flow - - generic [ref=e53]: "UI version: 8.4.3" - - button "Document Tree" [ref=e54] [cursor=pointer]: - - button "Navigate" [ref=e55]: - - img [ref=e56] - - text: Document Tree - - generic [ref=e58]: - - generic [ref=e59]: - - generic [ref=e62]: - - button "Create new" [ref=e63] [cursor=pointer]: - - img [ref=e64] - - button "Hide / Unhide" [disabled] [ref=e66]: - - img [ref=e67] - - button "Copy" [ref=e69] [cursor=pointer]: - - img [ref=e70] - - button "Cut" [disabled] [ref=e72]: - - img [ref=e73] - - button "Paste" [disabled] [ref=e75]: - - img [ref=e76] - - button "Delete" [disabled] [ref=e78]: - - img [ref=e79] - - button "Refresh" [ref=e81] [cursor=pointer]: - - img [ref=e82] - - button [ref=e84] [cursor=pointer]: - - img [ref=e85] - - tree [ref=e87]: - - treeitem "Home" [ref=e88]: - - button "Collapse All" [ref=e89] [cursor=pointer]: - - img [ref=e90] - - treeitem "Home" [expanded] [ref=e92]: - - generic [ref=e94]: - - button "Home" [ref=e96] [cursor=pointer]: - - generic [ref=e97]: - - img [ref=e99] - - treeitem "Home" [ref=e101] - - button [ref=e102] [cursor=pointer]: - - img [ref=e103] - - generic [ref=e105]: - - treeitem "Home" [expanded] [ref=e106]: - - button "Home" [ref=e110] [cursor=pointer]: - - generic [ref=e111]: - - img [ref=e113] - - treeitem "Home" [ref=e115] - - treeitem "Features" [expanded] [ref=e116]: - - generic [ref=e118]: - - button "Features" [ref=e120] [cursor=pointer]: - - generic [ref=e121]: - - img [ref=e123] - - treeitem "Features" [ref=e125] - - button [ref=e126] [cursor=pointer]: - - img [ref=e127] - - generic [ref=e129]: - - treeitem "Multiple columns" [ref=e130]: - - button "Multiple columns" [ref=e134] [cursor=pointer]: - - generic [ref=e135]: - - img [ref=e137] - - treeitem "Multiple columns" [ref=e139] - - treeitem "Text & images" [ref=e140]: - - button "Text & images" [ref=e144] [cursor=pointer]: - - generic [ref=e145]: - - img [ref=e147] - - treeitem "Text & images" [ref=e149] - - treeitem "Navigation elements" [ref=e150]: - - generic [ref=e152]: - - button "Navigation elements" [ref=e154] [cursor=pointer]: - - generic [ref=e155]: - - img [ref=e157] - - treeitem "Navigation elements" [ref=e159] - - button [ref=e160] [cursor=pointer]: - - img [ref=e161] - - treeitem "Custom elements" [ref=e163]: - - button "Custom elements" [ref=e167] [cursor=pointer]: - - generic [ref=e168]: - - img [ref=e170] - - treeitem "Custom elements" [ref=e172] - - treeitem "Other elements" [ref=e173]: - - button "Other elements" [ref=e177] [cursor=pointer]: - - generic [ref=e178]: - - img [ref=e180] - - treeitem "Other elements" [ref=e182] - - treeitem "Forms" [ref=e183]: - - button "Forms" [ref=e187] [cursor=pointer]: - - generic [ref=e188]: - - img [ref=e190] - - treeitem "Forms" [ref=e192] - - treeitem "Blog" [expanded] [ref=e194]: - - generic [ref=e196]: - - button "Blog" [ref=e198] [cursor=pointer]: - - generic [ref=e199]: - - img [ref=e201] - - treeitem "Blog" [ref=e203] - - button [ref=e204] [cursor=pointer]: - - img [ref=e205] - - generic [ref=e207]: - - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e208]: - - button "Neos 8.0 \"Mad Hatter\" released" [ref=e212] [cursor=pointer]: - - generic [ref=e213]: - - img [ref=e215] - - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e217] - - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e218]: - - button "Neos 8.1 \"Frog–Footman\" released" [ref=e222] [cursor=pointer]: - - generic [ref=e223]: - - img [ref=e225] - - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e227] - - treeitem "Neos 8.2 released" [ref=e228]: - - button "Neos 8.2 released" [ref=e232] [cursor=pointer]: - - generic [ref=e233]: - - img [ref=e235] - - treeitem "Neos 8.2 released" [ref=e237] - - treeitem "Neos 8.3 released" [ref=e238]: - - button "Neos 8.3 released" [ref=e242] [cursor=pointer]: - - generic [ref=e243]: - - img [ref=e245] - - treeitem "Neos 8.3 released" [ref=e247] - - treeitem "Neos 8.4 released" [ref=e248]: - - button "Neos 8.4 released" [ref=e252] [cursor=pointer]: - - generic [ref=e253]: - - img [ref=e255] - - treeitem "Neos 8.4 released" [ref=e257] - - treeitem "Download" [expanded] [ref=e259]: - - button "Download" [ref=e263] [cursor=pointer]: - - generic [ref=e264]: - - img [ref=e266] - - treeitem "Download" [ref=e268] - - treeitem "Contribute" [expanded] [ref=e269]: - - button "Contribute" [ref=e273] [cursor=pointer]: - - generic [ref=e274]: - - img [ref=e276] - - treeitem "Contribute" [ref=e278] - - treeitem "Documentation & Support" [expanded] [ref=e279]: - - button "Documentation & Support" [ref=e283] [cursor=pointer]: - - generic [ref=e284]: - - img [ref=e286] - - treeitem "Documentation & Support" [ref=e288] - - treeitem "Not Found" [expanded] [ref=e289]: - - button "Not Found" [ref=e293] [cursor=pointer]: - - generic [ref=e294]: - - img [ref=e296] - - treeitem "Not Found" [ref=e298] - - generic [ref=e301]: - - button "Toggle content tree Content Tree" [ref=e303] [cursor=pointer]: - - button "Toggle content tree" [ref=e304]: - - img [ref=e305] - - text: Content Tree - - generic [ref=e308]: - - button "Create new" [ref=e309] [cursor=pointer]: - - img [ref=e310] - - button "Hide / Unhide" [ref=e312] [cursor=pointer]: - - img [ref=e313] - - button "Copy" [ref=e315] [cursor=pointer]: - - img [ref=e316] - - button "Cut" [ref=e318] [cursor=pointer]: - - img [ref=e319] - - button "Paste" [disabled] [ref=e321]: - - img [ref=e322] - - button "Delete" [ref=e324] [cursor=pointer]: - - img [ref=e325] - - button "Refresh" [ref=e327] [cursor=pointer]: - - img [ref=e328] - - form [ref=e330]: - - button "Toggle inspector" [ref=e331] [cursor=pointer]: - - img [ref=e332] - - generic [ref=e335]: - - generic [ref=e336]: - - heading "Selected element" [level=1] [ref=e337]: - - generic [ref=e339]: Selected element - - button "Home" [ref=e342] [cursor=pointer]: - - button "Home" [ref=e345]: - - generic [ref=e346]: - - img - - text: Home - - img - - tablist [ref=e347]: - - list [ref=e348]: - - tab "General" [selected] [ref=e349] [cursor=pointer]: - - img [ref=e350] - - tab "Site configuration" [ref=e352] [cursor=pointer]: - - img [ref=e353] - - tab "Metadata" [ref=e355] [cursor=pointer]: - - img [ref=e356] - - tab "SEO" [ref=e358] [cursor=pointer]: - - img [ref=e359] - - tabpanel [ref=e362]: - - generic [ref=e363]: - - generic [ref=e364]: - - button "Document" [expanded] [ref=e365]: - - heading "Document" [level=2] [ref=e366] [cursor=pointer]: - - img [ref=e368] - - text: Document - - button [ref=e370] [cursor=pointer]: - - img [ref=e371] - - generic [ref=e375]: - - generic [ref=e376]: - - generic [ref=e379] [cursor=pointer]: Title - - textbox "Title" [ref=e381]: Home - - generic [ref=e382]: - - generic [ref=e385] [cursor=pointer]: URL path segment - - generic [ref=e386]: - - textbox "URL path segment" [ref=e389]: home - - button "Syncronize with the title property" [ref=e391] [cursor=pointer]: - - img [ref=e392] - - generic [ref=e394]: - - generic [ref=e397] [cursor=pointer]: Title Override - - textbox "Title Override" [ref=e398]: - - /placeholder: Used in <title> tag, max. 60 chars - - generic [ref=e399]: - - button "Teaser image" [expanded] [ref=e400]: - - heading "Teaser image" [level=2] [ref=e401] [cursor=pointer]: - - img [ref=e403] - - text: Teaser image - - button [ref=e405] [cursor=pointer]: - - img [ref=e406] - - generic [ref=e411]: - - generic [ref=e414] [cursor=pointer]: Image - - generic [ref=e415]: - - button "image" [ref=e418] [cursor=pointer]: - - img "image" [ref=e420] - - generic [ref=e421]: - - generic [ref=e422]: - - button "Media" [ref=e423] [cursor=pointer]: - - img [ref=e424] - - button "Choose file" [ref=e426] [cursor=pointer]: - - img [ref=e427] - - button "Remove" [ref=e429] [cursor=pointer]: - - img [ref=e430] - - button "Crop" [ref=e432] [cursor=pointer]: - - img [ref=e433] - - generic [ref=e435]: - - button "Discard" [disabled] [ref=e436] - - button "Apply" [disabled] [ref=e437] -``` \ No newline at end of file diff --git a/Tests/E2E/test-results/enforce-for-provider-editor-is-redirected-to-2FA-setup-chromium/error-context.md b/Tests/E2E/test-results/enforce-for-provider-editor-is-redirected-to-2FA-setup-chromium/error-context.md deleted file mode 100644 index ab3dd36..0000000 --- a/Tests/E2E/test-results/enforce-for-provider-editor-is-redirected-to-2FA-setup-chromium/error-context.md +++ /dev/null @@ -1,327 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e4]: - - generic [ref=e5]: - - generic [ref=e6]: - - button "Menu" [ref=e7] [cursor=pointer] - - img [ref=e11] - - generic [ref=e16]: - - button "In-Place" [ref=e19] [cursor=pointer]: - - img - - generic [ref=e20]: In-Place - - img - - button "English (US)" [ref=e23] [cursor=pointer]: - - button "English (US)" [ref=e26]: - - generic [ref=e27]: - - img - - link "English (US)" [ref=e28]: - - /url: http://localhost:8081/neos/content?node=%2Fsites%2Fneosdemo%3Blanguage%3Den_US - - img - - button "E2E Editor" [ref=e31] [cursor=pointer]: - - img - - text: E2E Editor - - img - - generic [ref=e32]: - - button "Published - live" [disabled] [ref=e33] - - button [ref=e34] [cursor=pointer]: - - img - - generic [ref=e36]: - - button "Display Keyboard Shortcuts" [ref=e37] [cursor=pointer]: - - img [ref=e38] - - link "Show preview" [ref=e40] [cursor=pointer]: - - /url: http://localhost:8081/neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eeditor%3Blanguage%3Den_US - - img [ref=e41] - - button "Activate Fullscreen edit mode" [ref=e43] [cursor=pointer]: - - img [ref=e44] - - iframe [ref=e48]: - - generic [active] [ref=f1e1]: - - banner [ref=f1e2]: - - generic [ref=f1e3]: - - link [ref=f1e4] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eeditor%3Blanguage%3Den_US - - img [ref=f1e5] - - navigation - - button "Open menu" [ref=f1e11] [cursor=pointer]: - - generic [ref=f1e12]: Open menu - - img [ref=f1e13] - - navigation [ref=f1e15]: - - button "English (US)" [ref=f1e16] [cursor=pointer]: - - text: English (US) - - img [ref=f1e17] - - heading "Welcome to the Neos CMS demo" [level=1] [ref=f1e22]: - - textbox "Rich Text Editor, main" [ref=f1e23] - - main [ref=f1e24]: - - generic [ref=f1e25]: - - generic [ref=f1e26]: - - generic [ref=f1e27]: - - heading "Imagine this..." [level=2] [ref=f1e28]: - - textbox "Rich Text Editor, main" [ref=f1e29] - - textbox "Rich Text Editor, main" [ref=f1e31]: - - paragraph [ref=f1e32]: In only a few years from now, the world will have entirely new cities. Cities where development has not even started. Cities that can draw from all we know about liveability and functionality. - - paragraph [ref=f1e33]: "The same goes for websites. Major websites will be built with the tools we’re constructing today. But we don't know how they will be like. Just think of how different sites were 3 years ago. For both cities and websites, one basic rule counts: Things are constantly changing and can never be fully anticipated." - - paragraph [ref=f1e34]: The inspiration from urban planning permeates our UX master plan, giving us crucial guidelines for structuring everything we know - and making room for all the future change we don’t know. - - generic [ref=f1e35]: - - heading "Power when you need it" [level=2] [ref=f1e36]: - - textbox "Rich Text Editor, main" [ref=f1e37] - - textbox "Rich Text Editor, main" [ref=f1e39]: - - paragraph [ref=f1e40]: The distribution of power in a city makes sure that everyone gets what they need. You don’t power a shed in the way you power a nuclear plant. The ultra-simple editing makes editors center their attention on content. If you need to add SEO data next to the content, Neos has an inspector that opens up for that kind of functionality. Just a bit more power, one step at a time... - - paragraph [ref=f1e41]: If you need more, the inspector will be expandable to the full backend screen providing the user with all the options. That’s not enough? Create custom backend modules. And if even that's not enough, underlying Neos, we have Flow - a first-class web framework that enables you to build web applications that integrate with your other systems. - - generic [ref=f1e42]: - - generic [ref=f1e43]: - - heading "Create an account and access the backend" [level=2] [ref=f1e44]: - - textbox "Rich Text Editor, main" [ref=f1e45]: - - generic [ref=f1e46]: Create an account and access the backend - - textbox "Rich Text Editor, main" [ref=f1e48]: - - paragraph [ref=f1e49]: Try out our latest development of the Neos user interface. Get a first impression of CKEditor, the content editor, and try out the simple publishing mechanism based on workspaces. - - paragraph [ref=f1e50]: The registration form is also an example of a "Plugin"-element. - - generic [ref=f1e53]: - - group [ref=f1e54]: - - generic [ref=f1e55]: - - generic [ref=f1e56]: Username - - textbox "Username" [ref=f1e57] - - generic [ref=f1e58]: (lowercase letters and numbers only) - - generic [ref=f1e59]: - - generic [ref=f1e60]: Password - - textbox "Password" [ref=f1e61] - - button "Submit" [ref=f1e62] [cursor=pointer] - - contentinfo [ref=f1e64]: - - navigation [ref=f1e65]: - - link "Contribute" [ref=f1e66] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fcontribute%40user-e2eeditor%3Blanguage%3Den_US - - link "Documentation & Support" [ref=f1e67] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdocumentation%40user-e2eeditor%3Blanguage%3Den_US - - link "Download" [ref=f1e68] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdownload%40user-e2eeditor%3Blanguage%3Den_US - - textbox "Rich Text Editor, main" [ref=f1e72]: - - paragraph [ref=f1e73]: Powered by Neos & Flow - - generic [ref=e53]: "UI version: 8.4.3" - - button "Document Tree" [ref=e54] [cursor=pointer]: - - button "Navigate" [ref=e55]: - - img [ref=e56] - - text: Document Tree - - generic [ref=e58]: - - generic [ref=e59]: - - generic [ref=e62]: - - button "Create new" [ref=e63] [cursor=pointer]: - - img [ref=e64] - - button "Hide / Unhide" [disabled] [ref=e66]: - - img [ref=e67] - - button "Copy" [ref=e69] [cursor=pointer]: - - img [ref=e70] - - button "Cut" [disabled] [ref=e72]: - - img [ref=e73] - - button "Paste" [disabled] [ref=e75]: - - img [ref=e76] - - button "Delete" [disabled] [ref=e78]: - - img [ref=e79] - - button "Refresh" [ref=e81] [cursor=pointer]: - - img [ref=e82] - - button [ref=e84] [cursor=pointer]: - - img [ref=e85] - - tree [ref=e87]: - - treeitem "Home" [ref=e88]: - - button "Collapse All" [ref=e89] [cursor=pointer]: - - img [ref=e90] - - treeitem "Home" [expanded] [ref=e92]: - - generic [ref=e94]: - - button "Home" [ref=e96] [cursor=pointer]: - - generic [ref=e97]: - - img [ref=e99] - - treeitem "Home" [ref=e101] - - button [ref=e102] [cursor=pointer]: - - img [ref=e103] - - generic [ref=e105]: - - treeitem "Home" [expanded] [ref=e106]: - - button "Home" [ref=e110] [cursor=pointer]: - - generic [ref=e111]: - - img [ref=e113] - - treeitem "Home" [ref=e115] - - treeitem "Features" [expanded] [ref=e116]: - - generic [ref=e118]: - - button "Features" [ref=e120] [cursor=pointer]: - - generic [ref=e121]: - - img [ref=e123] - - treeitem "Features" [ref=e125] - - button [ref=e126] [cursor=pointer]: - - img [ref=e127] - - generic [ref=e129]: - - treeitem "Multiple columns" [ref=e130]: - - button "Multiple columns" [ref=e134] [cursor=pointer]: - - generic [ref=e135]: - - img [ref=e137] - - treeitem "Multiple columns" [ref=e139] - - treeitem "Text & images" [ref=e140]: - - button "Text & images" [ref=e144] [cursor=pointer]: - - generic [ref=e145]: - - img [ref=e147] - - treeitem "Text & images" [ref=e149] - - treeitem "Navigation elements" [ref=e150]: - - generic [ref=e152]: - - button "Navigation elements" [ref=e154] [cursor=pointer]: - - generic [ref=e155]: - - img [ref=e157] - - treeitem "Navigation elements" [ref=e159] - - button [ref=e160] [cursor=pointer]: - - img [ref=e161] - - treeitem "Custom elements" [ref=e163]: - - button "Custom elements" [ref=e167] [cursor=pointer]: - - generic [ref=e168]: - - img [ref=e170] - - treeitem "Custom elements" [ref=e172] - - treeitem "Other elements" [ref=e173]: - - button "Other elements" [ref=e177] [cursor=pointer]: - - generic [ref=e178]: - - img [ref=e180] - - treeitem "Other elements" [ref=e182] - - treeitem "Forms" [ref=e183]: - - button "Forms" [ref=e187] [cursor=pointer]: - - generic [ref=e188]: - - img [ref=e190] - - treeitem "Forms" [ref=e192] - - treeitem "Blog" [expanded] [ref=e194]: - - generic [ref=e196]: - - button "Blog" [ref=e198] [cursor=pointer]: - - generic [ref=e199]: - - img [ref=e201] - - treeitem "Blog" [ref=e203] - - button [ref=e204] [cursor=pointer]: - - img [ref=e205] - - generic [ref=e207]: - - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e208]: - - button "Neos 8.0 \"Mad Hatter\" released" [ref=e212] [cursor=pointer]: - - generic [ref=e213]: - - img [ref=e215] - - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e217] - - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e218]: - - button "Neos 8.1 \"Frog–Footman\" released" [ref=e222] [cursor=pointer]: - - generic [ref=e223]: - - img [ref=e225] - - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e227] - - treeitem "Neos 8.2 released" [ref=e228]: - - button "Neos 8.2 released" [ref=e232] [cursor=pointer]: - - generic [ref=e233]: - - img [ref=e235] - - treeitem "Neos 8.2 released" [ref=e237] - - treeitem "Neos 8.3 released" [ref=e238]: - - button "Neos 8.3 released" [ref=e242] [cursor=pointer]: - - generic [ref=e243]: - - img [ref=e245] - - treeitem "Neos 8.3 released" [ref=e247] - - treeitem "Neos 8.4 released" [ref=e248]: - - button "Neos 8.4 released" [ref=e252] [cursor=pointer]: - - generic [ref=e253]: - - img [ref=e255] - - treeitem "Neos 8.4 released" [ref=e257] - - treeitem "Download" [expanded] [ref=e259]: - - button "Download" [ref=e263] [cursor=pointer]: - - generic [ref=e264]: - - img [ref=e266] - - treeitem "Download" [ref=e268] - - treeitem "Contribute" [expanded] [ref=e269]: - - button "Contribute" [ref=e273] [cursor=pointer]: - - generic [ref=e274]: - - img [ref=e276] - - treeitem "Contribute" [ref=e278] - - treeitem "Documentation & Support" [expanded] [ref=e279]: - - button "Documentation & Support" [ref=e283] [cursor=pointer]: - - generic [ref=e284]: - - img [ref=e286] - - treeitem "Documentation & Support" [ref=e288] - - treeitem "Not Found" [expanded] [ref=e289]: - - button "Not Found" [ref=e293] [cursor=pointer]: - - generic [ref=e294]: - - img [ref=e296] - - treeitem "Not Found" [ref=e298] - - generic [ref=e301]: - - button "Toggle content tree Content Tree" [ref=e303] [cursor=pointer]: - - button "Toggle content tree" [ref=e304]: - - img [ref=e305] - - text: Content Tree - - generic [ref=e308]: - - button "Create new" [ref=e309] [cursor=pointer]: - - img [ref=e310] - - button "Hide / Unhide" [ref=e312] [cursor=pointer]: - - img [ref=e313] - - button "Copy" [ref=e315] [cursor=pointer]: - - img [ref=e316] - - button "Cut" [ref=e318] [cursor=pointer]: - - img [ref=e319] - - button "Paste" [disabled] [ref=e321]: - - img [ref=e322] - - button "Delete" [ref=e324] [cursor=pointer]: - - img [ref=e325] - - button "Refresh" [ref=e327] [cursor=pointer]: - - img [ref=e328] - - form [ref=e330]: - - button "Toggle inspector" [ref=e331] [cursor=pointer]: - - img [ref=e332] - - generic [ref=e335]: - - generic [ref=e336]: - - heading "Selected element" [level=1] [ref=e337]: - - generic [ref=e339]: Selected element - - button "Home" [ref=e342] [cursor=pointer]: - - button "Home" [ref=e345]: - - generic [ref=e346]: - - img - - text: Home - - img - - tablist [ref=e347]: - - list [ref=e348]: - - tab "General" [selected] [ref=e349] [cursor=pointer]: - - img [ref=e350] - - tab "Site configuration" [ref=e352] [cursor=pointer]: - - img [ref=e353] - - tab "Metadata" [ref=e355] [cursor=pointer]: - - img [ref=e356] - - tab "SEO" [ref=e358] [cursor=pointer]: - - img [ref=e359] - - tabpanel [ref=e362]: - - generic [ref=e363]: - - generic [ref=e364]: - - button "Document" [expanded] [ref=e365]: - - heading "Document" [level=2] [ref=e366] [cursor=pointer]: - - img [ref=e368] - - text: Document - - button [ref=e370] [cursor=pointer]: - - img [ref=e371] - - generic [ref=e375]: - - generic [ref=e376]: - - generic [ref=e379] [cursor=pointer]: Title - - textbox "Title" [ref=e381]: Home - - generic [ref=e382]: - - generic [ref=e385] [cursor=pointer]: URL path segment - - generic [ref=e386]: - - textbox "URL path segment" [ref=e389]: home - - button "Syncronize with the title property" [ref=e391] [cursor=pointer]: - - img [ref=e392] - - generic [ref=e394]: - - generic [ref=e397] [cursor=pointer]: Title Override - - textbox "Title Override" [ref=e398]: - - /placeholder: Used in <title> tag, max. 60 chars - - generic [ref=e399]: - - button "Teaser image" [expanded] [ref=e400]: - - heading "Teaser image" [level=2] [ref=e401] [cursor=pointer]: - - img [ref=e403] - - text: Teaser image - - button [ref=e405] [cursor=pointer]: - - img [ref=e406] - - generic [ref=e411]: - - generic [ref=e414] [cursor=pointer]: Image - - generic [ref=e415]: - - button "image" [ref=e418] [cursor=pointer]: - - img "image" [ref=e420] - - generic [ref=e421]: - - generic [ref=e422]: - - button "Media" [ref=e423] [cursor=pointer]: - - img [ref=e424] - - button "Choose file" [ref=e426] [cursor=pointer]: - - img [ref=e427] - - button "Remove" [ref=e429] [cursor=pointer]: - - img [ref=e430] - - button "Crop" [ref=e432] [cursor=pointer]: - - img [ref=e433] - - generic [ref=e435]: - - button "Discard" [disabled] [ref=e436] - - button "Apply" [disabled] [ref=e437] -``` \ No newline at end of file diff --git a/Tests/E2E/test-results/enforce-for-role-administrator-is-redirected-to-2FA-setup-chromium/error-context.md b/Tests/E2E/test-results/enforce-for-role-administrator-is-redirected-to-2FA-setup-chromium/error-context.md deleted file mode 100644 index 018279c..0000000 --- a/Tests/E2E/test-results/enforce-for-role-administrator-is-redirected-to-2FA-setup-chromium/error-context.md +++ /dev/null @@ -1,327 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e4]: - - generic [ref=e5]: - - generic [ref=e6]: - - button "Menu" [ref=e7] [cursor=pointer] - - img [ref=e11] - - generic [ref=e16]: - - button "In-Place" [ref=e19] [cursor=pointer]: - - img - - generic [ref=e20]: In-Place - - img - - button "English (US)" [ref=e23] [cursor=pointer]: - - button "English (US)" [ref=e26]: - - generic [ref=e27]: - - img - - link "English (US)" [ref=e28]: - - /url: http://localhost:8081/neos/content?node=%2Fsites%2Fneosdemo%3Blanguage%3Den_US - - img - - button "E2E Admin" [ref=e31] [cursor=pointer]: - - img - - text: E2E Admin - - img - - generic [ref=e32]: - - button "Published - live" [disabled] [ref=e33] - - button [ref=e34] [cursor=pointer]: - - img - - generic [ref=e36]: - - button "Display Keyboard Shortcuts" [ref=e37] [cursor=pointer]: - - img [ref=e38] - - link "Show preview" [ref=e40] [cursor=pointer]: - - /url: http://localhost:8081/neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eadmin%3Blanguage%3Den_US - - img [ref=e41] - - button "Activate Fullscreen edit mode" [ref=e43] [cursor=pointer]: - - img [ref=e44] - - iframe [ref=e48]: - - generic [active] [ref=f1e1]: - - banner [ref=f1e2]: - - generic [ref=f1e3]: - - link [ref=f1e4] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%40user-e2eadmin%3Blanguage%3Den_US - - img [ref=f1e5] - - navigation - - button "Open menu" [ref=f1e11] [cursor=pointer]: - - generic [ref=f1e12]: Open menu - - img [ref=f1e13] - - navigation [ref=f1e15]: - - button "English (US)" [ref=f1e16] [cursor=pointer]: - - text: English (US) - - img [ref=f1e17] - - heading "Welcome to the Neos CMS demo" [level=1] [ref=f1e22]: - - textbox "Rich Text Editor, main" [ref=f1e23] - - main [ref=f1e24]: - - generic [ref=f1e25]: - - generic [ref=f1e26]: - - generic [ref=f1e27]: - - heading "Imagine this..." [level=2] [ref=f1e28]: - - textbox "Rich Text Editor, main" [ref=f1e29] - - textbox "Rich Text Editor, main" [ref=f1e31]: - - paragraph [ref=f1e32]: In only a few years from now, the world will have entirely new cities. Cities where development has not even started. Cities that can draw from all we know about liveability and functionality. - - paragraph [ref=f1e33]: "The same goes for websites. Major websites will be built with the tools we’re constructing today. But we don't know how they will be like. Just think of how different sites were 3 years ago. For both cities and websites, one basic rule counts: Things are constantly changing and can never be fully anticipated." - - paragraph [ref=f1e34]: The inspiration from urban planning permeates our UX master plan, giving us crucial guidelines for structuring everything we know - and making room for all the future change we don’t know. - - generic [ref=f1e35]: - - heading "Power when you need it" [level=2] [ref=f1e36]: - - textbox "Rich Text Editor, main" [ref=f1e37] - - textbox "Rich Text Editor, main" [ref=f1e39]: - - paragraph [ref=f1e40]: The distribution of power in a city makes sure that everyone gets what they need. You don’t power a shed in the way you power a nuclear plant. The ultra-simple editing makes editors center their attention on content. If you need to add SEO data next to the content, Neos has an inspector that opens up for that kind of functionality. Just a bit more power, one step at a time... - - paragraph [ref=f1e41]: If you need more, the inspector will be expandable to the full backend screen providing the user with all the options. That’s not enough? Create custom backend modules. And if even that's not enough, underlying Neos, we have Flow - a first-class web framework that enables you to build web applications that integrate with your other systems. - - generic [ref=f1e42]: - - generic [ref=f1e43]: - - heading "Create an account and access the backend" [level=2] [ref=f1e44]: - - textbox "Rich Text Editor, main" [ref=f1e45]: - - generic [ref=f1e46]: Create an account and access the backend - - textbox "Rich Text Editor, main" [ref=f1e48]: - - paragraph [ref=f1e49]: Try out our latest development of the Neos user interface. Get a first impression of CKEditor, the content editor, and try out the simple publishing mechanism based on workspaces. - - paragraph [ref=f1e50]: The registration form is also an example of a "Plugin"-element. - - generic [ref=f1e53]: - - group [ref=f1e54]: - - generic [ref=f1e55]: - - generic [ref=f1e56]: Username - - textbox "Username" [ref=f1e57] - - generic [ref=f1e58]: (lowercase letters and numbers only) - - generic [ref=f1e59]: - - generic [ref=f1e60]: Password - - textbox "Password" [ref=f1e61] - - button "Submit" [ref=f1e62] [cursor=pointer] - - contentinfo [ref=f1e64]: - - navigation [ref=f1e65]: - - link "Contribute" [ref=f1e66] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fcontribute%40user-e2eadmin%3Blanguage%3Den_US - - link "Documentation & Support" [ref=f1e67] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdocumentation%40user-e2eadmin%3Blanguage%3Den_US - - link "Download" [ref=f1e68] [cursor=pointer]: - - /url: /neos/preview?node%5B__contextNodePath%5D=%2Fsites%2Fneosdemo%2Fdownload%40user-e2eadmin%3Blanguage%3Den_US - - textbox "Rich Text Editor, main" [ref=f1e72]: - - paragraph [ref=f1e73]: Powered by Neos & Flow - - generic [ref=e53]: "UI version: 8.4.3" - - button "Document Tree" [ref=e54] [cursor=pointer]: - - button "Navigate" [ref=e55]: - - img [ref=e56] - - text: Document Tree - - generic [ref=e58]: - - generic [ref=e59]: - - generic [ref=e62]: - - button "Create new" [ref=e63] [cursor=pointer]: - - img [ref=e64] - - button "Hide / Unhide" [disabled] [ref=e66]: - - img [ref=e67] - - button "Copy" [ref=e69] [cursor=pointer]: - - img [ref=e70] - - button "Cut" [disabled] [ref=e72]: - - img [ref=e73] - - button "Paste" [disabled] [ref=e75]: - - img [ref=e76] - - button "Delete" [disabled] [ref=e78]: - - img [ref=e79] - - button "Refresh" [ref=e81] [cursor=pointer]: - - img [ref=e82] - - button [ref=e84] [cursor=pointer]: - - img [ref=e85] - - tree [ref=e87]: - - treeitem "Home" [ref=e88]: - - button "Collapse All" [ref=e89] [cursor=pointer]: - - img [ref=e90] - - treeitem "Home" [expanded] [ref=e92]: - - generic [ref=e94]: - - button "Home" [ref=e96] [cursor=pointer]: - - generic [ref=e97]: - - img [ref=e99] - - treeitem "Home" [ref=e101] - - button [ref=e102] [cursor=pointer]: - - img [ref=e103] - - generic [ref=e105]: - - treeitem "Home" [expanded] [ref=e106]: - - button "Home" [ref=e110] [cursor=pointer]: - - generic [ref=e111]: - - img [ref=e113] - - treeitem "Home" [ref=e115] - - treeitem "Features" [expanded] [ref=e116]: - - generic [ref=e118]: - - button "Features" [ref=e120] [cursor=pointer]: - - generic [ref=e121]: - - img [ref=e123] - - treeitem "Features" [ref=e125] - - button [ref=e126] [cursor=pointer]: - - img [ref=e127] - - generic [ref=e129]: - - treeitem "Multiple columns" [ref=e130]: - - button "Multiple columns" [ref=e134] [cursor=pointer]: - - generic [ref=e135]: - - img [ref=e137] - - treeitem "Multiple columns" [ref=e139] - - treeitem "Text & images" [ref=e140]: - - button "Text & images" [ref=e144] [cursor=pointer]: - - generic [ref=e145]: - - img [ref=e147] - - treeitem "Text & images" [ref=e149] - - treeitem "Navigation elements" [ref=e150]: - - generic [ref=e152]: - - button "Navigation elements" [ref=e154] [cursor=pointer]: - - generic [ref=e155]: - - img [ref=e157] - - treeitem "Navigation elements" [ref=e159] - - button [ref=e160] [cursor=pointer]: - - img [ref=e161] - - treeitem "Custom elements" [ref=e163]: - - button "Custom elements" [ref=e167] [cursor=pointer]: - - generic [ref=e168]: - - img [ref=e170] - - treeitem "Custom elements" [ref=e172] - - treeitem "Other elements" [ref=e173]: - - button "Other elements" [ref=e177] [cursor=pointer]: - - generic [ref=e178]: - - img [ref=e180] - - treeitem "Other elements" [ref=e182] - - treeitem "Forms" [ref=e183]: - - button "Forms" [ref=e187] [cursor=pointer]: - - generic [ref=e188]: - - img [ref=e190] - - treeitem "Forms" [ref=e192] - - treeitem "Blog" [expanded] [ref=e194]: - - generic [ref=e196]: - - button "Blog" [ref=e198] [cursor=pointer]: - - generic [ref=e199]: - - img [ref=e201] - - treeitem "Blog" [ref=e203] - - button [ref=e204] [cursor=pointer]: - - img [ref=e205] - - generic [ref=e207]: - - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e208]: - - button "Neos 8.0 \"Mad Hatter\" released" [ref=e212] [cursor=pointer]: - - generic [ref=e213]: - - img [ref=e215] - - treeitem "Neos 8.0 \"Mad Hatter\" released" [ref=e217] - - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e218]: - - button "Neos 8.1 \"Frog–Footman\" released" [ref=e222] [cursor=pointer]: - - generic [ref=e223]: - - img [ref=e225] - - treeitem "Neos 8.1 \"Frog–Footman\" released" [ref=e227] - - treeitem "Neos 8.2 released" [ref=e228]: - - button "Neos 8.2 released" [ref=e232] [cursor=pointer]: - - generic [ref=e233]: - - img [ref=e235] - - treeitem "Neos 8.2 released" [ref=e237] - - treeitem "Neos 8.3 released" [ref=e238]: - - button "Neos 8.3 released" [ref=e242] [cursor=pointer]: - - generic [ref=e243]: - - img [ref=e245] - - treeitem "Neos 8.3 released" [ref=e247] - - treeitem "Neos 8.4 released" [ref=e248]: - - button "Neos 8.4 released" [ref=e252] [cursor=pointer]: - - generic [ref=e253]: - - img [ref=e255] - - treeitem "Neos 8.4 released" [ref=e257] - - treeitem "Download" [expanded] [ref=e259]: - - button "Download" [ref=e263] [cursor=pointer]: - - generic [ref=e264]: - - img [ref=e266] - - treeitem "Download" [ref=e268] - - treeitem "Contribute" [expanded] [ref=e269]: - - button "Contribute" [ref=e273] [cursor=pointer]: - - generic [ref=e274]: - - img [ref=e276] - - treeitem "Contribute" [ref=e278] - - treeitem "Documentation & Support" [expanded] [ref=e279]: - - button "Documentation & Support" [ref=e283] [cursor=pointer]: - - generic [ref=e284]: - - img [ref=e286] - - treeitem "Documentation & Support" [ref=e288] - - treeitem "Not Found" [expanded] [ref=e289]: - - button "Not Found" [ref=e293] [cursor=pointer]: - - generic [ref=e294]: - - img [ref=e296] - - treeitem "Not Found" [ref=e298] - - generic [ref=e301]: - - button "Toggle content tree Content Tree" [ref=e303] [cursor=pointer]: - - button "Toggle content tree" [ref=e304]: - - img [ref=e305] - - text: Content Tree - - generic [ref=e308]: - - button "Create new" [ref=e309] [cursor=pointer]: - - img [ref=e310] - - button "Hide / Unhide" [ref=e312] [cursor=pointer]: - - img [ref=e313] - - button "Copy" [ref=e315] [cursor=pointer]: - - img [ref=e316] - - button "Cut" [ref=e318] [cursor=pointer]: - - img [ref=e319] - - button "Paste" [disabled] [ref=e321]: - - img [ref=e322] - - button "Delete" [ref=e324] [cursor=pointer]: - - img [ref=e325] - - button "Refresh" [ref=e327] [cursor=pointer]: - - img [ref=e328] - - form [ref=e330]: - - button "Toggle inspector" [ref=e331] [cursor=pointer]: - - img [ref=e332] - - generic [ref=e335]: - - generic [ref=e336]: - - heading "Selected element" [level=1] [ref=e337]: - - generic [ref=e339]: Selected element - - button "Home" [ref=e342] [cursor=pointer]: - - button "Home" [ref=e345]: - - generic [ref=e346]: - - img - - text: Home - - img - - tablist [ref=e347]: - - list [ref=e348]: - - tab "General" [selected] [ref=e349] [cursor=pointer]: - - img [ref=e350] - - tab "Site configuration" [ref=e352] [cursor=pointer]: - - img [ref=e353] - - tab "Metadata" [ref=e355] [cursor=pointer]: - - img [ref=e356] - - tab "SEO" [ref=e358] [cursor=pointer]: - - img [ref=e359] - - tabpanel [ref=e362]: - - generic [ref=e363]: - - generic [ref=e364]: - - button "Document" [expanded] [ref=e365]: - - heading "Document" [level=2] [ref=e366] [cursor=pointer]: - - img [ref=e368] - - text: Document - - button [ref=e370] [cursor=pointer]: - - img [ref=e371] - - generic [ref=e375]: - - generic [ref=e376]: - - generic [ref=e379] [cursor=pointer]: Title - - textbox "Title" [ref=e381]: Home - - generic [ref=e382]: - - generic [ref=e385] [cursor=pointer]: URL path segment - - generic [ref=e386]: - - textbox "URL path segment" [ref=e389]: home - - button "Syncronize with the title property" [ref=e391] [cursor=pointer]: - - img [ref=e392] - - generic [ref=e394]: - - generic [ref=e397] [cursor=pointer]: Title Override - - textbox "Title Override" [ref=e398]: - - /placeholder: Used in <title> tag, max. 60 chars - - generic [ref=e399]: - - button "Teaser image" [expanded] [ref=e400]: - - heading "Teaser image" [level=2] [ref=e401] [cursor=pointer]: - - img [ref=e403] - - text: Teaser image - - button [ref=e405] [cursor=pointer]: - - img [ref=e406] - - generic [ref=e411]: - - generic [ref=e414] [cursor=pointer]: Image - - generic [ref=e415]: - - button "image" [ref=e418] [cursor=pointer]: - - img "image" [ref=e420] - - generic [ref=e421]: - - generic [ref=e422]: - - button "Media" [ref=e423] [cursor=pointer]: - - img [ref=e424] - - button "Choose file" [ref=e426] [cursor=pointer]: - - img [ref=e427] - - button "Remove" [ref=e429] [cursor=pointer]: - - img [ref=e430] - - button "Crop" [ref=e432] [cursor=pointer]: - - img [ref=e433] - - generic [ref=e435]: - - button "Discard" [disabled] [ref=e436] - - button "Apply" [disabled] [ref=e437] -``` \ No newline at end of file diff --git a/Tests/E2E/test-results/issuer-name-change-2FA-set-63185-with-the-custom-issuer-name-chromium/error-context.md b/Tests/E2E/test-results/issuer-name-change-2FA-set-63185-with-the-custom-issuer-name-chromium/error-context.md deleted file mode 100644 index d21e3c5..0000000 --- a/Tests/E2E/test-results/issuer-name-change-2FA-set-63185-with-the-custom-issuer-name-chromium/error-context.md +++ /dev/null @@ -1,25 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e2]: - - main [ref=e3]: - - generic [ref=e4]: - - figure [ref=e5]: - - img "Neos Logo" [ref=e6] - - heading "Login to Neos Demo Site" [level=1] [ref=e7]: - - text: Login to - - strong [ref=e8]: Neos Demo Site - - generic [ref=e10]: - - img [ref=e12] - - button "Show Code" [ref=e15] [cursor=pointer] - - textbox "OTP from App" [ref=e17] - - button "Register new OTP" [ref=e19] [cursor=pointer] - - contentinfo [ref=e20]: - - paragraph [ref=e21]: - - link "Neos" [ref=e22] [cursor=pointer]: - - /url: http://neos.io - - text: – © 2006-2026 This is free software, licensed under GPL3 or higher, and you are welcome to redistribute it under certain conditions; Neos comes with ABSOLUTELY NO WARRANTY; See - - link "neos.io" [ref=e23] [cursor=pointer]: - - /url: http://neos.io - - text: for more details. Obstructing the appearance of this notice is prohibited by law. -``` \ No newline at end of file diff --git a/Tests/E2E/test-results/second-factor-login-login--8650e-en-a-2FA-device-is-enrolled-chromium/error-context.md b/Tests/E2E/test-results/second-factor-login-login--8650e-en-a-2FA-device-is-enrolled-chromium/error-context.md deleted file mode 100644 index d21e3c5..0000000 --- a/Tests/E2E/test-results/second-factor-login-login--8650e-en-a-2FA-device-is-enrolled-chromium/error-context.md +++ /dev/null @@ -1,25 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e2]: - - main [ref=e3]: - - generic [ref=e4]: - - figure [ref=e5]: - - img "Neos Logo" [ref=e6] - - heading "Login to Neos Demo Site" [level=1] [ref=e7]: - - text: Login to - - strong [ref=e8]: Neos Demo Site - - generic [ref=e10]: - - img [ref=e12] - - button "Show Code" [ref=e15] [cursor=pointer] - - textbox "OTP from App" [ref=e17] - - button "Register new OTP" [ref=e19] [cursor=pointer] - - contentinfo [ref=e20]: - - paragraph [ref=e21]: - - link "Neos" [ref=e22] [cursor=pointer]: - - /url: http://neos.io - - text: – © 2006-2026 This is free software, licensed under GPL3 or higher, and you are welcome to redistribute it under certain conditions; Neos comes with ABSOLUTELY NO WARRANTY; See - - link "neos.io" [ref=e23] [cursor=pointer]: - - /url: http://neos.io - - text: for more details. Obstructing the appearance of this notice is prohibited by law. -``` \ No newline at end of file diff --git a/Tests/E2E/test-results/second-factor-setup-comple-4d756-ts-away-from-the-setup-page-chromium/error-context.md b/Tests/E2E/test-results/second-factor-setup-comple-4d756-ts-away-from-the-setup-page-chromium/error-context.md deleted file mode 100644 index d21e3c5..0000000 --- a/Tests/E2E/test-results/second-factor-setup-comple-4d756-ts-away-from-the-setup-page-chromium/error-context.md +++ /dev/null @@ -1,25 +0,0 @@ -# Page snapshot - -```yaml -- generic [ref=e2]: - - main [ref=e3]: - - generic [ref=e4]: - - figure [ref=e5]: - - img "Neos Logo" [ref=e6] - - heading "Login to Neos Demo Site" [level=1] [ref=e7]: - - text: Login to - - strong [ref=e8]: Neos Demo Site - - generic [ref=e10]: - - img [ref=e12] - - button "Show Code" [ref=e15] [cursor=pointer] - - textbox "OTP from App" [ref=e17] - - button "Register new OTP" [ref=e19] [cursor=pointer] - - contentinfo [ref=e20]: - - paragraph [ref=e21]: - - link "Neos" [ref=e22] [cursor=pointer]: - - /url: http://neos.io - - text: – © 2006-2026 This is free software, licensed under GPL3 or higher, and you are welcome to redistribute it under certain conditions; Neos comes with ABSOLUTELY NO WARRANTY; See - - link "neos.io" [ref=e23] [cursor=pointer]: - - /url: http://neos.io - - text: for more details. Obstructing the appearance of this notice is prohibited by law. -``` \ No newline at end of file diff --git a/Tests/E2E/tests/backend-module.spec.ts b/Tests/E2E/tests/backend-module.spec.ts deleted file mode 100644 index f75b2ec..0000000 --- a/Tests/E2E/tests/backend-module.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { execSync } from 'node:child_process'; -import { NeosLoginPage, SecondFactorLoginPage, SecondFactorSetupPage, BackendModulePage } from '../helpers/pages'; -import { generateOtp } from '../helpers/totp'; - -/** - * Tests the Neos backend module for managing 2FA devices. - * A device is enrolled in beforeAll by navigating to the setup page directly. - * After all tests, any remaining devices are removed via the Flow CLI command. - */ - -const CONTAINER = `${process.env.SUT || 'neos8'}-neos-1`; - -let enrolledSecret: string; - -test.beforeAll(async ({ browser }) => { - const page = await browser.newPage(); - const loginPage = new NeosLoginPage(page); - const setupPage = new SecondFactorSetupPage(page); - - await loginPage.goto(); - await loginPage.login('e2eadmin', 'password123'); - await page.goto('/neos/second-factor-setup'); - await setupPage.waitForPage(); - - enrolledSecret = await setupPage.getSecret(); - await setupPage.submitOtp(enrolledSecret); - - await page.close(); -}); - -test.afterAll(() => { - execSync( - `docker exec -u www-data -w /app ${CONTAINER} ./flow secondFactor:deleteForAccount --username e2eadmin`, - { stdio: 'inherit' } - ); -}); - -async function loginWithOtp(page: any) { - const loginPage = new NeosLoginPage(page); - const otpPage = new SecondFactorLoginPage(page); - - await loginPage.goto(); - await loginPage.login('e2eadmin', 'password123'); - - await otpPage.waitForPage(); - await otpPage.enterOtp(generateOtp(enrolledSecret)); -} - -test('backend module shows enrolled 2FA devices', async ({ page }) => { - await loginWithOtp(page); - - const modulePage = new BackendModulePage(page); - await modulePage.goto(); - await modulePage.waitForPage(); - - const deviceCount = await modulePage.getDeviceCount(); - expect(deviceCount).toBeGreaterThan(0); -}); - -test('backend module allows deleting a 2FA device', async ({ page }) => { - await loginWithOtp(page); - - const modulePage = new BackendModulePage(page); - await modulePage.goto(); - await modulePage.waitForPage(); - - const before = await modulePage.getDeviceCount(); - await modulePage.deleteFirstDevice(); - - await page.waitForLoadState('networkidle'); - const after = await modulePage.getDeviceCount(); - expect(after).toBe(before - 1); -}); diff --git a/Tests/E2E/tests/enforce-for-all.spec.ts b/Tests/E2E/tests/enforce-for-all.spec.ts deleted file mode 100644 index e837087..0000000 --- a/Tests/E2E/tests/enforce-for-all.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Tests for FLOW_CONTEXT=Production/E2E-SUT/EnforceForAll - * Config: enforceTwoFactorAuthentication: true - * - * All backend users must set up 2FA before accessing the backend. - */ -import { test, expect } from '@playwright/test'; -import { NeosLoginPage } from '../helpers/pages'; - -test('administrator is redirected to 2FA setup', async ({ page }) => { - const loginPage = new NeosLoginPage(page); - await loginPage.goto(); - await loginPage.login('e2eadmin', 'password123'); - - await expect(page).toHaveURL(/second-factor-setup/); -}); - -test('editor is redirected to 2FA setup', async ({ page }) => { - const loginPage = new NeosLoginPage(page); - await loginPage.goto(); - await loginPage.login('e2eeditor', 'password123'); - - await expect(page).toHaveURL(/second-factor-setup/); -}); diff --git a/Tests/E2E/tests/enforce-for-provider.spec.ts b/Tests/E2E/tests/enforce-for-provider.spec.ts deleted file mode 100644 index 1e97d34..0000000 --- a/Tests/E2E/tests/enforce-for-provider.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Tests for FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider - * Config: enforce2FAForAuthenticationProviders: ['Neos.Neos:Backend'] - * - * All users authenticating via the Neos.Neos:Backend provider must set up 2FA. - * In this SUT all backend users (admin and editor) use that provider. - */ -import { test, expect } from '@playwright/test'; -import { NeosLoginPage } from '../helpers/pages'; - -test('administrator is redirected to 2FA setup', async ({ page }) => { - const loginPage = new NeosLoginPage(page); - await loginPage.goto(); - await loginPage.login('e2eadmin', 'password123'); - - await expect(page).toHaveURL(/second-factor-setup/); -}); - -test('editor is redirected to 2FA setup', async ({ page }) => { - const loginPage = new NeosLoginPage(page); - await loginPage.goto(); - await loginPage.login('e2eeditor', 'password123'); - - await expect(page).toHaveURL(/second-factor-setup/); -}); diff --git a/Tests/E2E/tests/enforce-for-role.spec.ts b/Tests/E2E/tests/enforce-for-role.spec.ts deleted file mode 100644 index 119e17a..0000000 --- a/Tests/E2E/tests/enforce-for-role.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Tests for FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole - * Config: enforce2FAForRoles: ['Neos.Neos:Administrator'] - * - * Only users with the Administrator role are required to set up 2FA. - * Users with only the Editor role must be able to log in without it. - */ -import { test, expect } from '@playwright/test'; -import { NeosLoginPage } from '../helpers/pages'; - -test('administrator is redirected to 2FA setup', async ({ page }) => { - const loginPage = new NeosLoginPage(page); - await loginPage.goto(); - await loginPage.login('e2eadmin', 'password123'); - - await expect(page).toHaveURL(/second-factor-setup/); -}); - -test('editor is not redirected and accesses backend directly', async ({ page }) => { - const loginPage = new NeosLoginPage(page); - await loginPage.goto(); - await loginPage.login('e2eeditor', 'password123'); - - await expect(page).not.toHaveURL(/second-factor/); - await expect(page).toHaveURL(/neos/); -}); diff --git a/Tests/E2E/tests/issuer-name-change.spec.ts b/Tests/E2E/tests/issuer-name-change.spec.ts deleted file mode 100644 index b1f4350..0000000 --- a/Tests/E2E/tests/issuer-name-change.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Tests for FLOW_CONTEXT=Production/E2E-SUT/IssuerNameChange - * Config: issuerName: 'Test Issuer' - * - * 2FA is not enforced. The setup page is accessed directly after login. - * Tests verify that the changed issuer name does not break the 2FA setup flow. - */ -import { test, expect } from '@playwright/test'; -import { execSync } from 'node:child_process'; -import { NeosLoginPage, SecondFactorSetupPage } from '../helpers/pages'; - -const CONTAINER = `${process.env.SUT || 'neos8'}-neos-1`; - -test.afterEach(() => { - execSync( - `docker exec -u www-data -w /app ${CONTAINER} ./flow secondFactor:deleteForAccount --username e2eadmin`, - { stdio: 'inherit' } - ); -}); - -test('setup page is reachable after login', async ({ page }) => { - const loginPage = new NeosLoginPage(page); - await loginPage.goto(); - await loginPage.login('e2eadmin', 'password123'); - - await page.goto('/neos/second-factor-setup'); - - await expect(page).toHaveURL(/second-factor-setup/); - await expect(page.locator('img[style*="max-width"]')).toBeVisible(); - await expect(page.locator('input#secret')).toBeAttached(); -}); - -test('2FA setup can be completed with the custom issuer name', async ({ page }) => { - const loginPage = new NeosLoginPage(page); - const setupPage = new SecondFactorSetupPage(page); - - await loginPage.goto(); - await loginPage.login('e2eadmin', 'password123'); - - await page.goto('/neos/second-factor-setup'); - await setupPage.waitForPage(); - - const secret = await setupPage.getSecret(); - await setupPage.submitOtp(secret); - - await expect(page).not.toHaveURL(/second-factor-setup/); -}); diff --git a/Tests/E2E/tests/login-without-2fa.spec.ts b/Tests/E2E/tests/login-without-2fa.spec.ts deleted file mode 100644 index ccee0db..0000000 --- a/Tests/E2E/tests/login-without-2fa.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { NeosLoginPage } from '../helpers/pages'; - -test('admin can log in without 2FA configured', async ({ page }) => { - const loginPage = new NeosLoginPage(page); - await loginPage.goto(); - await loginPage.login('e2eadmin', 'password123'); - - // Should land in the Neos backend, not on a 2FA page - await expect(page).not.toHaveURL(/second-factor/); - await expect(page).toHaveURL(/neos/); -}); diff --git a/Tests/E2E/tests/second-factor-login.spec.ts b/Tests/E2E/tests/second-factor-login.spec.ts deleted file mode 100644 index 82c35db..0000000 --- a/Tests/E2E/tests/second-factor-login.spec.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { execSync } from 'node:child_process'; -import { NeosLoginPage, SecondFactorLoginPage, SecondFactorSetupPage } from '../helpers/pages'; -import { generateOtp } from '../helpers/totp'; - -/** - * Tests the 2FA OTP login flow. - * A device is enrolled in beforeAll by navigating to the setup page directly. - * After all tests, the device is removed via the Flow CLI command. - */ - -const CONTAINER = `${process.env.SUT || 'neos8'}-neos-1`; - -let enrolledSecret: string; - -test.beforeAll(async ({ browser }) => { - const page = await browser.newPage(); - const loginPage = new NeosLoginPage(page); - const setupPage = new SecondFactorSetupPage(page); - - await loginPage.goto(); - await loginPage.login('e2eadmin', 'password123'); - await page.goto('/neos/second-factor-setup'); - await setupPage.waitForPage(); - - enrolledSecret = await setupPage.getSecret(); - await setupPage.submitOtp(enrolledSecret); - - await page.close(); -}); - -test.afterAll(() => { - execSync( - `docker exec -u www-data -w /app ${CONTAINER} ./flow secondFactor:deleteForAccount --username e2eadmin`, - { stdio: 'inherit' } - ); -}); - -test('login redirects to OTP page when a 2FA device is enrolled', async ({ page }) => { - const loginPage = new NeosLoginPage(page); - const otpPage = new SecondFactorLoginPage(page); - - await loginPage.goto(); - await loginPage.login('e2eadmin', 'password123'); - - await otpPage.waitForPage(); - await expect(page).toHaveURL(/second-factor-login/); -}); - -test('entering a valid OTP grants backend access', async ({ page }) => { - const loginPage = new NeosLoginPage(page); - const otpPage = new SecondFactorLoginPage(page); - - await loginPage.goto(); - await loginPage.login('e2eadmin', 'password123'); - - await otpPage.waitForPage(); - await otpPage.enterOtp(generateOtp(enrolledSecret)); - - await expect(page).not.toHaveURL(/second-factor/); - await expect(page).toHaveURL(/neos/); -}); - -test('entering an invalid OTP shows an error and stays on the OTP page', async ({ page }) => { - const loginPage = new NeosLoginPage(page); - const otpPage = new SecondFactorLoginPage(page); - - await loginPage.goto(); - await loginPage.login('e2eadmin', 'password123'); - - await otpPage.waitForPage(); - await otpPage.enterOtp('000000'); - - await expect(page).toHaveURL(/second-factor-login/); - const error = await otpPage.getErrorMessage(); - expect(error).toBeTruthy(); -}); diff --git a/Tests/E2E/tests/second-factor-setup.spec.ts b/Tests/E2E/tests/second-factor-setup.spec.ts deleted file mode 100644 index 9f81ddd..0000000 --- a/Tests/E2E/tests/second-factor-setup.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { test, expect } from '@playwright/test'; -import { execSync } from 'node:child_process'; -import { NeosLoginPage, SecondFactorSetupPage } from '../helpers/pages'; - -/** - * Tests the 2FA setup flow. - * The setup page is accessible to any authenticated user regardless of enforcement, - * so tests navigate to it directly after login. - */ - -const CONTAINER = `${process.env.SUT || 'neos8'}-neos-1`; - -function deleteSecondFactors(username: string) { - execSync( - `docker exec -u www-data -w /app ${CONTAINER} ./flow secondFactor:deleteForAccount --username ${username}`, - { stdio: 'inherit' } - ); -} - -test.afterEach(async () => { - deleteSecondFactors('e2eadmin'); -}); - -test('setup page is accessible to authenticated users', async ({ page }) => { - const loginPage = new NeosLoginPage(page); - await loginPage.goto(); - await loginPage.login('e2eadmin', 'password123'); - - await page.goto('/neos/second-factor-setup'); - - await expect(page).toHaveURL(/second-factor-setup/); - await expect(page.locator('input#secret')).toBeAttached(); -}); - -test('completing setup with a valid OTP redirects away from the setup page', async ({ page }) => { - const loginPage = new NeosLoginPage(page); - const setupPage = new SecondFactorSetupPage(page); - - await loginPage.goto(); - await loginPage.login('e2eadmin', 'password123'); - - await page.goto('/neos/second-factor-setup'); - - const secret = await setupPage.getSecret(); - await setupPage.submitOtp(secret); - - await expect(page).not.toHaveURL(/second-factor-setup/); -}); diff --git a/Tests/E2E/tsconfig.json b/Tests/E2E/tsconfig.json index 304009c..48e839a 100644 --- a/Tests/E2E/tsconfig.json +++ b/Tests/E2E/tsconfig.json @@ -34,10 +34,11 @@ // Recommended Options "strict": true, "jsx": "react-jsx", - "verbatimModuleSyntax": true, + "verbatimModuleSyntax": false, "isolatedModules": true, "noUncheckedSideEffectImports": true, "moduleDetection": "force", "skipLibCheck": true, + "allowImportingTsExtensions": true, } } diff --git a/Tests/sytem_under_test/neos8/docker-compose.yaml b/Tests/sytem_under_test/neos8/docker-compose.yaml index 3b1a5eb..08f20d0 100644 --- a/Tests/sytem_under_test/neos8/docker-compose.yaml +++ b/Tests/sytem_under_test/neos8/docker-compose.yaml @@ -40,7 +40,7 @@ services: MARIADB_PASSWORD: 'neos' MARIADB_AUTO_UPGRADE: 1 volumes: - - db:/var/lib/mysql + - db_neos8_data:/var/lib/mysql command: ['mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci'] redis: @@ -50,7 +50,7 @@ services: - neos8_SUT volumes: - db: + db_neos8_data: networks: neos8_SUT: diff --git a/Tests/sytem_under_test/neos8/sut-files/entrypoint.sh b/Tests/sytem_under_test/neos8/sut-files/entrypoint.sh index 95d57de..8e6130c 100755 --- a/Tests/sytem_under_test/neos8/sut-files/entrypoint.sh +++ b/Tests/sytem_under_test/neos8/sut-files/entrypoint.sh @@ -18,8 +18,5 @@ yes y | ./flow resource:clean || true ./flow resource:publish --collection static -./flow user:create --roles Neos.Neos:Administrator e2eadmin password123 E2E Admin -./flow user:create --roles Neos.Neos:Editor e2eeditor password123 E2E Editor - # 2. We now can start caretakerd, which will run the remaining steps in parallel and restart the container if they fail. /usr/bin/caretakerd run diff --git a/Tests/sytem_under_test/neos8/sut-files/etc/frankenphp/Caddyfile b/Tests/sytem_under_test/neos8/sut-files/etc/frankenphp/Caddyfile index c2d5f0f..c757047 100644 --- a/Tests/sytem_under_test/neos8/sut-files/etc/frankenphp/Caddyfile +++ b/Tests/sytem_under_test/neos8/sut-files/etc/frankenphp/Caddyfile @@ -17,7 +17,7 @@ } :8081 { - log + # log root /app/Web encode zstd br gzip diff --git a/Tests/sytem_under_test/neos9/docker-compose.yaml b/Tests/sytem_under_test/neos9/docker-compose.yaml index 1bd366e..d6a11f1 100644 --- a/Tests/sytem_under_test/neos9/docker-compose.yaml +++ b/Tests/sytem_under_test/neos9/docker-compose.yaml @@ -40,7 +40,7 @@ services: MARIADB_PASSWORD: 'neos' MARIADB_AUTO_UPGRADE: 1 volumes: - - db:/var/lib/mysql + - db_neos9_data:/var/lib/mysql command: ['mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci'] redis: @@ -50,7 +50,7 @@ services: - neos9_SUT volumes: - db: + db_neos9_data: networks: neos9_SUT: diff --git a/Tests/sytem_under_test/neos9/sut-files/entrypoint.sh b/Tests/sytem_under_test/neos9/sut-files/entrypoint.sh index 2e5ed1a..53d1b8a 100755 --- a/Tests/sytem_under_test/neos9/sut-files/entrypoint.sh +++ b/Tests/sytem_under_test/neos9/sut-files/entrypoint.sh @@ -21,8 +21,5 @@ yes y | ./flow resource:clean || true ./flow resource:publish --collection static -./flow user:create --roles Neos.Neos:Administrator e2eadmin password123 E2E Admin -./flow user:create --roles Neos.Neos:Editor e2eeditor password123 E2E Editor - # 2. We now can start caretakerd, which will run the remaining steps in parallel and restart the container if they fail. /usr/bin/caretakerd run diff --git a/Tests/sytem_under_test/neos9/sut-files/etc/frankenphp/Caddyfile b/Tests/sytem_under_test/neos9/sut-files/etc/frankenphp/Caddyfile index c2d5f0f..c757047 100644 --- a/Tests/sytem_under_test/neos9/sut-files/etc/frankenphp/Caddyfile +++ b/Tests/sytem_under_test/neos9/sut-files/etc/frankenphp/Caddyfile @@ -17,7 +17,7 @@ } :8081 { - log + # log root /app/Web encode zstd br gzip diff --git a/composer.json b/composer.json index 6ee86e9..ed9b83f 100644 --- a/composer.json +++ b/composer.json @@ -42,5 +42,10 @@ "Neos.Flow-20220318174300", "Neos.Fusion-20220326120900" ] + }, + "config": { + "allow-plugins": { + "neos/composer-plugin": true + } } } From 3771bbb2dd7f517198e95bbb12d78de142a280d7 Mon Sep 17 00:00:00 2001 From: Robert Baruck <robert.baruck@sandstorm.de> Date: Thu, 26 Mar 2026 10:23:25 +0100 Subject: [PATCH 04/12] TASK: Tests pass for neos8 & neos9; Bugfix for neos9 (persistenceManager in ActionController) --- Classes/Controller/BackendController.php | 4 +++- Tests/E2E/helpers/state.ts | 1 + Tests/E2E/helpers/system.ts | 6 ------ 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Classes/Controller/BackendController.php b/Classes/Controller/BackendController.php index 0ba7c48..000462a 100644 --- a/Classes/Controller/BackendController.php +++ b/Classes/Controller/BackendController.php @@ -189,7 +189,9 @@ public function deleteAction(SecondFactor $secondFactor): void if ($isAdministrator || ($isOwner && $this->secondFactorService->canOneSecondFactorBeDeletedForAccount($account))) { // User is admin or has more than one second factor $this->secondFactorRepository->remove($secondFactor); - $this->persistenceManager->persistAll(); + // neos8 backwards compatibility + $this->persistenceManager?->persistAll(); + $this->addFlashMessage( $this->translator->translateById( 'module.index.delete.flashMessage.secondFactorDeleted', diff --git a/Tests/E2E/helpers/state.ts b/Tests/E2E/helpers/state.ts index 17a3266..9d7b9ac 100644 --- a/Tests/E2E/helpers/state.ts +++ b/Tests/E2E/helpers/state.ts @@ -1,3 +1,4 @@ export const state = { + // used to track otp secrets (for testing deviceName-secret is enough) - when writing a test with multiple equal device names this would not suffice anymore deviceNameSecretMap: new Map<string, string>(), } diff --git a/Tests/E2E/helpers/system.ts b/Tests/E2E/helpers/system.ts index 23d2011..97b3a50 100644 --- a/Tests/E2E/helpers/system.ts +++ b/Tests/E2E/helpers/system.ts @@ -18,12 +18,6 @@ export function removeAllUsers() { ) } -export function removeAllSecondFactors() { - execSync( - `docker exec -u www-data -w /app ${CONTAINER} bash -c "./flow secondFactor:deleteAll"`, - { stdio: 'inherit', cwd: dirname('.') }) -} - export async function logout(page: Page) { await page.context().request.post('/neos/logout'); } From 5aff382890d6d66b75cbc73f8f44c62e407d9ee2 Mon Sep 17 00:00:00 2001 From: Robert Baruck <robert.baruck@sandstorm.de> Date: Thu, 26 Mar 2026 11:05:54 +0100 Subject: [PATCH 05/12] TASK: Readme & Makefile --- Makefile | 63 +++++++++++++++++++ README.md | 80 +++++++++++++++++++++++++ Tests/E2E/package.json | 2 +- Tests/sytem_under_test/neos9/Dockerfile | 12 ++-- 4 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f6c2dce --- /dev/null +++ b/Makefile @@ -0,0 +1,63 @@ +NEOS8_COMPOSE = Tests/sytem_under_test/neos8/docker-compose.yaml +NEOS9_COMPOSE = Tests/sytem_under_test/neos9/docker-compose.yaml +E2E_DIR = Tests/E2E + +.PHONY: setup-test test test-neos8 test-neos9 \ + test-neos8-defaults test-neos8-enforce-all test-neos8-enforce-role test-neos8-enforce-provider test-neos8-issuer-name \ + test-neos9-defaults test-neos9-enforce-all test-neos9-enforce-role test-neos9-enforce-provider test-neos9-issuer-name \ + down + +# initial setup +setup-test: + cd $(E2E_DIR) && \ + if [ -s "$$HOME/.nvm/nvm.sh" ]; then \ + . "$$HOME/.nvm/nvm.sh" && nvm install; \ + fi && \ + npm install && npx playwright install + +## Run all E2E tests +test: + cd $(E2E_DIR) && npm test + +## Run all neos8 E2E tests +test-neos8: + cd $(E2E_DIR) && npm run test:neos8:defaults && npm run test:neos8:enforce-all && npm run test:neos8:enforce-role && npm run test:neos8:enforce-provider && npm run test:neos8:issuer-name + +## Run all neos9 E2E tests +test-neos9: + cd $(E2E_DIR) && npm run test:neos9:defaults && npm run test:neos9:enforce-all && npm run test:neos9:enforce-role && npm run test:neos9:enforce-provider && npm run test:neos9:issuer-name + +test-neos8-defaults: + cd $(E2E_DIR) && npm run test:neos8:defaults + +test-neos8-enforce-all: + cd $(E2E_DIR) && npm run test:neos8:enforce-all + +test-neos8-enforce-role: + cd $(E2E_DIR) && npm run test:neos8:enforce-role + +test-neos8-enforce-provider: + cd $(E2E_DIR) && npm run test:neos8:enforce-provider + +test-neos8-issuer-name: + cd $(E2E_DIR) && npm run test:neos8:issuer-name + +test-neos9-defaults: + cd $(E2E_DIR) && npm run test:neos9:defaults + +test-neos9-enforce-all: + cd $(E2E_DIR) && npm run test:neos9:enforce-all + +test-neos9-enforce-role: + cd $(E2E_DIR) && npm run test:neos9:enforce-role + +test-neos9-enforce-provider: + cd $(E2E_DIR) && npm run test:neos9:enforce-provider + +test-neos9-issuer-name: + cd $(E2E_DIR) && npm run test:neos9:issuer-name + +## Tear down all docker compose environments and remove volumes +down: + docker compose -f $(NEOS8_COMPOSE) down -v + docker compose -f $(NEOS9_COMPOSE) down -v diff --git a/README.md b/README.md index 4573608..d59ee68 100644 --- a/README.md +++ b/README.md @@ -177,3 +177,83 @@ causes the same exception again. We get caught in an endless redirect. The [Neos Flow Security Documentation](https://flowframework.readthedocs.io/en/stable/TheDefinitiveGuide/PartIII/Security.html#multi-factor-authentication-strategy) suggests how to implement a multi-factor-authentication, but this method seems like it was never tested. At the moment of writing it seems like the `authenticationStrategy: allTokens` flag is broken and not usable. + +## Contributing + +### Testing + +The package ships with end-to-end tests built on [Playwright](https://playwright.dev) and written in Gherkin syntax via [playwright-bdd](https://vitalets.github.io/playwright-bdd/). + +#### Running the tests + +Tests require Docker and Node.js. Install dependencies once (if [nvm](https://github.com/nvm-sh/nvm) is available it will automatically switch to the Node version from `.nvmrc`): + +```bash +make setup-test +``` + +Use the Makefile targets from the repository root: + +```bash +make test # run all tests (neos8 + neos9, all configurations) + +make test-neos8 # run all neos8 tests +make test-neos8-defaults # default configuration only +make test-neos8-enforce-all # enforceTwoFactorAuthentication: true +make test-neos8-enforce-role +make test-neos8-enforce-provider +make test-neos8-issuer-name + +make test-neos9 # same targets for neos9 / PHP 8.3 + +make down # tear down all docker compose environments and remove volumes +``` + +Each `npm run test:*` script calls `bddgen` first (to regenerate Playwright specs from the `.feature` files) and then runs Playwright with the appropriate `--grep` tag. + +**Useful environment variables:** + +| Variable | Default | Description | +|---|---|---| +| `REUSE_SUT` | `true` | Skip rebuilding the docker compose SUT if it is already running. Set to `false` to force a rebuild. | +| `HEADLESS` | `true` | Set to `false` to watch the browser while tests run. | + +#### System under test (SUT) + +There are two docker compose environments in `Tests/sytem_under_test/`: + +- `neos8/` — Neos with PHP 8.2 +- `neos9/` — Neos with PHP 8.3 + +Both are built from the repository root as the Docker build context, so the local package source is copied into the container and installed via a Composer path repository. This means every test run tests the _current working tree_ of the package, not a published version. + +#### Configuration variants + +The `FLOW_CONTEXT` environment variable is passed into the docker compose environment via variable substitution, and Flow's hierarchical configuration loading picks up the corresponding `Settings.yaml` from the SUT: + +| Playwright tag | `FLOW_CONTEXT` | What is tested | +|---|---|---| +| `@default-context` | `Production/E2E-SUT` | No enforcement — 2FA is optional | +| `@enforce-for-all` | `Production/E2E-SUT/EnforceForAll` | `enforceTwoFactorAuthentication: true` | +| `@enforce-for-role` | `Production/E2E-SUT/EnforceForRole` | Enforcement scoped to `Neos.Neos:Administrator` | +| `@enforce-for-provider` | `Production/E2E-SUT/EnforceForProvider` | Enforcement scoped to an authentication provider | +| `@issuer-name-change` | `Production/E2E-SUT/IssuerNameChange` | Custom `issuerName` setting | + +#### Test isolation + +Each scenario starts with a clean state. An `AfterScenario` hook runs after every scenario to: + +1. Log the browser out via a POST to `/neos/logout` +2. Delete all Neos users (`./flow user:delete --assume-yes '*'`) + +Deleting all users also cascades to their 2FA devices, so no separate cleanup step is needed. Users and devices are re-created by the Background steps at the start of each scenario. + +#### Design decisions + +**Gherkin / BDD over plain Playwright specs** — the feature files document the intended behaviour of each configuration variant at a level that is readable without knowing the implementation. The generated Playwright spec files (`.features-gen/`) are not committed; they are re-generated by `bddgen` before each test run. + +**UI-only device enrolment** — 2FA devices are enrolled through the browser UI (the backend module or the setup page) rather than a dedicated CLI command. This avoids coupling the tests to internal persistence details and exercises the same enrolment path a real user would take. The `deviceNameSecretMap` in `helpers/state.ts` carries TOTP secrets across steps within a scenario (e.g. from the enrolment step to the OTP entry step). + +**Sequential execution** — tests run with `workers: 1` and `fullyParallel: false` because all scenarios share a single running SUT container and a single database. Running them in parallel would cause interference between scenarios. + +**User creation via `docker exec`** — Neos user creation is done through the Flow CLI (`./flow user:create`) rather than the UI because the UI path is not part of what this package tests, and using the CLI is faster and more reliable for setup. diff --git a/Tests/E2E/package.json b/Tests/E2E/package.json index d21652e..ba8662c 100644 --- a/Tests/E2E/package.json +++ b/Tests/E2E/package.json @@ -14,7 +14,7 @@ "test:neos9:enforce-role": "npm run generate-tests; SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole npx playwright test --grep @enforce-for-role", "test:neos9:enforce-provider": "npm run generate-tests; SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider npx playwright test --grep @enforce-for-provider", "test:neos9:issuer-name": "npm run generate-tests; SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/IssuerNameChange npx playwright test --grep @issuer-name-change", - "test": "npm run generate-tests; npm run test:neos8 && npm run test:neos8:enforce-all && npm run test:neos8:enforce-role && npm run test:neos8:enforce-provider && npm run test:neos8:issuer-name && npm run test:neos9 && npm run test:neos9:enforce-all && npm run test:neos9:enforce-role && npm run test:neos9:enforce-provider && npm run test:neos9:issuer-name" + "test": "npm run generate-tests; npm run test:neos8:defaults && npm run test:neos8:enforce-all && npm run test:neos8:enforce-role && npm run test:neos8:enforce-provider && npm run test:neos8:issuer-name && npm run test:neos9:defaults && npm run test:neos9:enforce-all && npm run test:neos9:enforce-role && npm run test:neos9:enforce-provider && npm run test:neos9:issuer-name" }, "devDependencies": { "@types/node": "^25.5.0", diff --git a/Tests/sytem_under_test/neos9/Dockerfile b/Tests/sytem_under_test/neos9/Dockerfile index 44ea46b..90de457 100644 --- a/Tests/sytem_under_test/neos9/Dockerfile +++ b/Tests/sytem_under_test/neos9/Dockerfile @@ -1,4 +1,4 @@ -FROM dunglas/frankenphp:1-php8.3-trixie +FROM dunglas/frankenphp:1-php8.5-trixie COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer @@ -23,9 +23,9 @@ ARG USER=www-data # Give write access to /config/caddy and /data/caddy RUN \ - useradd ${USER}; \ - chown -R ${USER}:${USER} /config/caddy /data/caddy \ - && touch /var/run/caretakerd.key && chown ${USER}:${USER} /var/run/caretakerd.key + useradd ${USER}; \ + chown -R ${USER}:${USER} /config/caddy /data/caddy \ + && touch /var/run/caretakerd.key && chown ${USER}:${USER} /var/run/caretakerd.key # Add Caretaker (Startup manager) ARG TARGETARCH @@ -39,7 +39,7 @@ RUN echo '. /etc/bash.vips-arm64-hotfix.sh' >> /etc/bash.bashrc # Install Neos base distribution RUN --mount=type=cache,target=/root/.composer rm -rf /app \ - && composer create-project neos/neos-base-distribution:^9 /app + && composer create-project neos/neos-base-distribution:^9 /app --no-dev RUN --mount=type=cache,target=/root/.composer composer require rokka/imagine-vips:0.* @@ -53,7 +53,7 @@ RUN --mount=type=cache,target=/root/.composer \ && composer require sandstorm/neostwofactorauthentication:@dev # Add config files -ADD Tests/sytem_under_test/neos9/sut-files/ / +COPY Tests/sytem_under_test/neos9/sut-files/ / # chown for neos data folder and Resources ONLY RUN mkdir -p /app/Data /app/Web/_Resources \ From 8053dad448b242a4e3178d127eebf89b7de1f6c0 Mon Sep 17 00:00:00 2001 From: Robert Baruck <robert.baruck@sandstorm.de> Date: Thu, 26 Mar 2026 12:09:40 +0100 Subject: [PATCH 06/12] TASK: Readme & Makefile --- Makefile | 41 +++++++++++++++++++++------------- README.md | 14 +++++------- Tests/E2E/package.json | 5 +++-- Tests/E2E/playwright.config.ts | 13 +++-------- 4 files changed, 38 insertions(+), 35 deletions(-) diff --git a/Makefile b/Makefile index f6c2dce..868c43a 100644 --- a/Makefile +++ b/Makefile @@ -1,31 +1,41 @@ -NEOS8_COMPOSE = Tests/sytem_under_test/neos8/docker-compose.yaml -NEOS9_COMPOSE = Tests/sytem_under_test/neos9/docker-compose.yaml -E2E_DIR = Tests/E2E +NEOS8_COMPOSE = $(CURDIR)/Tests/sytem_under_test/neos8/docker-compose.yaml +NEOS9_COMPOSE = $(CURDIR)/Tests/sytem_under_test/neos9/docker-compose.yaml +E2E_DIR = $(CURDIR)/Tests/E2E -.PHONY: setup-test test test-neos8 test-neos9 \ +.PHONY: setup-test generate-bdd-files test test-neos8 test-neos9 \ test-neos8-defaults test-neos8-enforce-all test-neos8-enforce-role test-neos8-enforce-provider test-neos8-issuer-name \ test-neos9-defaults test-neos9-enforce-all test-neos9-enforce-role test-neos9-enforce-provider test-neos9-issuer-name \ down +# COLORS +GREEN := $(shell tput -Txterm setaf 2) +YELLOW := $(shell tput -Txterm setaf 3) +RESET := $(shell tput -Txterm sgr0) + # initial setup setup-test: + @echo '${GREEN}Installing test setup.${RESET}' && \ cd $(E2E_DIR) && \ - if [ -s "$$HOME/.nvm/nvm.sh" ]; then \ - . "$$HOME/.nvm/nvm.sh" && nvm install; \ + if [ -s "$$NVM_DIR" ]; then \ + echo '${GREEN}Found nvm on system, using it to install nodejs!${RESET}'; \ + . "$$NVM_DIR/nvm.sh" && nvm install; \ fi && \ - npm install && npx playwright install + npm install && npx playwright install --with-deps chromium && \ + echo '' && echo '${GREEN}generate BDD files from feature files${RESET}' && npm run generate-tests + +# generate BDD files from feature files +generate-bdd-files: + @echo '${GREEN}generate BDD files from feature files${RESET}'; \ + cd $(E2E_DIR) && npm run generate-tests ## Run all E2E tests -test: - cd $(E2E_DIR) && npm test +test: test-neos8 test-neos9 ## Run all neos8 E2E tests -test-neos8: - cd $(E2E_DIR) && npm run test:neos8:defaults && npm run test:neos8:enforce-all && npm run test:neos8:enforce-role && npm run test:neos8:enforce-provider && npm run test:neos8:issuer-name +test-neos8: test-neos8-defaults test-neos8-enforce-all ## Run all neos9 E2E tests -test-neos9: - cd $(E2E_DIR) && npm run test:neos9:defaults && npm run test:neos9:enforce-all && npm run test:neos9:enforce-role && npm run test:neos9:enforce-provider && npm run test:neos9:issuer-name +test-neos9: test-neos9-defaults test-neos9-enforce-all test-neos8-defaults: cd $(E2E_DIR) && npm run test:neos8:defaults @@ -59,5 +69,6 @@ test-neos9-issuer-name: ## Tear down all docker compose environments and remove volumes down: - docker compose -f $(NEOS8_COMPOSE) down -v - docker compose -f $(NEOS9_COMPOSE) down -v + @echo '${YELLOW}Shutting down all SUTs and removing their volumes.${RESET}' + @docker compose -f $(NEOS8_COMPOSE) down -v + @docker compose -f $(NEOS9_COMPOSE) down -v diff --git a/README.md b/README.md index d59ee68..24a73ef 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,11 @@ Tests require Docker and Node.js. Install dependencies once (if [nvm](https://gi make setup-test ``` +Re-generate Playwright spec files whenever a `.feature` file changes: +```bash +make generate-bdd-files +``` + Use the Makefile targets from the repository root: ```bash @@ -211,19 +216,12 @@ make down # tear down all docker compose environments and remo Each `npm run test:*` script calls `bddgen` first (to regenerate Playwright specs from the `.feature` files) and then runs Playwright with the appropriate `--grep` tag. -**Useful environment variables:** - -| Variable | Default | Description | -|---|---|---| -| `REUSE_SUT` | `true` | Skip rebuilding the docker compose SUT if it is already running. Set to `false` to force a rebuild. | -| `HEADLESS` | `true` | Set to `false` to watch the browser while tests run. | - #### System under test (SUT) There are two docker compose environments in `Tests/sytem_under_test/`: - `neos8/` — Neos with PHP 8.2 -- `neos9/` — Neos with PHP 8.3 +- `neos9/` — Neos with PHP 8.5 Both are built from the repository root as the Docker build context, so the local package source is copied into the container and installed via a Composer path repository. This means every test run tests the _current working tree_ of the package, not a published version. diff --git a/Tests/E2E/package.json b/Tests/E2E/package.json index ba8662c..a876e6f 100644 --- a/Tests/E2E/package.json +++ b/Tests/E2E/package.json @@ -4,17 +4,18 @@ "type": "module", "scripts": { "generate-tests": "npx bddgen", + "test:neos8:defaults": "npm run generate-tests; SUT=neos8 npx playwright test --grep @default-context", "test:neos8:enforce-all": "npm run generate-tests; SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForAll npx playwright test --grep @enforce-for-all", "test:neos8:enforce-role": "npm run generate-tests; SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole npx playwright test --grep @enforce-for-role", "test:neos8:enforce-provider": "npm run generate-tests; SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider npx playwright test --grep @enforce-for-provider", "test:neos8:issuer-name": "npm run generate-tests; SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/IssuerNameChange npx playwright test --grep @issuer-name-change", + "test:neos9:defaults": "npm run generate-tests; SUT=neos9 npx playwright test --grep @default-context", "test:neos9:enforce-all": "npm run generate-tests; SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForAll npx playwright test --grep @enforce-for-all", "test:neos9:enforce-role": "npm run generate-tests; SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole npx playwright test --grep @enforce-for-role", "test:neos9:enforce-provider": "npm run generate-tests; SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider npx playwright test --grep @enforce-for-provider", - "test:neos9:issuer-name": "npm run generate-tests; SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/IssuerNameChange npx playwright test --grep @issuer-name-change", - "test": "npm run generate-tests; npm run test:neos8:defaults && npm run test:neos8:enforce-all && npm run test:neos8:enforce-role && npm run test:neos8:enforce-provider && npm run test:neos8:issuer-name && npm run test:neos9:defaults && npm run test:neos9:enforce-all && npm run test:neos9:enforce-role && npm run test:neos9:enforce-provider && npm run test:neos9:issuer-name" + "test:neos9:issuer-name": "npm run generate-tests; SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/IssuerNameChange npx playwright test --grep @issuer-name-change" }, "devDependencies": { "@types/node": "^25.5.0", diff --git a/Tests/E2E/playwright.config.ts b/Tests/E2E/playwright.config.ts index ba52d5a..2c855c0 100644 --- a/Tests/E2E/playwright.config.ts +++ b/Tests/E2E/playwright.config.ts @@ -1,16 +1,11 @@ import { defineConfig, devices } from '@playwright/test'; import { defineBddConfig } from 'playwright-bdd'; +// env API to select system under test (SUT) (neos8 | neos9) and flow context for the configuration to be used (default, enforce for all users, etc.) const SUT = process.env.SUT || 'neos8'; const FLOW_CONTEXT = process.env.FLOW_CONTEXT || 'Production/E2E-SUT'; -const HEADLESS = process.env.HEADLESS !== 'false'; -const REUSE_SUT = process.env.REUSE_SUT == null ? true : !!process.env.REUSE_SUT; -const sutDir = `../sytem_under_test/${SUT}`; -console.log('### Loading config with env') -console.table({ - SUT, FLOW_CONTEXT, HEADLESS, REUSE_SUT -}) +const sutDir = `../sytem_under_test/${SUT}`; const testDir = defineBddConfig({ features: 'features/**/*.feature', @@ -24,15 +19,13 @@ export default defineConfig({ retries: 0, use: { baseURL: 'http://localhost:8081', - headless: HEADLESS, trace: 'on-first-retry', screenshot: "only-on-failure", }, globalTeardown: './global-teardown.ts', webServer: { - command: `FLOW_CONTEXT=${FLOW_CONTEXT} docker compose -f ${sutDir}/docker-compose.yaml up --build`, + command: `echo "starting SUT ${SUT} with context ${FLOW_CONTEXT}"; FLOW_CONTEXT=${FLOW_CONTEXT} docker compose -f ${sutDir}/docker-compose.yaml up --build`, url: 'http://localhost:8081/', - reuseExistingServer: REUSE_SUT, timeout: 120_000, stdout: 'pipe', stderr: 'pipe', From 907bc3774461da040a382436af8eec4c792cc3e0 Mon Sep 17 00:00:00 2001 From: Robert Baruck <robert.baruck@sandstorm.de> Date: Thu, 26 Mar 2026 14:25:50 +0100 Subject: [PATCH 07/12] TASK: Tests for enforce-for-role --- Makefile | 13 +++-- README.md | 12 ++++- Resources/Public/Styles/Login.css | 4 ++ .../enforce-for-role/backend-module.feature | 53 +++++++++++++++++++ .../features/enforce-for-role/login.feature | 31 +++++++++++ Tests/E2E/helpers/pages.ts | 23 ++++++++ Tests/E2E/package.json | 20 +++---- Tests/E2E/playwright.config.ts | 2 +- Tests/E2E/steps/backend-module.steps.ts | 8 +++ .../E2E-SUT/EnforceForRole/Settings.2FA.yaml | 2 +- .../Production/E2E-SUT/Policy.yaml | 5 ++ Tests/sytem_under_test/neos9/Dockerfile | 3 +- .../E2E-SUT/EnforceForRole/Settings.2FA.yaml | 2 +- .../Production/E2E-SUT/Policy.yaml | 5 ++ 14 files changed, 165 insertions(+), 18 deletions(-) create mode 100644 Tests/E2E/features/enforce-for-role/backend-module.feature create mode 100644 Tests/E2E/features/enforce-for-role/login.feature create mode 100644 Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Policy.yaml create mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Policy.yaml diff --git a/Makefile b/Makefile index 868c43a..13b5fa4 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ E2E_DIR = $(CURDIR)/Tests/E2E .PHONY: setup-test generate-bdd-files test test-neos8 test-neos9 \ test-neos8-defaults test-neos8-enforce-all test-neos8-enforce-role test-neos8-enforce-provider test-neos8-issuer-name \ test-neos9-defaults test-neos9-enforce-all test-neos9-enforce-role test-neos9-enforce-provider test-neos9-issuer-name \ - down + enter-neos8 enter-neos9 down # COLORS GREEN := $(shell tput -Txterm setaf 2) @@ -32,10 +32,10 @@ generate-bdd-files: test: test-neos8 test-neos9 ## Run all neos8 E2E tests -test-neos8: test-neos8-defaults test-neos8-enforce-all +test-neos8: test-neos8-defaults test-neos8-enforce-all test-neos8-enforce-role ## Run all neos9 E2E tests -test-neos9: test-neos9-defaults test-neos9-enforce-all +test-neos9: test-neos9-defaults test-neos9-enforce-all test-neos9-enforce-role test-neos8-defaults: cd $(E2E_DIR) && npm run test:neos8:defaults @@ -67,6 +67,13 @@ test-neos9-enforce-provider: test-neos9-issuer-name: cd $(E2E_DIR) && npm run test:neos9:issuer-name +## Enter SUT containers +enter-neos8: + @docker compose -f $(NEOS8_COMPOSE) exec neos bash + +enter-neos9: + @docker compose -f $(NEOS9_COMPOSE) exec neos bash + ## Tear down all docker compose environments and remove volumes down: @echo '${YELLOW}Shutting down all SUTs and removing their volumes.${RESET}' diff --git a/README.md b/README.md index 24a73ef..e68259c 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,17 @@ make test-neos9 # same targets for neos9 / PHP 8.3 make down # tear down all docker compose environments and remove volumes ``` -Each `npm run test:*` script calls `bddgen` first (to regenerate Playwright specs from the `.feature` files) and then runs Playwright with the appropriate `--grep` tag. +#### Debugging tests +To debug a test, run the test with flags like this: + +- `make test-neos8-enforce-all -- --debug` - to run the test in headed mode with Playwright Inspector +- `make test-neos8-enforce-all -- --ui` - to run the test in headed mode with Playwright Test Runner UI + +If you just want to see the test running in the browser just `make test-neos8-enforce-all -- --headed`. + +> While debugging you can also enter the SUT with `make enter-neos8` and `make enter-neos9` respectively. +> +> You can even the tests you want to debug with `make test-neos8-enforce-all -- --grep @debug` and adding the `@debug` tag to the scenario you want to debug. But using the --ui flag is usually more convenient for debugging. #### System under test (SUT) diff --git a/Resources/Public/Styles/Login.css b/Resources/Public/Styles/Login.css index 28d9b6f..0634492 100644 --- a/Resources/Public/Styles/Login.css +++ b/Resources/Public/Styles/Login.css @@ -159,3 +159,7 @@ .neos-two-factor__secret-wrapper ::-webkit-scrollbar-track { background-color: initial; } + +.neos-control-group > * + * { + margin-top: 8px; +} diff --git a/Tests/E2E/features/enforce-for-role/backend-module.feature b/Tests/E2E/features/enforce-for-role/backend-module.feature new file mode 100644 index 0000000..1c6402b --- /dev/null +++ b/Tests/E2E/features/enforce-for-role/backend-module.feature @@ -0,0 +1,53 @@ +@enforce-for-role +Feature: Backend module with 2FA enforced for administrators only + + # Requires FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole + # Config: enforce2FAForRoles: ['Neos.Neos:Administrator', 'Neos.Neos:SecondFactorUser'] + + Background: + Given A user with username "admin", password "password" and role "Neos.Neos:Administrator" with enrolled 2FA device with name "Admin Initial Device" exists + And A user with username "editor", password "password" and role "Neos.Neos:Editor" exists + And A user with username "secondFactorUser", password "password" and role "Neos.Neos:SecondFactorUser" with enrolled 2FA device with name "2FA-User Initial Device" exists + + Scenario: Administrator must enter TOTP before accessing the backend module + When I log in with username "admin" and password "password" + And I enter a valid TOTP for device "Admin Initial Device" + And I navigate to the 2FA management page + Then There should be a 2FA device with the name "Admin Initial Device" + + Scenario: Editor can access the backend module without 2FA + When I log in with username "editor" and password "password" + And I navigate to the 2FA management page + And I add a new TOTP 2FA device with name "Editor Test Device" + Then There should be 1 enrolled 2FA device + And There should be a 2FA device with the name "Editor Test Device" + + Scenario: Editor can remove their own last 2FA device when 2FA is not enforced for their role + When I log in with username "editor" and password "password" + And I navigate to the 2FA management page + And I add a new TOTP 2FA device with name "Editor Test Device" + And I remove the 2FA device with the name "Editor Test Device" + Then There should be 0 enrolled 2FA devices + + Scenario: Administrator can remove another user's last 2FA device, even when 2FA is enforced for their role + When I log in with username "admin" and password "password" + And I enter a valid TOTP for device "Admin Initial Device" + And I navigate to the 2FA management page + And I remove the 2FA device with the name "2FA-User Initial Device" + Then There should be no 2FA device with the name "2FA-User Initial Device" + + Scenario: Administrator can remove their own last 2FA device even when 2FA is enforced for their role + When I log in with username "admin" and password "password" + And I enter a valid TOTP for device "Admin Initial Device" + And I navigate to the 2FA management page + And I remove the 2FA device with the name "Admin Initial Device" + Then There should be 1 enrolled 2FA device + And There should be no 2FA device with the name "Admin Initial Device" + + Scenario: User with role "Neos.Neos:SecondFactorUser" must enter TOTP before accessing the backend module + When I log in with username "secondFactorUser" and password "password" + And I enter a valid TOTP for device "2FA-User Initial Device" + And I navigate to the 2FA management page + And I remove the 2FA device with the name "2FA-User Initial Device" + Then There should be 1 enrolled 2FA device + And There should be a 2FA device with the name "2FA-User Initial Device" diff --git a/Tests/E2E/features/enforce-for-role/login.feature b/Tests/E2E/features/enforce-for-role/login.feature new file mode 100644 index 0000000..f69c672 --- /dev/null +++ b/Tests/E2E/features/enforce-for-role/login.feature @@ -0,0 +1,31 @@ +@enforce-for-role +Feature: Login flow with 2FA enforced for administrators only + + # Requires FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole + # Config: enforce2FAForRoles: ['Neos.Neos:Administrator'] + + Background: + Given A user with username "admin", password "password" and role "Neos.Neos:Administrator" exists + And A user with username "editor", password "password" and role "Neos.Neos:Editor" exists + + Scenario: Administrator is redirected to 2FA setup when no device is enrolled + When I log in with username "admin" and password "password" + Then I should see the 2FA setup page + And I cannot access the Neos content page + + Scenario: Administrator can log in after setting up a 2FA device + When I log in with username "admin" and password "password" + And I set up a 2FA device with name "Admin Test Device" + Then I should see the Neos content page + + Scenario: Editor can log in without 2FA when no device is enrolled + When I log in with username "editor" and password "password" + Then I should see the Neos content page + + Scenario: Editor still has to enter a TOTP code when a device is enrolled + When I log in with username "editor" and password "password" + And I navigate to the 2FA management page + And I add a new TOTP 2FA device with name "Editor Test Device" + And I log out + And I log in with username "editor" and password "password" + Then I should see the 2FA verification page diff --git a/Tests/E2E/helpers/pages.ts b/Tests/E2E/helpers/pages.ts index e5c7308..ebd8183 100644 --- a/Tests/E2E/helpers/pages.ts +++ b/Tests/E2E/helpers/pages.ts @@ -104,6 +104,29 @@ export class BackendModulePage { await this.page.waitForLoadState('networkidle'); } + /** + * Attempt to delete a device by name without assuming success. + * If the delete button is disabled or no confirmation modal appears, the step passes silently. + * Use this when the deletion may be blocked (e.g. last device with enforcement active). + */ + async tryDeleteDeviceByName(name: string): Promise<void> { + const row = this.locatorForDeviceRow(name); + const deleteButton = row.locator('button[data-test-id="delete-second-factor-button"]'); + + if (await deleteButton.isDisabled()) { + return; + } + + await deleteButton.click(); + + const confirmButton = this.page.locator('button[data-test-id="confirm-delete"]:visible'); + if (await confirmButton.isVisible({ timeout: 1000 })) { + await confirmButton.click(); + } + + await this.page.waitForLoadState('networkidle'); + } + /** Locator for the table row matching a device name (for assertions). */ locatorForDeviceRow(name: string) { return this.page.locator('.neos-table tbody tr').filter({ hasText: name }); diff --git a/Tests/E2E/package.json b/Tests/E2E/package.json index a876e6f..c7c2c86 100644 --- a/Tests/E2E/package.json +++ b/Tests/E2E/package.json @@ -5,17 +5,17 @@ "scripts": { "generate-tests": "npx bddgen", - "test:neos8:defaults": "npm run generate-tests; SUT=neos8 npx playwright test --grep @default-context", - "test:neos8:enforce-all": "npm run generate-tests; SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForAll npx playwright test --grep @enforce-for-all", - "test:neos8:enforce-role": "npm run generate-tests; SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole npx playwright test --grep @enforce-for-role", - "test:neos8:enforce-provider": "npm run generate-tests; SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider npx playwright test --grep @enforce-for-provider", - "test:neos8:issuer-name": "npm run generate-tests; SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/IssuerNameChange npx playwright test --grep @issuer-name-change", + "test:neos8:defaults": "npm run generate-tests && SUT=neos8 npx playwright test --grep @default-context", + "test:neos8:enforce-all": "npm run generate-tests && SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForAll npx playwright test --grep @enforce-for-all", + "test:neos8:enforce-role": "npm run generate-tests && SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole npx playwright test --grep @enforce-for-role", + "test:neos8:enforce-provider": "npm run generate-tests && SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider npx playwright test --grep @enforce-for-provider", + "test:neos8:issuer-name": "npm run generate-tests && SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/IssuerNameChange npx playwright test --grep @issuer-name-change", - "test:neos9:defaults": "npm run generate-tests; SUT=neos9 npx playwright test --grep @default-context", - "test:neos9:enforce-all": "npm run generate-tests; SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForAll npx playwright test --grep @enforce-for-all", - "test:neos9:enforce-role": "npm run generate-tests; SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole npx playwright test --grep @enforce-for-role", - "test:neos9:enforce-provider": "npm run generate-tests; SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider npx playwright test --grep @enforce-for-provider", - "test:neos9:issuer-name": "npm run generate-tests; SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/IssuerNameChange npx playwright test --grep @issuer-name-change" + "test:neos9:defaults": "npm run generate-tests && SUT=neos9 npx playwright test --grep @default-context", + "test:neos9:enforce-all": "npm run generate-tests && SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForAll npx playwright test --grep @enforce-for-all", + "test:neos9:enforce-role": "npm run generate-tests && SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole npx playwright test --grep @enforce-for-role", + "test:neos9:enforce-provider": "npm run generate-tests && SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider npx playwright test --grep @enforce-for-provider", + "test:neos9:issuer-name": "npm run generate-tests && SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/IssuerNameChange npx playwright test --grep @issuer-name-change" }, "devDependencies": { "@types/node": "^25.5.0", diff --git a/Tests/E2E/playwright.config.ts b/Tests/E2E/playwright.config.ts index 2c855c0..26b3bdd 100644 --- a/Tests/E2E/playwright.config.ts +++ b/Tests/E2E/playwright.config.ts @@ -26,7 +26,7 @@ export default defineConfig({ webServer: { command: `echo "starting SUT ${SUT} with context ${FLOW_CONTEXT}"; FLOW_CONTEXT=${FLOW_CONTEXT} docker compose -f ${sutDir}/docker-compose.yaml up --build`, url: 'http://localhost:8081/', - timeout: 120_000, + timeout: 600_000, stdout: 'pipe', stderr: 'pipe', }, diff --git a/Tests/E2E/steps/backend-module.steps.ts b/Tests/E2E/steps/backend-module.steps.ts index e6d8129..1a24f89 100644 --- a/Tests/E2E/steps/backend-module.steps.ts +++ b/Tests/E2E/steps/backend-module.steps.ts @@ -20,6 +20,14 @@ When('I add a new TOTP 2FA device with name {string}', }, ); +When('I try to remove the 2FA device with the name {string}', + async ({ page }, name: string) => { + const modulePage = new BackendModulePage(page); + await page.pause(); + await modulePage.tryDeleteDeviceByName(name); + }, +); + // "with name" and "with the name" are both used in feature files When('I remove the 2FA device with the name {string}', async ({ page }, name: string) => { diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml index bb69cc9..e4a4aca 100644 --- a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml +++ b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml @@ -1,4 +1,4 @@ Sandstorm: NeosTwoFactorAuthentication: # enforce 2FA for specific roles (e.g. Neos.Neos:Administrator) - enforce2FAForRoles: ['Neos.Neos:Administrator'] + enforce2FAForRoles: ['Neos.Neos:Administrator', 'Neos.Neos:SecondFactorUser'] diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Policy.yaml b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Policy.yaml new file mode 100644 index 0000000..0eba65e --- /dev/null +++ b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Policy.yaml @@ -0,0 +1,5 @@ +roles: + 'Neos.Neos:SecondFactorUser': + label: 2FA Required User + description: Just for testing role based 2FA enforcement + parentRoles: ['Neos.Neos:Editor'] diff --git a/Tests/sytem_under_test/neos9/Dockerfile b/Tests/sytem_under_test/neos9/Dockerfile index 90de457..2b585b7 100644 --- a/Tests/sytem_under_test/neos9/Dockerfile +++ b/Tests/sytem_under_test/neos9/Dockerfile @@ -38,8 +38,9 @@ RUN curl -SL "https://caretakerd.echocat.org/latest/download/caretakerd-linux-${ RUN echo '. /etc/bash.vips-arm64-hotfix.sh' >> /etc/bash.bashrc # Install Neos base distribution +# TODO: We can not specify `--no-dev` here, because the the package wants to run `./flow welcome` as `"post-create-project-cmd"` but that needs the dev dependencies to be installed. RUN --mount=type=cache,target=/root/.composer rm -rf /app \ - && composer create-project neos/neos-base-distribution:^9 /app --no-dev + && composer create-project neos/neos-base-distribution:^9 /app RUN --mount=type=cache,target=/root/.composer composer require rokka/imagine-vips:0.* diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml index bb69cc9..e4a4aca 100644 --- a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml +++ b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml @@ -1,4 +1,4 @@ Sandstorm: NeosTwoFactorAuthentication: # enforce 2FA for specific roles (e.g. Neos.Neos:Administrator) - enforce2FAForRoles: ['Neos.Neos:Administrator'] + enforce2FAForRoles: ['Neos.Neos:Administrator', 'Neos.Neos:SecondFactorUser'] diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Policy.yaml b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Policy.yaml new file mode 100644 index 0000000..0eba65e --- /dev/null +++ b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Policy.yaml @@ -0,0 +1,5 @@ +roles: + 'Neos.Neos:SecondFactorUser': + label: 2FA Required User + description: Just for testing role based 2FA enforcement + parentRoles: ['Neos.Neos:Editor'] From a26ff2ddb1702b4184ea98e9f25fafbfecd5afa0 Mon Sep 17 00:00:00 2001 From: Robert Baruck <robert.baruck@sandstorm.de> Date: Thu, 26 Mar 2026 16:50:19 +0100 Subject: [PATCH 08/12] TASK: Tests for enforce-for-provider --- Makefile | 4 +- .../backend-module.feature | 44 +++++++++++++++++++ .../enforce-for-provider/login.feature | 31 +++++++++++++ Tests/E2E/global-teardown.ts | 6 +-- Tests/E2E/helpers/system.ts | 4 +- Tests/E2E/playwright.config.ts | 3 ++ 6 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 Tests/E2E/features/enforce-for-provider/backend-module.feature create mode 100644 Tests/E2E/features/enforce-for-provider/login.feature diff --git a/Makefile b/Makefile index 13b5fa4..a226e34 100644 --- a/Makefile +++ b/Makefile @@ -32,10 +32,10 @@ generate-bdd-files: test: test-neos8 test-neos9 ## Run all neos8 E2E tests -test-neos8: test-neos8-defaults test-neos8-enforce-all test-neos8-enforce-role +test-neos8: test-neos8-defaults test-neos8-enforce-all test-neos8-enforce-role test-neos8-enforce-provider ## Run all neos9 E2E tests -test-neos9: test-neos9-defaults test-neos9-enforce-all test-neos9-enforce-role +test-neos9: test-neos9-defaults test-neos9-enforce-all test-neos9-enforce-role test-neos9-enforce-provider test-neos8-defaults: cd $(E2E_DIR) && npm run test:neos8:defaults diff --git a/Tests/E2E/features/enforce-for-provider/backend-module.feature b/Tests/E2E/features/enforce-for-provider/backend-module.feature new file mode 100644 index 0000000..5660bc1 --- /dev/null +++ b/Tests/E2E/features/enforce-for-provider/backend-module.feature @@ -0,0 +1,44 @@ +@enforce-for-provider +Feature: Backend module with 2FA enforced for the Neos.Neos:Backend authentication provider + + # Requires FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider + # Config: enforce2FAForAuthenticationProviders: ['Neos.Neos:Backend'] + + Background: + Given A user with username "admin", password "password" and role "Neos.Neos:Administrator" with enrolled 2FA device with name "Admin Initial Device" exists + And A user with username "editor", password "password" and role "Neos.Neos:Editor" with enrolled 2FA device with name "Editor Initial Device" exists + + Scenario: Admin user must enter TOTP before accessing the backend module + When I log in with username "admin" and password "password" + And I enter a valid TOTP for device "Admin Initial Device" + And I navigate to the 2FA management page + Then There should be a 2FA device with the name "Admin Initial Device" + + Scenario: Editor user must enter TOTP before accessing the backend module + When I log in with username "editor" and password "password" + And I enter a valid TOTP for device "Editor Initial Device" + And I navigate to the 2FA management page + Then There should be a 2FA device with the name "Editor Initial Device" + + Scenario: User can add a 2FA device in the backend module + When I log in with username "editor" and password "password" + And I enter a valid TOTP for device "Editor Initial Device" + And I navigate to the 2FA management page + And I add a new TOTP 2FA device with name "Editor Test Device" + Then There should be a 2FA device with the name "Editor Test Device" + And There should be a 2FA device with the name "Editor Initial Device" + + Scenario: User cannot remove their last 2FA device when 2FA is enforced for their provider + When I log in with username "editor" and password "password" + And I enter a valid TOTP for device "Editor Initial Device" + And I navigate to the 2FA management page + And I remove the 2FA device with the name "Editor Initial Device" + Then There should be 1 enrolled 2FA device + And There should be a 2FA device with the name "Editor Initial Device" + + Scenario: Admin user can remove another user's last 2FA device, even when 2FA is enforced for their provider + When I log in with username "admin" and password "password" + And I enter a valid TOTP for device "Admin Initial Device" + And I navigate to the 2FA management page + And I remove the 2FA device with the name "Editor Initial Device" + Then There should be no 2FA device with the name "Editor Initial Device" diff --git a/Tests/E2E/features/enforce-for-provider/login.feature b/Tests/E2E/features/enforce-for-provider/login.feature new file mode 100644 index 0000000..09eced6 --- /dev/null +++ b/Tests/E2E/features/enforce-for-provider/login.feature @@ -0,0 +1,31 @@ +@enforce-for-provider +Feature: Login flow with 2FA enforced for the Neos.Neos:Backend authentication provider + + # Requires FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider + # Config: enforce2FAForAuthenticationProviders: ['Neos.Neos:Backend'] + + Background: + Given A user with username "admin", password "password" and role "Neos.Neos:Administrator" exists + And A user with username "editor", password "password" and role "Neos.Neos:Editor" exists + + Scenario: Administrator is redirected to 2FA setup when no device is enrolled + When I log in with username "admin" and password "password" + Then I should see the 2FA setup page + And I cannot access the Neos content page + + Scenario: Editor is redirected to 2FA setup when no device is enrolled + When I log in with username "editor" and password "password" + Then I should see the 2FA setup page + And I cannot access the Neos content page + + Scenario: User can log in after setting up a 2FA device + When I log in with username "editor" and password "password" + And I set up a 2FA device with name "Editor Test Device" + Then I should see the Neos content page + + Scenario: User still has to enter a TOTP code when a device is enrolled + When I log in with username "admin" and password "password" + And I set up a 2FA device with name "Admin Test Device" + And I log out + And I log in with username "admin" and password "password" + Then I should see the 2FA verification page diff --git a/Tests/E2E/global-teardown.ts b/Tests/E2E/global-teardown.ts index 1e66205..6cefa77 100644 --- a/Tests/E2E/global-teardown.ts +++ b/Tests/E2E/global-teardown.ts @@ -2,13 +2,9 @@ import { execSync } from 'node:child_process'; import { dirname } from 'node:path'; export default async function globalTeardown() { - if (process.env.REUSE_SUT) { - return; - } const sut = process.env.SUT || 'neos8'; - const flowContext = process.env.FLOW_CONTEXT || 'Production/E2E-SUT'; execSync( - `FLOW_CONTEXT=${flowContext} docker compose -f ../sytem_under_test/${sut}/docker-compose.yaml down -v`, + `docker compose -f ../sytem_under_test/${sut}/docker-compose.yaml down -v`, { stdio: 'inherit', cwd: dirname('.') } ); } diff --git a/Tests/E2E/helpers/system.ts b/Tests/E2E/helpers/system.ts index 97b3a50..1c47385 100644 --- a/Tests/E2E/helpers/system.ts +++ b/Tests/E2E/helpers/system.ts @@ -7,14 +7,14 @@ const CONTAINER = `${process.env.SUT || 'neos8'}-neos-1`; export function createUser(name: string, password: string, roles: string[]) { execSync( `docker exec -u www-data -w /app ${CONTAINER} bash -c "./flow user:create ${name} ${password} Test${name} User${name} --roles ${roles.join(',')}"`, - { stdio: 'inherit', cwd: dirname('.') } + { stdio: 'ignore', cwd: dirname('.') } ) } export function removeAllUsers() { execSync( `docker exec -u www-data -w /app ${CONTAINER} bash -c "./flow user:delete --assume-yes '*'"`, - { stdio: 'inherit', cwd: dirname('.') } + { stdio: 'ignore', cwd: dirname('.') } ) } diff --git a/Tests/E2E/playwright.config.ts b/Tests/E2E/playwright.config.ts index 26b3bdd..4c076c8 100644 --- a/Tests/E2E/playwright.config.ts +++ b/Tests/E2E/playwright.config.ts @@ -12,6 +12,8 @@ const testDir = defineBddConfig({ steps: 'steps/**/*.ts', }); +// TODO: maybe run SUTs on different ports to at least run every SUT in parallel - maybe have to up the worker count to 2? + export default defineConfig({ testDir, fullyParallel: false, @@ -24,6 +26,7 @@ export default defineConfig({ }, globalTeardown: './global-teardown.ts', webServer: { + // TODO: try to build, start, and then only to restart neos container command: `echo "starting SUT ${SUT} with context ${FLOW_CONTEXT}"; FLOW_CONTEXT=${FLOW_CONTEXT} docker compose -f ${sutDir}/docker-compose.yaml up --build`, url: 'http://localhost:8081/', timeout: 600_000, From 4156fc026437a79ae508a00ddf4698f7d5d2c0ee Mon Sep 17 00:00:00 2001 From: Robert Baruck <robert.baruck@sandstorm.de> Date: Fri, 27 Mar 2026 23:47:54 +0100 Subject: [PATCH 09/12] TASK: Fix tests & ux --- Makefile | 50 +++++++++++++------ README.md | 10 ++-- .../features/enforce-for-all/login.feature | 4 +- .../backend-module.feature | 20 -------- .../enforce-for-role/backend-module.feature | 15 +----- Tests/E2E/playwright.config.ts | 2 +- .../neos8/sut-files/entrypoint.sh | 3 +- .../neos9/sut-files/entrypoint.sh | 2 +- 8 files changed, 47 insertions(+), 59 deletions(-) diff --git a/Makefile b/Makefile index a226e34..acca019 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,11 @@ NEOS8_COMPOSE = $(CURDIR)/Tests/sytem_under_test/neos8/docker-compose.yaml NEOS9_COMPOSE = $(CURDIR)/Tests/sytem_under_test/neos9/docker-compose.yaml E2E_DIR = $(CURDIR)/Tests/E2E -.PHONY: setup-test generate-bdd-files test test-neos8 test-neos9 \ +.SILENT: +.PHONY: setup setup-sut setup-test generate-bdd-files test test-neos8 test-neos9 \ test-neos8-defaults test-neos8-enforce-all test-neos8-enforce-role test-neos8-enforce-provider test-neos8-issuer-name \ test-neos9-defaults test-neos9-enforce-all test-neos9-enforce-role test-neos9-enforce-provider test-neos9-issuer-name \ - enter-neos8 enter-neos9 down + start-sut-neos8 start-sut-neos9 log-sut-neos8 log-sut-neos9 enter-sut-neos8 enter-sut-neos9 sut-down # COLORS GREEN := $(shell tput -Txterm setaf 2) @@ -13,19 +14,24 @@ YELLOW := $(shell tput -Txterm setaf 3) RESET := $(shell tput -Txterm sgr0) # initial setup +setup: setup-sut setup-test + +setup-sut: + docker compose -f $(NEOS8_COMPOSE) build --pull + docker compose -f $(NEOS9_COMPOSE) build --pull + setup-test: - @echo '${GREEN}Installing test setup.${RESET}' && \ + echo "${GREEN}Installing test setup.${RESET}" cd $(E2E_DIR) && \ if [ -s "$$NVM_DIR" ]; then \ - echo '${GREEN}Found nvm on system, using it to install nodejs!${RESET}'; \ - . "$$NVM_DIR/nvm.sh" && nvm install; \ + . "$$NVM_DIR/nvm.sh" && echo "${GREEN}Found nvm on system -> using it to install nodejs!${RESET}" && nvm install; \ fi && \ npm install && npx playwright install --with-deps chromium && \ - echo '' && echo '${GREEN}generate BDD files from feature files${RESET}' && npm run generate-tests + echo "" && echo "${GREEN}generate BDD files from feature files${RESET}" && npm run generate-tests # generate BDD files from feature files generate-bdd-files: - @echo '${GREEN}generate BDD files from feature files${RESET}'; \ + echo "${GREEN}generate BDD files from feature files${RESET}"; \ cd $(E2E_DIR) && npm run generate-tests ## Run all E2E tests @@ -67,15 +73,29 @@ test-neos9-enforce-provider: test-neos9-issuer-name: cd $(E2E_DIR) && npm run test:neos9:issuer-name +## Start SUT containers +start-sut-neos8: + docker compose -f $(NEOS8_COMPOSE) up -d --build + +start-sut-neos9: + docker compose -f $(NEOS9_COMPOSE) up -d --build + +## Follow logs of SUT containers +log-sut-neos8: + docker compose -f $(NEOS8_COMPOSE) logs -f + +log-sut-neos9: + docker compose -f $(NEOS9_COMPOSE) logs -f + ## Enter SUT containers -enter-neos8: - @docker compose -f $(NEOS8_COMPOSE) exec neos bash +enter-sut-neos8: + docker compose -f $(NEOS8_COMPOSE) exec neos bash -enter-neos9: - @docker compose -f $(NEOS9_COMPOSE) exec neos bash +enter-sut-neos9: + docker compose -f $(NEOS9_COMPOSE) exec neos bash ## Tear down all docker compose environments and remove volumes -down: - @echo '${YELLOW}Shutting down all SUTs and removing their volumes.${RESET}' - @docker compose -f $(NEOS8_COMPOSE) down -v - @docker compose -f $(NEOS9_COMPOSE) down -v +sut-down: + echo "${YELLOW}Shutting down all SUTs and removing their volumes.${RESET}" + docker compose -f $(NEOS8_COMPOSE) down -v + docker compose -f $(NEOS9_COMPOSE) down -v diff --git a/README.md b/README.md index e68259c..64dce83 100644 --- a/README.md +++ b/README.md @@ -215,16 +215,16 @@ make down # tear down all docker compose environments and remo ``` #### Debugging tests -To debug a test, run the test with flags like this: +To debug a test, run the test from `Tests/E2E/` with flags like this: -- `make test-neos8-enforce-all -- --debug` - to run the test in headed mode with Playwright Inspector -- `make test-neos8-enforce-all -- --ui` - to run the test in headed mode with Playwright Test Runner UI +- `npm run test:neos8:enforce-all -- --debug` - to run the test in headed mode with Playwright Inspector +- `npm run test:neos8:enforce-all -- --ui` - to run the test in headed mode with Playwright Test Runner UI -If you just want to see the test running in the browser just `make test-neos8-enforce-all -- --headed`. +If you just want to see the test running in the browser just `npm run test:neos8:enforce-all -- --headed`. > While debugging you can also enter the SUT with `make enter-neos8` and `make enter-neos9` respectively. > -> You can even the tests you want to debug with `make test-neos8-enforce-all -- --grep @debug` and adding the `@debug` tag to the scenario you want to debug. But using the --ui flag is usually more convenient for debugging. +> You can even the tests you want to debug with `npm run test:neos8:enforce-all -- --grep @debug` and adding the `@debug` tag to the scenario you want to debug. But using the --ui flag is usually more convenient for debugging. #### System under test (SUT) diff --git a/Tests/E2E/features/enforce-for-all/login.feature b/Tests/E2E/features/enforce-for-all/login.feature index 27da716..31d469f 100644 --- a/Tests/E2E/features/enforce-for-all/login.feature +++ b/Tests/E2E/features/enforce-for-all/login.feature @@ -5,12 +5,12 @@ Feature: Login flow with 2FA enforced for all users Given A user with username "admin", password "password" and role "Neos.Neos:Administrator" exists And A user with username "editor", password "password" and role "Neos.Neos:Editor" exists - Scenario: Admin has to enter 2FA code when 2FA is enforced for all users, even without a device + Scenario: Admin has to setup a 2FA when 2FA is enforced for all users, even without a device When I log in with username "admin" and password "password" Then I should see the 2FA setup page And I cannot access the Neos content page - Scenario: Editor has to enter 2FA code when 2FA is enforced for all users, even without a device + Scenario: Editor has to setup a 2FA when 2FA is enforced for all users, even without a device When I log in with username "editor" and password "password" Then I should see the 2FA setup page And I cannot access the Neos content page diff --git a/Tests/E2E/features/enforce-for-provider/backend-module.feature b/Tests/E2E/features/enforce-for-provider/backend-module.feature index 5660bc1..65095c0 100644 --- a/Tests/E2E/features/enforce-for-provider/backend-module.feature +++ b/Tests/E2E/features/enforce-for-provider/backend-module.feature @@ -8,26 +8,6 @@ Feature: Backend module with 2FA enforced for the Neos.Neos:Backend authenticati Given A user with username "admin", password "password" and role "Neos.Neos:Administrator" with enrolled 2FA device with name "Admin Initial Device" exists And A user with username "editor", password "password" and role "Neos.Neos:Editor" with enrolled 2FA device with name "Editor Initial Device" exists - Scenario: Admin user must enter TOTP before accessing the backend module - When I log in with username "admin" and password "password" - And I enter a valid TOTP for device "Admin Initial Device" - And I navigate to the 2FA management page - Then There should be a 2FA device with the name "Admin Initial Device" - - Scenario: Editor user must enter TOTP before accessing the backend module - When I log in with username "editor" and password "password" - And I enter a valid TOTP for device "Editor Initial Device" - And I navigate to the 2FA management page - Then There should be a 2FA device with the name "Editor Initial Device" - - Scenario: User can add a 2FA device in the backend module - When I log in with username "editor" and password "password" - And I enter a valid TOTP for device "Editor Initial Device" - And I navigate to the 2FA management page - And I add a new TOTP 2FA device with name "Editor Test Device" - Then There should be a 2FA device with the name "Editor Test Device" - And There should be a 2FA device with the name "Editor Initial Device" - Scenario: User cannot remove their last 2FA device when 2FA is enforced for their provider When I log in with username "editor" and password "password" And I enter a valid TOTP for device "Editor Initial Device" diff --git a/Tests/E2E/features/enforce-for-role/backend-module.feature b/Tests/E2E/features/enforce-for-role/backend-module.feature index 1c6402b..ae52cfc 100644 --- a/Tests/E2E/features/enforce-for-role/backend-module.feature +++ b/Tests/E2E/features/enforce-for-role/backend-module.feature @@ -9,19 +9,6 @@ Feature: Backend module with 2FA enforced for administrators only And A user with username "editor", password "password" and role "Neos.Neos:Editor" exists And A user with username "secondFactorUser", password "password" and role "Neos.Neos:SecondFactorUser" with enrolled 2FA device with name "2FA-User Initial Device" exists - Scenario: Administrator must enter TOTP before accessing the backend module - When I log in with username "admin" and password "password" - And I enter a valid TOTP for device "Admin Initial Device" - And I navigate to the 2FA management page - Then There should be a 2FA device with the name "Admin Initial Device" - - Scenario: Editor can access the backend module without 2FA - When I log in with username "editor" and password "password" - And I navigate to the 2FA management page - And I add a new TOTP 2FA device with name "Editor Test Device" - Then There should be 1 enrolled 2FA device - And There should be a 2FA device with the name "Editor Test Device" - Scenario: Editor can remove their own last 2FA device when 2FA is not enforced for their role When I log in with username "editor" and password "password" And I navigate to the 2FA management page @@ -44,7 +31,7 @@ Feature: Backend module with 2FA enforced for administrators only Then There should be 1 enrolled 2FA device And There should be no 2FA device with the name "Admin Initial Device" - Scenario: User with role "Neos.Neos:SecondFactorUser" must enter TOTP before accessing the backend module + Scenario: User can not remove their own last 2FA device when 2FA is enforced for their role When I log in with username "secondFactorUser" and password "password" And I enter a valid TOTP for device "2FA-User Initial Device" And I navigate to the 2FA management page diff --git a/Tests/E2E/playwright.config.ts b/Tests/E2E/playwright.config.ts index 4c076c8..407dab8 100644 --- a/Tests/E2E/playwright.config.ts +++ b/Tests/E2E/playwright.config.ts @@ -27,7 +27,7 @@ export default defineConfig({ globalTeardown: './global-teardown.ts', webServer: { // TODO: try to build, start, and then only to restart neos container - command: `echo "starting SUT ${SUT} with context ${FLOW_CONTEXT}"; FLOW_CONTEXT=${FLOW_CONTEXT} docker compose -f ${sutDir}/docker-compose.yaml up --build`, + command: `echo "starting SUT ${SUT} with context ${FLOW_CONTEXT}"; FLOW_CONTEXT=${FLOW_CONTEXT} docker compose -f ${sutDir}/docker-compose.yaml up --build 1>/dev/null 2>/dev/null`, url: 'http://localhost:8081/', timeout: 600_000, stdout: 'pipe', diff --git a/Tests/sytem_under_test/neos8/sut-files/entrypoint.sh b/Tests/sytem_under_test/neos8/sut-files/entrypoint.sh index 8e6130c..aff536a 100755 --- a/Tests/sytem_under_test/neos8/sut-files/entrypoint.sh +++ b/Tests/sytem_under_test/neos8/sut-files/entrypoint.sh @@ -3,9 +3,10 @@ set -eou pipefail # ping db until it is ready echo "Waiting for database..." -until mariadb -h"${DB_NEOS_HOST}" -P"${DB_NEOS_PORT}" -u"${DB_NEOS_USER}" -p"${DB_NEOS_PASSWORD}" -D"${DB_NEOS_DATABASE}" --disable-ssl --silent -e "SELECT 1;" 2>/dev/null; do +until mariadb -h"${DB_NEOS_HOST}" -P"${DB_NEOS_PORT}" -u"${DB_NEOS_USER}" -p"${DB_NEOS_PASSWORD}" -D"${DB_NEOS_DATABASE}" --disable-ssl --silent -e "SELECT 1;" 1>/dev/null 2>/dev/null; do sleep 2 done +echo "Database is ready." ./flow flow:cache:flush diff --git a/Tests/sytem_under_test/neos9/sut-files/entrypoint.sh b/Tests/sytem_under_test/neos9/sut-files/entrypoint.sh index 53d1b8a..b44ca14 100755 --- a/Tests/sytem_under_test/neos9/sut-files/entrypoint.sh +++ b/Tests/sytem_under_test/neos9/sut-files/entrypoint.sh @@ -3,7 +3,7 @@ set -eou pipefail # ping db until it is ready echo "Waiting for database..." -until mariadb -h"${DB_NEOS_HOST}" -P"${DB_NEOS_PORT}" -u"${DB_NEOS_USER}" -p"${DB_NEOS_PASSWORD}" -D"${DB_NEOS_DATABASE}" --disable-ssl --silent -e "SELECT 1;" 2>/dev/null; do +until mariadb -h"${DB_NEOS_HOST}" -P"${DB_NEOS_PORT}" -u"${DB_NEOS_USER}" -p"${DB_NEOS_PASSWORD}" -D"${DB_NEOS_DATABASE}" --disable-ssl --silent -e "SELECT 1;" 1>/dev/null 2>/dev/null; do sleep 2 done From c93ae872275860a0014248d2141645d6ea1b643c Mon Sep 17 00:00:00 2001 From: Robert Baruck <robert.baruck@sandstorm.de> Date: Sat, 28 Mar 2026 00:05:23 +0100 Subject: [PATCH 10/12] TASK: Add github workflow --- .github/workflows/e2e.yml | 60 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .github/workflows/e2e.yml diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 0000000..fd42175 --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,60 @@ +name: E2E Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + e2e: + name: E2E Tests (${{ matrix.neos }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + neos: [neos8, neos9] + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version-file: Tests/E2E/.nvmrc + cache: npm + cache-dependency-path: Tests/E2E/package-lock.json + + - name: Install dependencies + working-directory: Tests/E2E + run: npm ci + + - name: Install Playwright browsers + working-directory: Tests/E2E + run: npx playwright install --with-deps chromium + + - name: Pre-build Docker image + run: docker compose -f Tests/sytem_under_test/${{ matrix.neos }}/docker-compose.yaml build --pull + + - name: Test - defaults + working-directory: Tests/E2E + run: npm run test:${{ matrix.neos }}:defaults + + - name: Test - enforce-all + working-directory: Tests/E2E + run: npm run test:${{ matrix.neos }}:enforce-all + + - name: Test - enforce-role + working-directory: Tests/E2E + run: npm run test:${{ matrix.neos }}:enforce-role + + - name: Test - enforce-provider + working-directory: Tests/E2E + run: npm run test:${{ matrix.neos }}:enforce-provider + + - name: Upload Playwright report + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report-${{ matrix.neos }} + path: Tests/E2E/playwright-report/ + retention-days: 7 From 4a112fadfb6d89a525bd410005638eaa336f7741 Mon Sep 17 00:00:00 2001 From: Robert Baruck <robert.baruck@sandstorm.de> Date: Sat, 28 Mar 2026 00:30:53 +0100 Subject: [PATCH 11/12] TASK: Remove obsolete SecondFactorCommandController --- .../Command/SecondFactorCommandController.php | 58 ------------------- 1 file changed, 58 deletions(-) delete mode 100644 Classes/Command/SecondFactorCommandController.php diff --git a/Classes/Command/SecondFactorCommandController.php b/Classes/Command/SecondFactorCommandController.php deleted file mode 100644 index 280e60f..0000000 --- a/Classes/Command/SecondFactorCommandController.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php - -namespace Sandstorm\NeosTwoFactorAuthentication\Command; - -use Neos\Flow\Annotations as Flow; -use Neos\Flow\Cli\CommandController; -use Neos\Flow\Persistence\PersistenceManagerInterface; -use Sandstorm\NeosTwoFactorAuthentication\Domain\Repository\SecondFactorRepository; - -/** - * @Flow\Scope("singleton") - */ -class SecondFactorCommandController extends CommandController -{ - #[Flow\Inject] - protected SecondFactorRepository $secondFactorRepository; - - #[Flow\Inject] - protected PersistenceManagerInterface $persistenceManager; - - /** - * Delete all second factors for a given account identifier - * - * @param string $username The account identifier (username) to delete second factors for - */ - public function deleteForAccountCommand(string $username): void - { - $query = $this->secondFactorRepository->createQuery(); - $factors = $query->matching( - $query->equals('account.accountIdentifier', $username) - )->execute(); - - $count = 0; - foreach ($factors as $factor) { - $this->secondFactorRepository->remove($factor); - $count++; - } - $this->persistenceManager->persistAll(); - - $this->outputLine('Deleted %d second factor(s) for account "%s".', [$count, $username]); - } - - /** - * Delete all second factors for all accounts - */ - public function deleteAllCommand(): void - { - $factors = $this->secondFactorRepository->findAll(); - $count = 0; - foreach ($factors as $factor) { - $this->secondFactorRepository->remove($factor); - $count++; - } - $this->persistenceManager->persistAll(); - - $this->outputLine('Deleted %d second factor(s).', [$count]); - } -} From b2dd3a4e429d34c51cfea968c34f818167fbeec1 Mon Sep 17 00:00:00 2001 From: Robert Baruck <robert.baruck@sandstorm.de> Date: Mon, 30 Mar 2026 16:19:56 +0200 Subject: [PATCH 12/12] TASK: Smaller footprint --- .dockerignore | 6 + .github/workflows/e2e.yml | 2 +- Concept_Composer_Package.md | 169 ++++++++++++++++++ Makefile | 4 +- README.md | 2 +- Tests/E2E/.nvmrc | 2 +- Tests/E2E/global-teardown.ts | 2 +- Tests/E2E/helpers/{pages.ts => 2fa-pages.ts} | 14 -- Tests/E2E/helpers/general-pages.ts | 25 +++ Tests/E2E/package.json | 6 +- Tests/E2E/playwright.config.ts | 13 +- .../{login.steps.ts => 2fa-login.steps.ts} | 34 +--- Tests/E2E/steps/backend-module.steps.ts | 2 +- Tests/E2E/steps/general-login.steps.ts | 45 +++++ Tests/E2E/steps/hooks.ts | 4 +- Tests/E2E/tsconfig.json | 25 +-- .../neos8 => system_under_test}/Dockerfile | 31 ++-- .../neos8/docker-compose.yaml | 19 ++ .../neos8}/entrypoint.sh | 11 +- .../neos9/docker-compose.yaml | 22 +++ .../neos9}/entrypoint.sh | 12 +- .../sut-base-docker-compose.yaml | 72 ++++++++ .../Production/E2E-SUT/Caches.yaml | 0 .../E2E-SUT/EnforceForAll/Settings.2FA.yaml | 0 .../EnforceForProvider/Settings.2FA.yaml | 0 .../E2E-SUT/EnforceForRole}/Policy.yaml | 0 .../E2E-SUT/EnforceForRole/Settings.2FA.yaml | 0 .../IssuerNameChange/Settings.2FA.yaml | 0 .../Production/E2E-SUT/Settings.yaml | 0 .../etc/bash.vips-arm64-hotfix.sh | 0 .../etc/frankenphp/Caddyfile | 0 .../etc/php/conf.d/php-ini-overrides.ini | 0 .../neos8/docker-compose.yaml | 57 ------ .../Production/Settings.yaml.example | 19 -- .../neos8/sut-files/app/Configuration/README | 29 --- .../app/Configuration/Settings.yaml.example | 39 ---- .../neos8/sut-files/etc/caretakerd.yaml | 9 - Tests/sytem_under_test/neos9/Dockerfile | 67 ------- .../neos9/docker-compose.yaml | 57 ------ .../Production/E2E-SUT/Caches.yaml | 56 ------ .../E2E-SUT/EnforceForAll/Settings.2FA.yaml | 4 - .../EnforceForProvider/Settings.2FA.yaml | 4 - .../E2E-SUT/EnforceForRole/Settings.2FA.yaml | 4 - .../IssuerNameChange/Settings.2FA.yaml | 4 - .../Production/E2E-SUT/Policy.yaml | 5 - .../Production/E2E-SUT/Settings.yaml | 26 --- .../Production/Settings.yaml.example | 19 -- .../neos9/sut-files/app/Configuration/README | 29 --- .../app/Configuration/Settings.yaml.example | 39 ---- .../sut-files/etc/bash.vips-arm64-hotfix.sh | 17 -- .../neos9/sut-files/etc/caretakerd.yaml | 9 - .../neos9/sut-files/etc/frankenphp/Caddyfile | 41 ----- .../etc/php/conf.d/php-ini-overrides.ini | 20 --- 53 files changed, 405 insertions(+), 671 deletions(-) create mode 100644 Concept_Composer_Package.md rename Tests/E2E/helpers/{pages.ts => 2fa-pages.ts} (90%) create mode 100644 Tests/E2E/helpers/general-pages.ts rename Tests/E2E/steps/{login.steps.ts => 2fa-login.steps.ts} (74%) create mode 100644 Tests/E2E/steps/general-login.steps.ts rename Tests/{sytem_under_test/neos8 => system_under_test}/Dockerfile (54%) create mode 100644 Tests/system_under_test/neos8/docker-compose.yaml rename Tests/{sytem_under_test/neos8/sut-files => system_under_test/neos8}/entrypoint.sh (51%) create mode 100644 Tests/system_under_test/neos9/docker-compose.yaml rename Tests/{sytem_under_test/neos9/sut-files => system_under_test/neos9}/entrypoint.sh (50%) create mode 100644 Tests/system_under_test/sut-base-docker-compose.yaml rename Tests/{sytem_under_test/neos8/sut-files => system_under_test/sut_file_system_overrides}/app/Configuration/Production/E2E-SUT/Caches.yaml (100%) rename Tests/{sytem_under_test/neos8/sut-files => system_under_test/sut_file_system_overrides}/app/Configuration/Production/E2E-SUT/EnforceForAll/Settings.2FA.yaml (100%) rename Tests/{sytem_under_test/neos8/sut-files => system_under_test/sut_file_system_overrides}/app/Configuration/Production/E2E-SUT/EnforceForProvider/Settings.2FA.yaml (100%) rename Tests/{sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT => system_under_test/sut_file_system_overrides/app/Configuration/Production/E2E-SUT/EnforceForRole}/Policy.yaml (100%) rename Tests/{sytem_under_test/neos8/sut-files => system_under_test/sut_file_system_overrides}/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml (100%) rename Tests/{sytem_under_test/neos8/sut-files => system_under_test/sut_file_system_overrides}/app/Configuration/Production/E2E-SUT/IssuerNameChange/Settings.2FA.yaml (100%) rename Tests/{sytem_under_test/neos8/sut-files => system_under_test/sut_file_system_overrides}/app/Configuration/Production/E2E-SUT/Settings.yaml (100%) rename Tests/{sytem_under_test/neos8/sut-files => system_under_test/sut_file_system_overrides}/etc/bash.vips-arm64-hotfix.sh (100%) rename Tests/{sytem_under_test/neos8/sut-files => system_under_test/sut_file_system_overrides}/etc/frankenphp/Caddyfile (100%) rename Tests/{sytem_under_test/neos8/sut-files => system_under_test/sut_file_system_overrides}/usr/local/etc/php/conf.d/php-ini-overrides.ini (100%) delete mode 100644 Tests/sytem_under_test/neos8/docker-compose.yaml delete mode 100644 Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/Settings.yaml.example delete mode 100644 Tests/sytem_under_test/neos8/sut-files/app/Configuration/README delete mode 100644 Tests/sytem_under_test/neos8/sut-files/app/Configuration/Settings.yaml.example delete mode 100755 Tests/sytem_under_test/neos8/sut-files/etc/caretakerd.yaml delete mode 100644 Tests/sytem_under_test/neos9/Dockerfile delete mode 100644 Tests/sytem_under_test/neos9/docker-compose.yaml delete mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Caches.yaml delete mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForAll/Settings.2FA.yaml delete mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForProvider/Settings.2FA.yaml delete mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml delete mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/IssuerNameChange/Settings.2FA.yaml delete mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Policy.yaml delete mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Settings.yaml delete mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/Settings.yaml.example delete mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/README delete mode 100644 Tests/sytem_under_test/neos9/sut-files/app/Configuration/Settings.yaml.example delete mode 100644 Tests/sytem_under_test/neos9/sut-files/etc/bash.vips-arm64-hotfix.sh delete mode 100755 Tests/sytem_under_test/neos9/sut-files/etc/caretakerd.yaml delete mode 100644 Tests/sytem_under_test/neos9/sut-files/etc/frankenphp/Caddyfile delete mode 100644 Tests/sytem_under_test/neos9/sut-files/usr/local/etc/php/conf.d/php-ini-overrides.ini diff --git a/.dockerignore b/.dockerignore index fdbf90e..84423cc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,9 @@ .git/ .idea/ node_modules/ +vendor/ +Packages +.github +.claude +.idea +composer.lock diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index fd42175..2e4f165 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -33,7 +33,7 @@ jobs: run: npx playwright install --with-deps chromium - name: Pre-build Docker image - run: docker compose -f Tests/sytem_under_test/${{ matrix.neos }}/docker-compose.yaml build --pull + run: docker compose -f Tests/system_under_test/${{ matrix.neos }}/docker-compose.yaml build --pull - name: Test - defaults working-directory: Tests/E2E diff --git a/Concept_Composer_Package.md b/Concept_Composer_Package.md new file mode 100644 index 0000000..1478939 --- /dev/null +++ b/Concept_Composer_Package.md @@ -0,0 +1,169 @@ +# Concept: Reusable E2E Infrastructure as a Composer Package + +## Goal + +Extract the E2E test infrastructure from this plugin into a standalone Composer package +(`sandstorm/neos-e2e-testing`) that any Neos plugin can install to get a working +end-to-end test scaffold with minimal effort. + +**Scope:** Infrastructure only — Docker/SUT setup, Playwright project scaffold (config, +package.json, tsconfig, teardown), Makefile, and GitHub Actions workflow. Step definitions, +helpers (system, pages, totp, etc.), and feature files are entirely the plugin developer's +responsibility. + +--- + +## Package Structure + +### Type: `library` + +Not `neos-package` — this is a dev tool. It belongs in `vendor/`, not `Packages/`, and +has no Flow/Neos runtime dependency. + +``` +sandstorm/neos-e2e-testing/ +├── composer.json +├── bin/ +│ └── neos-e2e-setup ← self-contained PHP CLI script +└── templates/ + ├── Tests/ + │ ├── E2E/ + │ │ ├── package.json + │ │ ├── playwright.config.ts + │ │ ├── tsconfig.json + │ │ ├── global-teardown.ts + │ │ └── .nvmrc + │ └── system_under_test/ + │ ├── neos8/ + │ │ ├── Dockerfile + │ │ ├── docker-compose.yaml + │ │ └── sut-files/ + │ │ ├── entrypoint.sh + │ │ ├── etc/caretakerd.yaml + │ │ ├── etc/frankenphp/Caddyfile + │ │ └── usr/local/etc/php/conf.d/php-ini-overrides.ini + │ └── neos9/ + │ └── (same) + ├── .dockerignore + ├── Makefile + └── .github/ + └── workflows/ + └── e2e.yml +``` + +### `composer.json` + +```json +{ + "name": "sandstorm/neos-e2e-testing", + "type": "library", + "description": "Scaffolds E2E test infrastructure for Neos plugins", + "require": { "php": "^8.1" }, + "bin": ["bin/neos-e2e-setup"] +} +``` + +--- + +## Usage + +```sh +composer require --dev sandstorm/neos-e2e-testing +vendor/bin/neos-e2e-setup --plugin-package sandstorm/neostwofactorauthentication +``` + +The script copies templates into the plugin root, substitutes tokens, and skips files that +already exist (safe to re-run). + +--- + +## Template Tokens + +Only two things vary between plugins: + +| Token | Example | Used in | +|---|---|---| +| `{{PLUGIN_PACKAGE}}` | `sandstorm/neostwofactorauthentication` | Dockerfile, docker-compose project name, Makefile, GH Actions | +| `{{PLUGIN_SLUG}}` | `sandstorm-2fa` | Docker container/volume names | + +The slug is derived automatically from the package name (everything after `/`, hyphens +preserved), so the developer only needs to provide `--plugin-package`. + +--- + +## The Dockerfile Template + +The only plugin-specific lines in the Dockerfile are the `composer require` call. These +are made generic via a build arg: + +```dockerfile +ARG PLUGIN_PACKAGE={{PLUGIN_PACKAGE}} + +COPY . /tmp/plugin/ +RUN --mount=type=cache,target=/root/.composer \ + composer config repositories.plugin \ + '{"type":"path","url":"/tmp/plugin","options":{"symlink":false}}' \ + && composer require ${PLUGIN_PACKAGE}:@dev +``` + +Everything else (PHP extensions, caretakerd, FrankenPHP, Neos base distribution, config +copy) is already generic across plugins. + +--- + +## `.dockerignore` (generated at plugin root) + +Without this, `COPY . /tmp/plugin/` in the Dockerfile would include the test +infrastructure itself inside the image: + +``` +Tests/E2E/node_modules +Tests/E2E/.playwright +Tests/system_under_test +``` + +--- + +## The `bin/neos-e2e-setup` Script + +A self-contained PHP script — no autoloading, no framework. It: + +1. Parses `--plugin-package` from `$argv` +2. Derives `{{PLUGIN_SLUG}}` from the package name +3. Iterates over `templates/` recursively +4. For each template file: substitutes tokens, copies to the target path +5. Skips files that already exist (idempotent) + +--- + +## What the Developer Adds After Scaffolding + +The scaffolded layout has intentional empty directories (with `.gitkeep`) for the +plugin-specific content: + +``` +Tests/E2E/features/ + ← write .feature files here + +Tests/E2E/steps/ + ← write step definitions here + +Tests/system_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/ +Tests/system_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/ + ← plugin-specific Settings.yaml, Policy.yaml, context subdirectories, etc. +``` + +The scaffold writes the base `Configuration/Production/E2E-SUT/Settings.yaml` (DB + Redis +connection) and `Caches.yaml`. The plugin only adds its own configuration on top. + +--- + +## Open Question: Container Name Convention + +The `SUT` environment variable (used in step definitions as `${SUT}-neos-1` to target +`docker exec`) is a contract between the scaffolded `docker-compose.yaml` and the +developer's step helpers. Since step helpers are entirely the plugin developer's business, +this convention needs to be documented somewhere visible — either in a generated `README` +in `Tests/E2E/`, or as a comment in the scaffolded `playwright.config.ts`. + +A decision is needed on where this contract lives before the package is built. diff --git a/Makefile b/Makefile index acca019..2e9cf6f 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -NEOS8_COMPOSE = $(CURDIR)/Tests/sytem_under_test/neos8/docker-compose.yaml -NEOS9_COMPOSE = $(CURDIR)/Tests/sytem_under_test/neos9/docker-compose.yaml +NEOS8_COMPOSE = $(CURDIR)/Tests/system_under_test/neos8/docker-compose.yaml +NEOS9_COMPOSE = $(CURDIR)/Tests/system_under_test/neos9/docker-compose.yaml E2E_DIR = $(CURDIR)/Tests/E2E .SILENT: diff --git a/README.md b/README.md index 64dce83..2c1f8bf 100644 --- a/README.md +++ b/README.md @@ -228,7 +228,7 @@ If you just want to see the test running in the browser just `npm run test:neos8 #### System under test (SUT) -There are two docker compose environments in `Tests/sytem_under_test/`: +There are two docker compose environments in `Tests/system_under_test/`: - `neos8/` — Neos with PHP 8.2 - `neos9/` — Neos with PHP 8.5 diff --git a/Tests/E2E/.nvmrc b/Tests/E2E/.nvmrc index a4a7a41..a3b7a31 100644 --- a/Tests/E2E/.nvmrc +++ b/Tests/E2E/.nvmrc @@ -1 +1 @@ -v24.14.0 +v24.14.1 diff --git a/Tests/E2E/global-teardown.ts b/Tests/E2E/global-teardown.ts index 6cefa77..cc788d6 100644 --- a/Tests/E2E/global-teardown.ts +++ b/Tests/E2E/global-teardown.ts @@ -4,7 +4,7 @@ import { dirname } from 'node:path'; export default async function globalTeardown() { const sut = process.env.SUT || 'neos8'; execSync( - `docker compose -f ../sytem_under_test/${sut}/docker-compose.yaml down -v`, + `docker compose -f ../system_under_test/${sut}/docker-compose.yaml down -v`, { stdio: 'inherit', cwd: dirname('.') } ); } diff --git a/Tests/E2E/helpers/pages.ts b/Tests/E2E/helpers/2fa-pages.ts similarity index 90% rename from Tests/E2E/helpers/pages.ts rename to Tests/E2E/helpers/2fa-pages.ts index ebd8183..909a16d 100644 --- a/Tests/E2E/helpers/pages.ts +++ b/Tests/E2E/helpers/2fa-pages.ts @@ -1,20 +1,6 @@ import type { Page } from '@playwright/test'; import { generateOtp } from './totp.js'; -export class NeosLoginPage { - constructor(private readonly page: Page) {} - - async goto() { - await this.page.goto('/neos/login'); - } - - async login(username: string, password: string) { - await this.page.locator('input[type="text"]').fill(username); - await this.page.locator('input[type="password"]').fill(password); - await this.page.locator('.neos-login-btn:not(.neos-disabled):not(.neos-hidden)').click(); - } -} - export class SecondFactorLoginPage { constructor(private readonly page: Page) {} diff --git a/Tests/E2E/helpers/general-pages.ts b/Tests/E2E/helpers/general-pages.ts new file mode 100644 index 0000000..9ff8832 --- /dev/null +++ b/Tests/E2E/helpers/general-pages.ts @@ -0,0 +1,25 @@ +import type { Page } from '@playwright/test'; + +export class NeosLoginPage { + constructor(private readonly page: Page) {} + + async goto() { + await this.page.goto('/neos/login'); + } + + async login(username: string, password: string) { + await this.page.locator('input[type="text"]').fill(username); + await this.page.locator('input[type="password"]').fill(password); + await this.page.locator('.neos-login-btn:not(.neos-disabled):not(.neos-hidden)').click(); + } +} + +export class NeosContentPage { + public readonly URL_REGEX = /neos\/content/; + + constructor(private readonly page: Page) {} + + async goto() { + await this.page.goto('/neos/content'); + } +} diff --git a/Tests/E2E/package.json b/Tests/E2E/package.json index c7c2c86..c54f6cf 100644 --- a/Tests/E2E/package.json +++ b/Tests/E2E/package.json @@ -3,15 +3,15 @@ "private": true, "type": "module", "scripts": { - "generate-tests": "npx bddgen", + "generate-tests": "SUT=notRelevantForBddgen FLOW_CONTEXT=notRelevantForBddgen npx bddgen", - "test:neos8:defaults": "npm run generate-tests && SUT=neos8 npx playwright test --grep @default-context", + "test:neos8:defaults": "npm run generate-tests && SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT npx playwright test --grep @default-context", "test:neos8:enforce-all": "npm run generate-tests && SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForAll npx playwright test --grep @enforce-for-all", "test:neos8:enforce-role": "npm run generate-tests && SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole npx playwright test --grep @enforce-for-role", "test:neos8:enforce-provider": "npm run generate-tests && SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider npx playwright test --grep @enforce-for-provider", "test:neos8:issuer-name": "npm run generate-tests && SUT=neos8 FLOW_CONTEXT=Production/E2E-SUT/IssuerNameChange npx playwright test --grep @issuer-name-change", - "test:neos9:defaults": "npm run generate-tests && SUT=neos9 npx playwright test --grep @default-context", + "test:neos9:defaults": "npm run generate-tests && SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT npx playwright test --grep @default-context", "test:neos9:enforce-all": "npm run generate-tests && SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForAll npx playwright test --grep @enforce-for-all", "test:neos9:enforce-role": "npm run generate-tests && SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForRole npx playwright test --grep @enforce-for-role", "test:neos9:enforce-provider": "npm run generate-tests && SUT=neos9 FLOW_CONTEXT=Production/E2E-SUT/EnforceForProvider npx playwright test --grep @enforce-for-provider", diff --git a/Tests/E2E/playwright.config.ts b/Tests/E2E/playwright.config.ts index 407dab8..1e2c799 100644 --- a/Tests/E2E/playwright.config.ts +++ b/Tests/E2E/playwright.config.ts @@ -2,18 +2,18 @@ import { defineConfig, devices } from '@playwright/test'; import { defineBddConfig } from 'playwright-bdd'; // env API to select system under test (SUT) (neos8 | neos9) and flow context for the configuration to be used (default, enforce for all users, etc.) -const SUT = process.env.SUT || 'neos8'; -const FLOW_CONTEXT = process.env.FLOW_CONTEXT || 'Production/E2E-SUT'; +const SUT = process.env.SUT; +const FLOW_CONTEXT = process.env.FLOW_CONTEXT; -const sutDir = `../sytem_under_test/${SUT}`; +if (SUT == null || FLOW_CONTEXT == null) { + throw new Error('SUT and FLOW_CONTEXT environment variables must be set!'); +} const testDir = defineBddConfig({ features: 'features/**/*.feature', steps: 'steps/**/*.ts', }); -// TODO: maybe run SUTs on different ports to at least run every SUT in parallel - maybe have to up the worker count to 2? - export default defineConfig({ testDir, fullyParallel: false, @@ -26,8 +26,7 @@ export default defineConfig({ }, globalTeardown: './global-teardown.ts', webServer: { - // TODO: try to build, start, and then only to restart neos container - command: `echo "starting SUT ${SUT} with context ${FLOW_CONTEXT}"; FLOW_CONTEXT=${FLOW_CONTEXT} docker compose -f ${sutDir}/docker-compose.yaml up --build 1>/dev/null 2>/dev/null`, + command: `echo "starting SUT ${SUT} with context ${FLOW_CONTEXT}"; FLOW_CONTEXT=${FLOW_CONTEXT} docker compose -f ../system_under_test/${SUT}/docker-compose.yaml up`, url: 'http://localhost:8081/', timeout: 600_000, stdout: 'pipe', diff --git a/Tests/E2E/steps/login.steps.ts b/Tests/E2E/steps/2fa-login.steps.ts similarity index 74% rename from Tests/E2E/steps/login.steps.ts rename to Tests/E2E/steps/2fa-login.steps.ts index 77af921..bcc5f49 100644 --- a/Tests/E2E/steps/login.steps.ts +++ b/Tests/E2E/steps/2fa-login.steps.ts @@ -1,6 +1,7 @@ import { expect } from '@playwright/test'; import { createBdd } from 'playwright-bdd'; -import { BackendModulePage, NeosLoginPage, SecondFactorLoginPage, SecondFactorSetupPage } from '../helpers/pages.ts'; +import { BackendModulePage, SecondFactorLoginPage, SecondFactorSetupPage } from '../helpers/2fa-pages.ts'; +import { NeosLoginPage } from "../helpers/general-pages.ts"; import { generateOtp } from '../helpers/totp.ts'; import { createUser, logout } from '../helpers/system.ts'; import { state } from "../helpers/state.ts"; @@ -9,12 +10,6 @@ const { Given, When, Then } = createBdd(); // ── Background / Given ──────────────────────────────────────────────────────── -Given('A user with username {string}, password {string} and role {string} exists', - async ({}, username: string, password: string, role: string) => { - createUser(username, password, [role]); - }, -); - Given('A user with username {string}, password {string} and role {string} with enrolled 2FA device with name {string} exists', async ({ page }, username: string, password: string, role: string, deviceName: string) => { createUser(username, password, [role]); @@ -46,20 +41,6 @@ Given('A user with username {string}, password {string} and role {string} with e // ── When ────────────────────────────────────────────────────────────────────── -When('I log in with username {string} and password {string}', - async ({ page }, username: string, password: string) => { - const loginPage = new NeosLoginPage(page); - await loginPage.goto(); - await loginPage.login(username, password); - await page.waitForLoadState('networkidle'); - }, -); - -When('I log out', async ({ page }) => { - await page.pause(); - await logout(page); -}); - When('I set up a 2FA device with name {string}', async ({ page }, deviceName: string) => { const setupPage = new SecondFactorSetupPage(page); await setupPage.waitForPage(); @@ -84,11 +65,6 @@ When('I enter a valid TOTP for device {string}', async ({ page }, deviceName: st // ── Then ────────────────────────────────────────────────────────────────────── -Then('I should see the Neos content page', async ({ page }) => { - await expect(page).not.toHaveURL(/neos\/login/); - await expect(page).not.toHaveURL(/second-factor/); -}); - Then('I should see the 2FA verification page', async ({ page }) => { await expect(page).toHaveURL(/second-factor-login/); }); @@ -96,9 +72,3 @@ Then('I should see the 2FA verification page', async ({ page }) => { Then('I should see the 2FA setup page', async ({ page }) => { await expect(page).toHaveURL(/second-factor-setup/); }); - -Then('I cannot access the Neos content page', async ({ page }) => { - await page.goto('/neos/content'); - - await expect(page).not.toHaveURL(/neos\/content/); -}); diff --git a/Tests/E2E/steps/backend-module.steps.ts b/Tests/E2E/steps/backend-module.steps.ts index 1a24f89..ce85224 100644 --- a/Tests/E2E/steps/backend-module.steps.ts +++ b/Tests/E2E/steps/backend-module.steps.ts @@ -1,6 +1,6 @@ import { expect } from '@playwright/test'; import { createBdd } from 'playwright-bdd'; -import { BackendModulePage } from '../helpers/pages.js'; +import { BackendModulePage } from '../helpers/2fa-pages.ts'; import { state } from '../helpers/state.js'; const { When, Then } = createBdd(); diff --git a/Tests/E2E/steps/general-login.steps.ts b/Tests/E2E/steps/general-login.steps.ts new file mode 100644 index 0000000..ccde9b5 --- /dev/null +++ b/Tests/E2E/steps/general-login.steps.ts @@ -0,0 +1,45 @@ +import { expect } from '@playwright/test'; +import { createBdd } from 'playwright-bdd'; +import { NeosLoginPage, NeosContentPage } from '../helpers/general-pages.ts'; +import { createUser, logout } from '../helpers/system.ts'; + +const { Given, When, Then } = createBdd(); + +// ── Background / Given ──────────────────────────────────────────────────────── + +Given('A user with username {string}, password {string} and role {string} exists', + async ({}, username: string, password: string, role: string) => { + createUser(username, password, [role]); + }, +); + +// ── When ────────────────────────────────────────────────────────────────────── + +When('I log in with username {string} and password {string}', + async ({ page }, username: string, password: string) => { + const loginPage = new NeosLoginPage(page); + await loginPage.goto(); + await loginPage.login(username, password); + await page.waitForLoadState('networkidle'); + }, +); + +When('I log out', async ({ page }) => { + await logout(page); +}); + + +// ── Then ────────────────────────────────────────────────────────────────────── + +Then('I should see the Neos content page', async ({ page }) => { + const neosContentPage = new NeosContentPage(page); + await expect(page).toHaveURL(neosContentPage.URL_REGEX); +}); + +Then('I cannot access the Neos content page', async ({ page }) => { + const neosContentPage = new NeosContentPage(page); + await neosContentPage.goto(); + + // expecting to be redirected (e.g. to /neos/login) + await expect(page).not.toHaveURL(neosContentPage.URL_REGEX) +}); diff --git a/Tests/E2E/steps/hooks.ts b/Tests/E2E/steps/hooks.ts index e5fabae..2761ee0 100644 --- a/Tests/E2E/steps/hooks.ts +++ b/Tests/E2E/steps/hooks.ts @@ -1,4 +1,3 @@ -import { expect } from '@playwright/test'; import { createBdd } from 'playwright-bdd'; import { state } from "../helpers/state.ts"; import { logout, removeAllUsers } from "../helpers/system.ts"; @@ -8,9 +7,8 @@ const { AfterScenario } = createBdd(); // reset db after each scenario AfterScenario(async ({ page }) => { await logout(page) + removeAllUsers(); // clear state state.deviceNameSecretMap.clear(); - - removeAllUsers(); }); diff --git a/Tests/E2E/tsconfig.json b/Tests/E2E/tsconfig.json index 48e839a..2393546 100644 --- a/Tests/E2E/tsconfig.json +++ b/Tests/E2E/tsconfig.json @@ -1,44 +1,23 @@ { // Visit https://aka.ms/tsconfig to read more about this file "compilerOptions": { - // File Layout - // "rootDir": "./src", - // "outDir": "./dist", - - // Environment Settings - // See also https://aka.ms/tsconfig/module + "noEmit": true, "module": "nodenext", "target": "ESNext", - // For nodejs: "lib": ["esnext"], "types": ["node"], - // and npm install -D @types/node - - // Other Outputs - "sourceMap": true, - "declaration": true, - "declarationMap": true, // Stricter Typechecking Options "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true, - // Style Options - // "noImplicitReturns": true, - // "noImplicitOverride": true, - // "noUnusedLocals": true, - // "noUnusedParameters": true, - // "noFallthroughCasesInSwitch": true, - // "noPropertyAccessFromIndexSignature": true, - // Recommended Options "strict": true, - "jsx": "react-jsx", "verbatimModuleSyntax": false, "isolatedModules": true, "noUncheckedSideEffectImports": true, "moduleDetection": "force", "skipLibCheck": true, - "allowImportingTsExtensions": true, + "allowImportingTsExtensions": true } } diff --git a/Tests/sytem_under_test/neos8/Dockerfile b/Tests/system_under_test/Dockerfile similarity index 54% rename from Tests/sytem_under_test/neos8/Dockerfile rename to Tests/system_under_test/Dockerfile index 57298be..4c79f1d 100644 --- a/Tests/sytem_under_test/neos8/Dockerfile +++ b/Tests/system_under_test/Dockerfile @@ -1,4 +1,5 @@ -FROM dunglas/frankenphp:1-php8.2-trixie +ARG PHP_VERSION +FROM dunglas/frankenphp:1-php${PHP_VERSION}-trixie COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer @@ -27,37 +28,27 @@ RUN \ chown -R ${USER}:${USER} /config/caddy /data/caddy \ && touch /var/run/caretakerd.key && chown ${USER}:${USER} /var/run/caretakerd.key -# Add Caretaker (Startup manager) -ARG TARGETARCH -RUN curl -SL "https://caretakerd.echocat.org/latest/download/caretakerd-linux-${TARGETARCH}.tar.gz" \ - | tar -xz --exclude caretakerd.html -C /usr/bin - # TODO: still needed? # HOTFIX for ARM64 Architectures and VIPS (see https://github.com/opencv/opencv/issues/14884#issuecomment-706725583 for details) # only needed for development on Apple Silicon Macs RUN echo '. /etc/bash.vips-arm64-hotfix.sh' >> /etc/bash.bashrc # Install Neos base distribution -RUN --mount=type=cache,target=/root/.composer rm -rf /app \ - && composer create-project neos/neos-base-distribution:^8 /app --no-dev - -RUN --mount=type=cache,target=/root/.composer composer require rokka/imagine-vips:0.* +ARG NEOS_VERSION +RUN rm -rf /app \ + && composer create-project neos/neos-base-distribution:^${NEOS_VERSION} /app -# Copy local 2FA package source (Tests/ excluded via .dockerignore) -COPY . /tmp/sandstorm-2fa/ - -# Register local path repository and require local package -RUN --mount=type=cache,target=/root/.composer \ - composer config repositories.sandstorm-2fa \ - '{"type":"path","url":"/tmp/sandstorm-2fa","options":{"symlink":false}}' \ - && composer require sandstorm/neostwofactorauthentication:@dev +RUN composer require rokka/imagine-vips:0.* # Add config files -COPY Tests/sytem_under_test/neos8/sut-files/ / +COPY Tests/system_under_test/sut_file_system_overrides/ / + +ARG ENTRY_POINT_FILE +COPY ${ENTRY_POINT_FILE} /entrypoint.sh # chown for neos data folder and Resources ONLY RUN mkdir -p /app/Data /app/Web/_Resources \ - && chown -R ${USER} /app/Data /app/Web/_Resources \ + && chown -R ${USER} /app \ && chmod +x /entrypoint.sh WORKDIR /app diff --git a/Tests/system_under_test/neos8/docker-compose.yaml b/Tests/system_under_test/neos8/docker-compose.yaml new file mode 100644 index 0000000..254b5d7 --- /dev/null +++ b/Tests/system_under_test/neos8/docker-compose.yaml @@ -0,0 +1,19 @@ +include: + - path: + - ../sut-base-docker-compose.yaml + +services: + neos: + build: + args: + PHP_VERSION: '8.2' + NEOS_VERSION: '8' + ENTRY_POINT_FILE: 'Tests/system_under_test/neos8/entrypoint.sh' + +volumes: + db_neos_data: + name: db_neos8_data + +networks: + neos_SUT: + name: neos8_SUT diff --git a/Tests/sytem_under_test/neos8/sut-files/entrypoint.sh b/Tests/system_under_test/neos8/entrypoint.sh similarity index 51% rename from Tests/sytem_under_test/neos8/sut-files/entrypoint.sh rename to Tests/system_under_test/neos8/entrypoint.sh index aff536a..26e3e7d 100755 --- a/Tests/sytem_under_test/neos8/sut-files/entrypoint.sh +++ b/Tests/system_under_test/neos8/entrypoint.sh @@ -1,7 +1,12 @@ #!/bin/bash set -eou pipefail -# ping db until it is ready +# Register local path repository and require local package +# The code will be mounted into the container by docker-compose, so we can use it as a path repository +composer config repositories.sandstorm-2fa \ + '{"type":"path","url":"/app/DistributionPackages/Sandstorm.NeosTwoFactorAuthentication","options":{"symlink":true}}' \ + && composer require sandstorm/neostwofactorauthentication:@dev + echo "Waiting for database..." until mariadb -h"${DB_NEOS_HOST}" -P"${DB_NEOS_PORT}" -u"${DB_NEOS_USER}" -p"${DB_NEOS_PASSWORD}" -D"${DB_NEOS_DATABASE}" --disable-ssl --silent -e "SELECT 1;" 1>/dev/null 2>/dev/null; do sleep 2 @@ -10,7 +15,6 @@ echo "Database is ready." ./flow flow:cache:flush -# this does the warmup as well ./flow doctrine:migrate yes y | ./flow resource:clean || true @@ -19,5 +23,4 @@ yes y | ./flow resource:clean || true ./flow resource:publish --collection static -# 2. We now can start caretakerd, which will run the remaining steps in parallel and restart the container if they fail. -/usr/bin/caretakerd run +frankenphp run --config /etc/frankenphp/Caddyfile diff --git a/Tests/system_under_test/neos9/docker-compose.yaml b/Tests/system_under_test/neos9/docker-compose.yaml new file mode 100644 index 0000000..1a521b8 --- /dev/null +++ b/Tests/system_under_test/neos9/docker-compose.yaml @@ -0,0 +1,22 @@ +include: + - path: + - ../sut-base-docker-compose.yaml + +services: + neos: + build: + args: + PHP_VERSION: '8.5' + NEOS_VERSION: '9' + ENTRY_POINT_FILE: 'Tests/system_under_test/neos9/entrypoint.sh' + + db: + image: mariadb:11.4 + +volumes: + db_neos_data: + name: db_neos9_data + +networks: + neos_SUT: + name: neos9_SUT diff --git a/Tests/sytem_under_test/neos9/sut-files/entrypoint.sh b/Tests/system_under_test/neos9/entrypoint.sh similarity index 50% rename from Tests/sytem_under_test/neos9/sut-files/entrypoint.sh rename to Tests/system_under_test/neos9/entrypoint.sh index b44ca14..093df44 100755 --- a/Tests/sytem_under_test/neos9/sut-files/entrypoint.sh +++ b/Tests/system_under_test/neos9/entrypoint.sh @@ -1,15 +1,20 @@ #!/bin/bash set -eou pipefail -# ping db until it is ready +# Register local path repository and require local package +# The code will be mounted into the container by docker-compose, so we can use it as a path repository +composer config repositories.sandstorm-2fa \ + '{"type":"path","url":"/app/DistributionPackages/Sandstorm.NeosTwoFactorAuthentication","options":{"symlink":true}}' \ + && composer require sandstorm/neostwofactorauthentication:@dev + echo "Waiting for database..." until mariadb -h"${DB_NEOS_HOST}" -P"${DB_NEOS_PORT}" -u"${DB_NEOS_USER}" -p"${DB_NEOS_PASSWORD}" -D"${DB_NEOS_DATABASE}" --disable-ssl --silent -e "SELECT 1;" 1>/dev/null 2>/dev/null; do sleep 2 done +echo "Database is ready." ./flow flow:cache:flush -# this does the warmup as well ./flow doctrine:migrate yes y | ./flow resource:clean || true @@ -21,5 +26,4 @@ yes y | ./flow resource:clean || true ./flow resource:publish --collection static -# 2. We now can start caretakerd, which will run the remaining steps in parallel and restart the container if they fail. -/usr/bin/caretakerd run +frankenphp run --config /etc/frankenphp/Caddyfile diff --git a/Tests/system_under_test/sut-base-docker-compose.yaml b/Tests/system_under_test/sut-base-docker-compose.yaml new file mode 100644 index 0000000..704af70 --- /dev/null +++ b/Tests/system_under_test/sut-base-docker-compose.yaml @@ -0,0 +1,72 @@ +services: + neos: + user: www-data:www-data + build: + context: ../../ + dockerfile: Tests/system_under_test/Dockerfile + args: + # minimum PHP version is '8.2' because that's the lowest version frankenPHP provides an image for + PHP_VERSION: '8.2' + # used to install the system under test + # which is a neos-base-distribution:^${NEOS_VERSION} + NEOS_VERSION: '8' + # docker will use this as entrypoint + ENTRY_POINT_FILE: 'Tests/system_under_test/neos8/entrypoint.sh' + environment: + FLOW_CONTEXT: "${FLOW_CONTEXT:-Production/E2E-SUT}" + # DB connection + DB_NEOS_HOST: 'db' + DB_NEOS_PORT: 3306 + DB_NEOS_USER: 'neos' + DB_NEOS_PASSWORD: 'neos' + DB_NEOS_DATABASE: 'neos' + # Redis connection + REDIS_HOST: 'redis' + REDIS_PORT: 6379 + # this is safe because the neos container port is only exposed to the local interface + # This means that the neos container is ALWAYS accessed through the front facing Ingress + FLOW_HTTP_TRUSTED_PROXIES: '*' + volumes: + - ../../Classes:/app/DistributionPackages/Sandstorm.NeosTwoFactorAuthentication/Classes:cached + - ../../Configuration:/app/DistributionPackages/Sandstorm.NeosTwoFactorAuthentication/Configuration:cached + - ../../Migrations:/app/DistributionPackages/Sandstorm.NeosTwoFactorAuthentication/Migrations:cached + - ../../Resources:/app/DistributionPackages/Sandstorm.NeosTwoFactorAuthentication/Resources:cached + - ../../composer.json:/app/DistributionPackages/Sandstorm.NeosTwoFactorAuthentication/composer.json:cached + networks: + - neos_SUT + ports: + - 8081:8081 + depends_on: + - db + - redis + + db: + image: mariadb:10.11 + restart: always + ports: + - "13306:3306" + networks: + - neos_SUT + environment: + MARIADB_RANDOM_ROOT_PASSWORD: 'true' + MARIADB_DATABASE: 'neos' + MARIADB_USER: 'neos' + MARIADB_PASSWORD: 'neos' + MARIADB_AUTO_UPGRADE: 1 + volumes: + - db_neos_data:/var/lib/mysql + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + + redis: + image: redis:7 + restart: always + networks: + - neos_SUT + +volumes: + db_neos_data: + name: db_neos_data + +networks: + neos_SUT: + name: neos_SUT diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Caches.yaml b/Tests/system_under_test/sut_file_system_overrides/app/Configuration/Production/E2E-SUT/Caches.yaml similarity index 100% rename from Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Caches.yaml rename to Tests/system_under_test/sut_file_system_overrides/app/Configuration/Production/E2E-SUT/Caches.yaml diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForAll/Settings.2FA.yaml b/Tests/system_under_test/sut_file_system_overrides/app/Configuration/Production/E2E-SUT/EnforceForAll/Settings.2FA.yaml similarity index 100% rename from Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForAll/Settings.2FA.yaml rename to Tests/system_under_test/sut_file_system_overrides/app/Configuration/Production/E2E-SUT/EnforceForAll/Settings.2FA.yaml diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForProvider/Settings.2FA.yaml b/Tests/system_under_test/sut_file_system_overrides/app/Configuration/Production/E2E-SUT/EnforceForProvider/Settings.2FA.yaml similarity index 100% rename from Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForProvider/Settings.2FA.yaml rename to Tests/system_under_test/sut_file_system_overrides/app/Configuration/Production/E2E-SUT/EnforceForProvider/Settings.2FA.yaml diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Policy.yaml b/Tests/system_under_test/sut_file_system_overrides/app/Configuration/Production/E2E-SUT/EnforceForRole/Policy.yaml similarity index 100% rename from Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Policy.yaml rename to Tests/system_under_test/sut_file_system_overrides/app/Configuration/Production/E2E-SUT/EnforceForRole/Policy.yaml diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml b/Tests/system_under_test/sut_file_system_overrides/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml similarity index 100% rename from Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml rename to Tests/system_under_test/sut_file_system_overrides/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/IssuerNameChange/Settings.2FA.yaml b/Tests/system_under_test/sut_file_system_overrides/app/Configuration/Production/E2E-SUT/IssuerNameChange/Settings.2FA.yaml similarity index 100% rename from Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/IssuerNameChange/Settings.2FA.yaml rename to Tests/system_under_test/sut_file_system_overrides/app/Configuration/Production/E2E-SUT/IssuerNameChange/Settings.2FA.yaml diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Settings.yaml b/Tests/system_under_test/sut_file_system_overrides/app/Configuration/Production/E2E-SUT/Settings.yaml similarity index 100% rename from Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/E2E-SUT/Settings.yaml rename to Tests/system_under_test/sut_file_system_overrides/app/Configuration/Production/E2E-SUT/Settings.yaml diff --git a/Tests/sytem_under_test/neos8/sut-files/etc/bash.vips-arm64-hotfix.sh b/Tests/system_under_test/sut_file_system_overrides/etc/bash.vips-arm64-hotfix.sh similarity index 100% rename from Tests/sytem_under_test/neos8/sut-files/etc/bash.vips-arm64-hotfix.sh rename to Tests/system_under_test/sut_file_system_overrides/etc/bash.vips-arm64-hotfix.sh diff --git a/Tests/sytem_under_test/neos8/sut-files/etc/frankenphp/Caddyfile b/Tests/system_under_test/sut_file_system_overrides/etc/frankenphp/Caddyfile similarity index 100% rename from Tests/sytem_under_test/neos8/sut-files/etc/frankenphp/Caddyfile rename to Tests/system_under_test/sut_file_system_overrides/etc/frankenphp/Caddyfile diff --git a/Tests/sytem_under_test/neos8/sut-files/usr/local/etc/php/conf.d/php-ini-overrides.ini b/Tests/system_under_test/sut_file_system_overrides/usr/local/etc/php/conf.d/php-ini-overrides.ini similarity index 100% rename from Tests/sytem_under_test/neos8/sut-files/usr/local/etc/php/conf.d/php-ini-overrides.ini rename to Tests/system_under_test/sut_file_system_overrides/usr/local/etc/php/conf.d/php-ini-overrides.ini diff --git a/Tests/sytem_under_test/neos8/docker-compose.yaml b/Tests/sytem_under_test/neos8/docker-compose.yaml deleted file mode 100644 index 08f20d0..0000000 --- a/Tests/sytem_under_test/neos8/docker-compose.yaml +++ /dev/null @@ -1,57 +0,0 @@ -services: - neos: - user: www-data:www-data - build: - context: ../../../ - dockerfile: Tests/sytem_under_test/neos8/Dockerfile - environment: - FLOW_CONTEXT: '${FLOW_CONTEXT:-Production/E2E-SUT}' - # DB connection - DB_NEOS_HOST: 'db' - DB_NEOS_PORT: 3306 - DB_NEOS_USER: 'neos' - DB_NEOS_PASSWORD: 'neos' - DB_NEOS_DATABASE: 'neos' - # Redis connection - REDIS_HOST: 'redis' - REDIS_PORT: 6379 - # this is safe because the neos container port is only exposed to the local interface - # This means that the neos container is ALWAYS accessed through the front facing Ingress - FLOW_HTTP_TRUSTED_PROXIES: '*' - networks: - - neos8_SUT - ports: - - 8081:8081 - depends_on: - - db - - redis - - db: - image: mariadb:10.11 - restart: always - ports: - - "13306:3306" - networks: - - neos8_SUT - environment: - MARIADB_RANDOM_ROOT_PASSWORD: 'true' - MARIADB_DATABASE: 'neos' - MARIADB_USER: 'neos' - MARIADB_PASSWORD: 'neos' - MARIADB_AUTO_UPGRADE: 1 - volumes: - - db_neos8_data:/var/lib/mysql - command: ['mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci'] - - redis: - image: redis:7 - restart: always - networks: - - neos8_SUT - -volumes: - db_neos8_data: - -networks: - neos8_SUT: - name: neos8_SUT diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/Settings.yaml.example b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/Settings.yaml.example deleted file mode 100644 index 58050de..0000000 --- a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Production/Settings.yaml.example +++ /dev/null @@ -1,19 +0,0 @@ -# # -# Example Settings # -# # -# This file contains settings for various parts of the application. # -# Copy this file to Settings.yaml, and adjust as necessary. # -# # -# Please refer to the default settings file(s) or the manuals for # -# possible configuration options. # -# # - -Neos: - Flow: - persistence: - # For a reference of the possible options, take a look at - # https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html - backendOptions: - dbname: 'flow' # adjust to your database name - user: '' # adjust to your database user - password: '' # adjust to your database password diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/README b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/README deleted file mode 100644 index 1f840e7..0000000 --- a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/README +++ /dev/null @@ -1,29 +0,0 @@ -============================================================================= -Flow Configuration Directory -============================================================================= - -This directory contains the configuration for the Flow framework and the -application based on it. The configuration files only contain additions and -modifications of the original configuration and therefore don't contain all -possible configuration options. - -The following files play a role in the configuration system: - - Settings.yaml Various application-level settings can be set in this file. - - Objects.yaml Additional objects configuration, overriding the - configuration which was defined in the various packages. - - Routes.yaml Provides the routing configuration for the MVC framework. - - Views.yaml Can be used to influence resolutions of views in MVC. - - Policy.yaml Provides the configuration for the security framework. - - Caches.yaml Provides the configuration for the caching framework. - -All of these files become active in any application context if they reside -in the main Configuration directory (the same directory where this README is -located). However, context-specific configuration may be defined in sub directories -which carry the same name as the application context. These configuration -files are loaded after the default and global configuration has been invoked. \ No newline at end of file diff --git a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Settings.yaml.example b/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Settings.yaml.example deleted file mode 100644 index c02aafc..0000000 --- a/Tests/sytem_under_test/neos8/sut-files/app/Configuration/Settings.yaml.example +++ /dev/null @@ -1,39 +0,0 @@ -# -# Example Settings -# -# This file can contain settings for your Flow application that override -# the various defaults defined by other packages. -# -# Copy this file to Settings.yaml, and adjust as necessary. -# -# For possible configuration options you may refer to the packages' -# default settings file(s) in the corresponding package directories (for -# example, Packages/Framework/Neos.Flow/Configuration/Settings.yaml) -# or read the fine manuals. - -Neos: - Flow: - persistence: - - # It is good practice to not specify user name and password of the database - # connection in this global Settings.yaml file. Rather specify them in the - # settings of the respective context (Production / Development ...). - # For a reference of the possible options, take a look at - # https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html - backendOptions: - driver: 'pdo_mysql' # use pdo_pgsql for PostgreSQL - charset: 'utf8mb4' # change to utf8 when using PostgreSQL - host: '127.0.0.1' # adjust to your database host - - # The following lines register the Flow default routes - # For productive use you should move this setting to the main package of this - # application and/or create custom routes. - mvc: - routes: - 'Neos.Flow': TRUE - - # You might need to uncomment the following lines and specify - # the location of the PHP binary manually. -# core: -# phpBinaryPathAndFilename: 'C:/path/to/php.exe' - diff --git a/Tests/sytem_under_test/neos8/sut-files/etc/caretakerd.yaml b/Tests/sytem_under_test/neos8/sut-files/etc/caretakerd.yaml deleted file mode 100755 index f57404b..0000000 --- a/Tests/sytem_under_test/neos8/sut-files/etc/caretakerd.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# DOCS: https://caretakerd.echocat.org/#configuration.structure - -rpc: - enabled: true - -services: - php-fpm: - type: master - command: ['frankenphp', 'run', '--config', '/etc/frankenphp/Caddyfile'] diff --git a/Tests/sytem_under_test/neos9/Dockerfile b/Tests/sytem_under_test/neos9/Dockerfile deleted file mode 100644 index 2b585b7..0000000 --- a/Tests/sytem_under_test/neos9/Dockerfile +++ /dev/null @@ -1,67 +0,0 @@ -FROM dunglas/frankenphp:1-php8.5-trixie - -COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer - -# reference: https://github.com/mlocati/docker-php-extension-installer -RUN install-php-extensions \ - intl \ - bcmath \ - opcache \ - pdo \ - pdo_mysql \ - xsl \ - ffi \ - vips \ - redis - -RUN apt update \ - && apt install -y git unzip mariadb-client \ - && apt clean \ - && rm -rf /var/lib/apt/lists/* - -ARG USER=www-data - -# Give write access to /config/caddy and /data/caddy -RUN \ - useradd ${USER}; \ - chown -R ${USER}:${USER} /config/caddy /data/caddy \ - && touch /var/run/caretakerd.key && chown ${USER}:${USER} /var/run/caretakerd.key - -# Add Caretaker (Startup manager) -ARG TARGETARCH -RUN curl -SL "https://caretakerd.echocat.org/latest/download/caretakerd-linux-${TARGETARCH}.tar.gz" \ - | tar -xz --exclude caretakerd.html -C /usr/bin - -# TODO: still needed? -# HOTFIX for ARM64 Architectures and VIPS (see https://github.com/opencv/opencv/issues/14884#issuecomment-706725583 for details) -# only needed for development on Apple Silicon Macs -RUN echo '. /etc/bash.vips-arm64-hotfix.sh' >> /etc/bash.bashrc - -# Install Neos base distribution -# TODO: We can not specify `--no-dev` here, because the the package wants to run `./flow welcome` as `"post-create-project-cmd"` but that needs the dev dependencies to be installed. -RUN --mount=type=cache,target=/root/.composer rm -rf /app \ - && composer create-project neos/neos-base-distribution:^9 /app - -RUN --mount=type=cache,target=/root/.composer composer require rokka/imagine-vips:0.* - -# Copy local 2FA package source (Tests/ excluded via .dockerignore) -COPY . /tmp/sandstorm-2fa/ - -# Register local path repository and require local package -RUN --mount=type=cache,target=/root/.composer \ - composer config repositories.sandstorm-2fa \ - '{"type":"path","url":"/tmp/sandstorm-2fa","options":{"symlink":false}}' \ - && composer require sandstorm/neostwofactorauthentication:@dev - -# Add config files -COPY Tests/sytem_under_test/neos9/sut-files/ / - -# chown for neos data folder and Resources ONLY -RUN mkdir -p /app/Data /app/Web/_Resources \ - && chown -R ${USER} /app/Data /app/Web/_Resources \ - && chmod +x /entrypoint.sh - -WORKDIR /app -USER ${USER} - -ENTRYPOINT ["/entrypoint.sh"] diff --git a/Tests/sytem_under_test/neos9/docker-compose.yaml b/Tests/sytem_under_test/neos9/docker-compose.yaml deleted file mode 100644 index d6a11f1..0000000 --- a/Tests/sytem_under_test/neos9/docker-compose.yaml +++ /dev/null @@ -1,57 +0,0 @@ -services: - neos: - user: www-data:www-data - build: - context: ../../../ - dockerfile: Tests/sytem_under_test/neos9/Dockerfile - environment: - FLOW_CONTEXT: '${FLOW_CONTEXT:-Production/E2E-SUT}' - # DB connection - DB_NEOS_HOST: 'db' - DB_NEOS_PORT: 3306 - DB_NEOS_USER: 'neos' - DB_NEOS_PASSWORD: 'neos' - DB_NEOS_DATABASE: 'neos' - # Redis connection - REDIS_HOST: 'redis' - REDIS_PORT: 6379 - # this is safe because the neos container port is only exposed to the local interface - # This means that the neos container is ALWAYS accessed through the front facing Ingress - FLOW_HTTP_TRUSTED_PROXIES: '*' - networks: - - neos9_SUT - ports: - - 8081:8081 - depends_on: - - db - - redis - - db: - image: mariadb:10.11 - restart: always - ports: - - "13306:3306" - networks: - - neos9_SUT - environment: - MARIADB_RANDOM_ROOT_PASSWORD: 'true' - MARIADB_DATABASE: 'neos' - MARIADB_USER: 'neos' - MARIADB_PASSWORD: 'neos' - MARIADB_AUTO_UPGRADE: 1 - volumes: - - db_neos9_data:/var/lib/mysql - command: ['mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci'] - - redis: - image: redis:7 - restart: always - networks: - - neos9_SUT - -volumes: - db_neos9_data: - -networks: - neos9_SUT: - name: neos9_SUT diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Caches.yaml b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Caches.yaml deleted file mode 100644 index 3844103..0000000 --- a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Caches.yaml +++ /dev/null @@ -1,56 +0,0 @@ -Flow_Mvc_Routing_Route: - backend: 'Neos\Cache\Backend\RedisBackend' - backendOptions: - hostname: '%env:REDIS_HOST%' - port: '%env:REDIS_PORT%' - # starting with database 2 here, since 0 and 1 are used and flushed by - # the core unit tests and should not be used if possible. - database: 2 - defaultLifetime: 0 - -Flow_Mvc_Routing_Resolve: - backend: 'Neos\Cache\Backend\RedisBackend' - backendOptions: - hostname: '%env:REDIS_HOST%' - port: '%env:REDIS_PORT%' - database: 2 - defaultLifetime: 0 - -# We want to test cache settings on Fusion components. -# Therefore, at one moment in the BDD test, we need to actively trigger the ContentCacheFlusher to invalidate the tags. -# Since we have multiple Flow contexts during tests, the cache settings are unified for all contexts. -# Explanation: -# - The system under test runs in Production/E2E-SUT; it writes cache entries -# - The Test Runner (behat) runs in Testing/Behat; it needs to invalidate cache for the SuT via service API call -# For now, we simply keep the cache settings in sync between those two profiles (like we do with the DB config). -Neos_Fusion_Content: - backend: 'Neos\Cache\Backend\RedisBackend' - backendOptions: - hostname: '%env:REDIS_HOST%' - port: '%env:REDIS_PORT%' - database: 2 - defaultLifetime: 0 - -Flow_Session_MetaData: - backend: 'Neos\Cache\Backend\RedisBackend' - backendOptions: - hostname: '%env:REDIS_HOST%' - port: '%env:REDIS_PORT%' - database: 2 - defaultLifetime: 0 - -Flow_Session_Storage: - backend: 'Neos\Cache\Backend\RedisBackend' - backendOptions: - hostname: '%env:REDIS_HOST%' - port: '%env:REDIS_PORT%' - database: 2 - defaultLifetime: 0 - -Neos_Media_ImageSize: - backend: 'Neos\Cache\Backend\RedisBackend' - backendOptions: - hostname: '%env:REDIS_HOST%' - port: '%env:REDIS_PORT%' - database: 2 - defaultLifetime: 0 diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForAll/Settings.2FA.yaml b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForAll/Settings.2FA.yaml deleted file mode 100644 index fcc6568..0000000 --- a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForAll/Settings.2FA.yaml +++ /dev/null @@ -1,4 +0,0 @@ -Sandstorm: - NeosTwoFactorAuthentication: - # enforce 2FA for all users - enforceTwoFactorAuthentication: true diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForProvider/Settings.2FA.yaml b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForProvider/Settings.2FA.yaml deleted file mode 100644 index 1921041..0000000 --- a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForProvider/Settings.2FA.yaml +++ /dev/null @@ -1,4 +0,0 @@ -Sandstorm: - NeosTwoFactorAuthentication: - # enforce 2FA for specific authentication providers (e.g. Neos.Neos:Backend) - enforce2FAForAuthenticationProviders : ['Neos.Neos:Backend'] diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml deleted file mode 100644 index e4a4aca..0000000 --- a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/EnforceForRole/Settings.2FA.yaml +++ /dev/null @@ -1,4 +0,0 @@ -Sandstorm: - NeosTwoFactorAuthentication: - # enforce 2FA for specific roles (e.g. Neos.Neos:Administrator) - enforce2FAForRoles: ['Neos.Neos:Administrator', 'Neos.Neos:SecondFactorUser'] diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/IssuerNameChange/Settings.2FA.yaml b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/IssuerNameChange/Settings.2FA.yaml deleted file mode 100644 index 44336f1..0000000 --- a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/IssuerNameChange/Settings.2FA.yaml +++ /dev/null @@ -1,4 +0,0 @@ -Sandstorm: - NeosTwoFactorAuthentication: - # (optional) if set this will be used as a naming convention for the TOTP. If empty the Site name will be used - issuerName: 'Test Issuer' diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Policy.yaml b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Policy.yaml deleted file mode 100644 index 0eba65e..0000000 --- a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Policy.yaml +++ /dev/null @@ -1,5 +0,0 @@ -roles: - 'Neos.Neos:SecondFactorUser': - label: 2FA Required User - description: Just for testing role based 2FA enforcement - parentRoles: ['Neos.Neos:Editor'] diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Settings.yaml b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Settings.yaml deleted file mode 100644 index 27101ea..0000000 --- a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/E2E-SUT/Settings.yaml +++ /dev/null @@ -1,26 +0,0 @@ -Neos: - Flow: - persistence: - backendOptions: - driver: 'pdo_mysql' - charset: 'utf8mb4' - host: '%env:DB_NEOS_HOST%' - port: '%env:DB_NEOS_PORT%' - password: '%env:DB_NEOS_PASSWORD%' - user: '%env:DB_NEOS_USER%' - dbname: '%env:DB_NEOS_DATABASE%' - cache: - applicationIdentifier: 'app' - - Imagine: - driver: 'Vips' - enabledDrivers: - Vips: true - Gd: true - Imagick: true - - Media: - image: - defaultOptions: - # The Vips driver does not support interlace - interlace: ~ diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/Settings.yaml.example b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/Settings.yaml.example deleted file mode 100644 index 58050de..0000000 --- a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Production/Settings.yaml.example +++ /dev/null @@ -1,19 +0,0 @@ -# # -# Example Settings # -# # -# This file contains settings for various parts of the application. # -# Copy this file to Settings.yaml, and adjust as necessary. # -# # -# Please refer to the default settings file(s) or the manuals for # -# possible configuration options. # -# # - -Neos: - Flow: - persistence: - # For a reference of the possible options, take a look at - # https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html - backendOptions: - dbname: 'flow' # adjust to your database name - user: '' # adjust to your database user - password: '' # adjust to your database password diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/README b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/README deleted file mode 100644 index 1f840e7..0000000 --- a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/README +++ /dev/null @@ -1,29 +0,0 @@ -============================================================================= -Flow Configuration Directory -============================================================================= - -This directory contains the configuration for the Flow framework and the -application based on it. The configuration files only contain additions and -modifications of the original configuration and therefore don't contain all -possible configuration options. - -The following files play a role in the configuration system: - - Settings.yaml Various application-level settings can be set in this file. - - Objects.yaml Additional objects configuration, overriding the - configuration which was defined in the various packages. - - Routes.yaml Provides the routing configuration for the MVC framework. - - Views.yaml Can be used to influence resolutions of views in MVC. - - Policy.yaml Provides the configuration for the security framework. - - Caches.yaml Provides the configuration for the caching framework. - -All of these files become active in any application context if they reside -in the main Configuration directory (the same directory where this README is -located). However, context-specific configuration may be defined in sub directories -which carry the same name as the application context. These configuration -files are loaded after the default and global configuration has been invoked. \ No newline at end of file diff --git a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Settings.yaml.example b/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Settings.yaml.example deleted file mode 100644 index c02aafc..0000000 --- a/Tests/sytem_under_test/neos9/sut-files/app/Configuration/Settings.yaml.example +++ /dev/null @@ -1,39 +0,0 @@ -# -# Example Settings -# -# This file can contain settings for your Flow application that override -# the various defaults defined by other packages. -# -# Copy this file to Settings.yaml, and adjust as necessary. -# -# For possible configuration options you may refer to the packages' -# default settings file(s) in the corresponding package directories (for -# example, Packages/Framework/Neos.Flow/Configuration/Settings.yaml) -# or read the fine manuals. - -Neos: - Flow: - persistence: - - # It is good practice to not specify user name and password of the database - # connection in this global Settings.yaml file. Rather specify them in the - # settings of the respective context (Production / Development ...). - # For a reference of the possible options, take a look at - # https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html - backendOptions: - driver: 'pdo_mysql' # use pdo_pgsql for PostgreSQL - charset: 'utf8mb4' # change to utf8 when using PostgreSQL - host: '127.0.0.1' # adjust to your database host - - # The following lines register the Flow default routes - # For productive use you should move this setting to the main package of this - # application and/or create custom routes. - mvc: - routes: - 'Neos.Flow': TRUE - - # You might need to uncomment the following lines and specify - # the location of the PHP binary manually. -# core: -# phpBinaryPathAndFilename: 'C:/path/to/php.exe' - diff --git a/Tests/sytem_under_test/neos9/sut-files/etc/bash.vips-arm64-hotfix.sh b/Tests/sytem_under_test/neos9/sut-files/etc/bash.vips-arm64-hotfix.sh deleted file mode 100644 index 9bb2110..0000000 --- a/Tests/sytem_under_test/neos9/sut-files/etc/bash.vips-arm64-hotfix.sh +++ /dev/null @@ -1,17 +0,0 @@ -if [ "$(uname -m)" = "aarch64" ]; then - echo "Using LD_Preload workaround for gomp issue" - - # WORKAROUND for Apple M1 Chips. Without this line, we get the error message: - # - # Warning: PHP Startup: Unable to load dynamic library 'vips.so' (tried: - # /usr/local/lib/php/extensions/no-debug-non-zts-20200930/vips.so - # (/usr/lib/aarch64-linux-gnu/libgomp.so.1: cannot allocate memory in - # static TLS block), /usr/local/lib/php/extensions/no-debug-non-zts-20200930/vips.so.so - # (/usr/local/lib/php/extensions/no-debug-non-zts-20200930/vips.so.so: cannot open - # shared object file: No such file or directory)) in Unknown on line 0 - # - # This error seems to be related to some OpenCV bug or issue described at - # https://github.com/opencv/opencv/issues/14884#issuecomment-706725583 - # And the workaround is to ensure that libgomp is loaded first. - export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libgomp.so.1 -fi diff --git a/Tests/sytem_under_test/neos9/sut-files/etc/caretakerd.yaml b/Tests/sytem_under_test/neos9/sut-files/etc/caretakerd.yaml deleted file mode 100755 index f57404b..0000000 --- a/Tests/sytem_under_test/neos9/sut-files/etc/caretakerd.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# DOCS: https://caretakerd.echocat.org/#configuration.structure - -rpc: - enabled: true - -services: - php-fpm: - type: master - command: ['frankenphp', 'run', '--config', '/etc/frankenphp/Caddyfile'] diff --git a/Tests/sytem_under_test/neos9/sut-files/etc/frankenphp/Caddyfile b/Tests/sytem_under_test/neos9/sut-files/etc/frankenphp/Caddyfile deleted file mode 100644 index c757047..0000000 --- a/Tests/sytem_under_test/neos9/sut-files/etc/frankenphp/Caddyfile +++ /dev/null @@ -1,41 +0,0 @@ -# The Caddyfile is an easy way to configure FrankenPHP and the Caddy web server. -# -# https://frankenphp.dev/docs/config -# https://caddyserver.com/docs/caddyfile -# https://github.com/php/frankenphp/blob/main/caddy/frankenphp/Caddyfile -{ - skip_install_trust - - # debug - - frankenphp { - # num_threads <num_threads> # Sets the number of PHP threads to start. Default: 2x the number of available CPUs. - #max_threads <num_threads> # Limits the number of additional PHP threads that can be started at runtime. Default: num_threads. Can be set to 'auto'. - #max_wait_time <duration> # Sets the maximum time a request may wait for a free PHP thread before timing out. Default: disabled. - #php_ini <key> <value> # Set a php.ini directive. Can be used several times to set multiple directives. - } -} - -:8081 { - # log - - root /app/Web - encode zstd br gzip - - request_body { - max_size 256MB - } - - # Block direct access to PHP files except index.php - @blockPhp { - path *.php - not path /index.php - } - - handle @blockPhp { - respond 404 - } - - - php_server -} diff --git a/Tests/sytem_under_test/neos9/sut-files/usr/local/etc/php/conf.d/php-ini-overrides.ini b/Tests/sytem_under_test/neos9/sut-files/usr/local/etc/php/conf.d/php-ini-overrides.ini deleted file mode 100644 index 1190ae0..0000000 --- a/Tests/sytem_under_test/neos9/sut-files/usr/local/etc/php/conf.d/php-ini-overrides.ini +++ /dev/null @@ -1,20 +0,0 @@ -; ================== -; Various defaults -; ================== - -; for PHP >= 8.1, disable deprecations to temporarily make Neos/Flow work -error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT -memory_limit = 512M -upload_max_filesize = 256M -post_max_size = 256M -date.timezone = "Europe/Berlin" - -; ================== -; VIPS -; ================== -; required for VIPS -ffi.enable = true - -; for VIPS on PHP >= 8.3 the following line is required, -; see https://github.com/libvips/php-vips -zend.max_allowed_stack_size = -1