Skip to content

refactor(ZMSKVR-686): zmsapiautomation to zmsautomation module with ATAF integration#1892

Open
ThomasAFink wants to merge 200 commits intonextfrom
feat-zmskvr-1124-refactor-to-use-cucumber-and-ataf-compatibility-and-setup-first-test-case
Open

refactor(ZMSKVR-686): zmsapiautomation to zmsautomation module with ATAF integration#1892
ThomasAFink wants to merge 200 commits intonextfrom
feat-zmskvr-1124-refactor-to-use-cucumber-and-ataf-compatibility-and-setup-first-test-case

Conversation

@ThomasAFink
Copy link
Member

@ThomasAFink ThomasAFink commented Feb 5, 2026

This commit implements the ATAF (Test Automation Framework) integration for ZMS API automation tests. The new zmsautomation module is a copy of zmsapiautomation with ATAF/Cucumber integration, while the original zmsapiautomation module remains unchanged for CI compatibility.

Phase 1: Project Setup

  • Copied zmsapiautomation to zmsautomation (both modules coexist)
  • Moved Flyway migrations from flyway/ to src/main/resources/db/migration/
  • Created ATAF directory structure (ataf/data, ataf/hooks, ataf/runner, ataf/steps)
  • Created features directory structure (features/zmsapi, features/zmscitizenapi)
  • Updated pom.xml with Maven profiles:
    • standalone profile (default, active) - for existing JUnit tests
    • ataf profile - with Artifactory repository and ATAF dependencies
  • Configured Surefire plugin for both JUnit (standalone) and TestNG (ataf)
  • Added dependencies: Cucumber, TestNG, Flyway, MySQL connector

Phase 2: Configuration Files

  • Created cucumber.properties with feature paths and glue code configuration
  • Created testautomation.properties with log level settings
  • Created log4j2-test.properties with console appender
  • Created testng.xml for TestNG test execution

Phase 3: ATAF Integration Code

  • Created TestData.java for environment configuration (LOCAL, DEV)
  • Created DatabaseHook.java with @BeforeSuite Flyway migration setup
  • Created TestRunner.java extending BasicTestNGRunner
  • Created ZmsApiSteps.java with step definitions for ZMS API endpoints:
    • Status endpoint
    • Availability endpoint
    • Scope information endpoint
    • Error handling scenarios
  • Created CitizenApiSteps.java with step definitions for Citizen API:
    • Offices and services endpoint
    • Booking flow
    • Cancellation flow

Phase 4: Feature Files

  • Converted StatusEndpointTest to features/zmsapi/status.feature
  • Converted OfficesAndServicesEndpointTest to features/zmscitizenapi/offices-and-services.feature
  • Created features/zmsapi/availability.feature
  • Created features/zmsapi/appointments.feature
  • Created features/zmscitizenapi/booking.feature

Phase 5: Documentation

  • Created README.md with:
    • Prerequisites and project structure
    • Test execution instructions for both profiles
    • Environment variables documentation
    • Database setup information
    • Test tags and CI/CD notes

Phase 6: Migration Examples

  • Created features/zmsapi/scopes.feature (example with Scenario Outline)
  • Created features/zmscitizenapi/cancellation.feature (example with multiple scenarios)
  • Created features/zmsapi/error-handling.feature (error scenario examples)
  • Created features/zmsapi/data-driven-example.feature (data-driven testing patterns)
  • Created features/MIGRATION_GUIDE.md with:
    • Before/after conversion examples
    • Common step definition patterns
    • Best practices and migration checklist

Key Features:

  • Dual profile support: standalone (CI) and ataf (local development)
  • Automatic Flyway database migrations before test execution
  • Comprehensive step definitions for common API operations
  • Example feature files demonstrating various Cucumber patterns
  • Migration guide for converting additional JUnit tests

The zmsapiautomation module remains unchanged and continues to run in GitHub Actions CI. The zmsautomation module is ready for local testing with the ATAF profile (requires Artifactory access). Both modules will coexist until Phase 7 when ATAF becomes open source and CI can be updated.

Pull Request Checklist (Feature Branch to next):

  • Ich habe die neuesten Änderungen aus dem next Branch in meinen Feature-Branch gemergt.
  • Das Code-Review wurde abgeschlossen.
  • Fachliche Tests wurden durchgeführt und sind abgeschlossen.

Summary by CodeRabbit

  • API Testing Framework

    • REST-assured-based API testing for ZMS and Citizen APIs with Cucumber BDD scenarios.
  • UI Testing Framework

    • Selenium-based UI automation for admin, statistics, and citizen-facing interfaces.
  • Database Automation

    • Flyway migrations and test data seeding for Munich test environment.
  • Test Scenarios

    • Cucumber feature files documenting API and UI test workflows.
  • Environment Setup

    • Docker and dev container configurations with browser testing infrastructure.
  • Documentation

    • Updated guides for test execution and environment configuration.

This commit implements the ATAF (Test Automation Framework) integration
for ZMS API automation tests. The new zmsautomation module is a copy
of zmsapiautomation with ATAF/Cucumber integration, while the original
zmsapiautomation module remains unchanged for CI compatibility.

Phase 1: Project Setup
- Copied zmsapiautomation to zmsautomation (both modules coexist)
- Moved Flyway migrations from flyway/ to src/main/resources/db/migration/
- Created ATAF directory structure (ataf/data, ataf/hooks, ataf/runner, ataf/steps)
- Created features directory structure (features/zmsapi, features/zmscitizenapi)
- Updated pom.xml with Maven profiles:
  * standalone profile (default, active) - for existing JUnit tests
  * ataf profile - with Artifactory repository and ATAF dependencies
- Configured Surefire plugin for both JUnit (standalone) and TestNG (ataf)
- Added dependencies: Cucumber, TestNG, Flyway, MySQL connector

Phase 2: Configuration Files
- Created cucumber.properties with feature paths and glue code configuration
- Created testautomation.properties with log level settings
- Created log4j2-test.properties with console appender
- Created testng.xml for TestNG test execution

Phase 3: ATAF Integration Code
- Created TestData.java for environment configuration (LOCAL, DEV)
- Created DatabaseHook.java with @BeforeSuite Flyway migration setup
- Created TestRunner.java extending BasicTestNGRunner
- Created ZmsApiSteps.java with step definitions for ZMS API endpoints:
  * Status endpoint
  * Availability endpoint
  * Scope information endpoint
  * Error handling scenarios
- Created CitizenApiSteps.java with step definitions for Citizen API:
  * Offices and services endpoint
  * Booking flow
  * Cancellation flow

Phase 4: Feature Files
- Converted StatusEndpointTest to features/zmsapi/status.feature
- Converted OfficesAndServicesEndpointTest to features/zmscitizenapi/offices-and-services.feature
- Created features/zmsapi/availability.feature
- Created features/zmsapi/appointments.feature
- Created features/zmscitizenapi/booking.feature

Phase 5: Documentation
- Created README.md with:
  * Prerequisites and project structure
  * Test execution instructions for both profiles
  * Environment variables documentation
  * Database setup information
  * Test tags and CI/CD notes

Phase 6: Migration Examples
- Created features/zmsapi/scopes.feature (example with Scenario Outline)
- Created features/zmscitizenapi/cancellation.feature (example with multiple scenarios)
- Created features/zmsapi/error-handling.feature (error scenario examples)
- Created features/zmsapi/data-driven-example.feature (data-driven testing patterns)
- Created features/MIGRATION_GUIDE.md with:
  * Before/after conversion examples
  * Common step definition patterns
  * Best practices and migration checklist

Key Features:
- Dual profile support: standalone (CI) and ataf (local development)
- Automatic Flyway database migrations before test execution
- Comprehensive step definitions for common API operations
- Example feature files demonstrating various Cucumber patterns
- Migration guide for converting additional JUnit tests

The zmsapiautomation module remains unchanged and continues to run in
GitHub Actions CI. The zmsautomation module is ready for local testing
with the ATAF profile (requires Artifactory access). Both modules will
coexist until Phase 7 when ATAF becomes open source and CI can be updated.
@ThomasAFink ThomasAFink changed the title fix(ZMSKVR-686): Add zmsautomation module with ATAF integration refactor(ZMSKVR-686): Add zmsautomation module with ATAF integration Feb 5, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Establishes a comprehensive Cucumber-based test automation framework for ZMS APIs and UI components. Introduces Maven POM configuration with ATAF integration, Flyway database migrations, test infrastructure (runners, hooks, step definitions), numerous Cucumber feature files for API and UI testing, Docker/environment support for browser automation, and reorganizes package structures. Updates CI/CD workflow to placeholder status and modifies hostname references from keycloak.local to keycloak.

Changes

Cohort / File(s) Summary
Maven and Java Configuration
zmsautomation/pom.xml, zmsautomation/src/test/java/config/TestConfig.java, zmsautomation/src/test/java/zms/ataf/data/TestData.java, zmsautomation/src/test/java/zms/ataf/runner/...
New Maven POM with Java 21, REST-assured, Cucumber, and ATAF dependencies. TestConfig centralizes API endpoint configuration. TestData and runners (TestRunner, ApiTestRunner, UiTestRunner) initialize test framework. Surefire integration with profile-based test execution.
Database Migrations
zmsautomation/src/main/resources/db/migration/V*__...sql
Flyway migrations including baseline placeholder, customer/location updates, test data for organisations, authorities, standorts, email addresses, opening hours, holidays, slot sequences, captcha disabling, Keycloak SSO user creation, and related test data seeding.
API Test Infrastructure
zmsautomation/src/test/java/zms/ataf/api/base/Base*.java, zmsautomation/src/test/java/zms/ataf/api/dto/..., zmsautomation/src/test/java/zms/ataf/api/helpers/..., zmsautomation/src/test/java/zms/ataf/api/steps/...
Base test classes, DTOs for API responses (ZMS API, Citizen API), builder helpers for test data, and Cucumber step definitions (CommonApiSteps, ZmsApiSteps, CitizenApiSteps) for REST-assured interactions and assertions.
UI Test Infrastructure
zmsautomation/src/test/java/zms/ataf/ui/base/..., zmsautomation/src/test/java/zms/ataf/ui/pages/admin/..., zmsautomation/src/test/java/zms/ataf/ui/pages/buergeransicht/..., zmsautomation/src/test/java/zms/ataf/ui/pages/mailinator/..., zmsautomation/src/test/java/zms/ataf/ui/pages/statistics/..., zmsautomation/src/test/java/zms/ataf/ui/steps/...
Comprehensive page objects for admin area, citizen booking frontend (Bürgeransicht), Mailinator integration, statistics pages, and Cucumber step definitions (AdminSteps, BuergeransichtSteps, MailinatorSteps, StatisticsSteps) driving UI interactions with Selenium WebDriver. Includes admin workviews (counter processing, queue, display call system) and service utilities.
Cucumber Feature Files
zmsautomation/src/test/resources/features/api/zmsapi/..., zmsautomation/src/test/resources/features/api/zmscitizenapi/..., zmsautomation/src/test/resources/features/ui/zmsadmin/..., zmsautomation/src/test/resources/features/ui/buergeransicht/..., zmsautomation/src/test/resources/features/ui/zmsstatistic/...
BDD feature files for API testing (status, availability, scopes, appointments, booking, cancellation) and UI testing (admin workflows, citizen booking, statistics, legacy frontend scenarios). Mostly disabled legacy tests marked with @ignore for legacy Bürgeransicht frontend.
Test Configuration and Helpers
zmsautomation/src/test/resources/cucumber.properties, zmsautomation/src/test/resources/log4j2-test.properties, zmsautomation/src/test/resources/testautomation.properties, zmsautomation/src/test/java/zms/ataf/helpers/RandomNameHelper.java, zmsautomation/src/test/java/zms/ataf/hooks/DatabaseHook.java
Cucumber glue configuration, Log4j2 logging setup, test properties (API URIs, credentials, logging), and utility classes for database initialization and random test data generation.
Test Execution Script
zmsautomation/zmsautomation-test
Comprehensive Bash script orchestrating end-to-end testing: database backup/restore, Flyway migrations, cache clearing, time/Keycloak mocking, health checks, Maven test execution with tag filtering, artifact collection, and cleanup.
Docker and Environment Setup
.ddev/web-build/Dockerfile, .ddev/config.yaml, .devcontainer/Containerfile, .devcontainer/devcontainer.json, .devcontainer/docker-compose.yaml, .ddev/.env.template, .devcontainer/.env.template
Added Firefox ESR, xvfb, geckodriver, and X11 utilities for browser automation. Keycloak hostname changed from keycloak.local to keycloak across all configs. Environment variables for SSO and display settings. Post-start hooks for Firefox symlink and Xvfb initialization.
Keycloak Configuration
.resources/keycloak/migration/02_add-clients.yml, .resources/keycloak/migration/07_add-ataf-user.yml, .resources/keycloak/migration/keycloak-changelog.yml, zmsadmin/keycloak.json
Added Keycloak redirect URIs for port 443 (container UI test support), new ATAF test user creation, migration changelog update, and updated client config to use keycloak hostname.
CI/CD and Documentation
.github/workflows/zmsautomation-tests.yaml, README.md, zmsautomation/README.md
GitHub workflow simplified to placeholder job. Root README updated with keycloak hostname changes and API testing section documentation. New comprehensive project README for zmsautomation with prerequisites, structure, test execution, and environment variable guidance.
Package Reorganization
zmsautomation/src/test/java/zms/ataf/api/base/Base*.java, zmsautomation/src/test/java/zms/ataf/api/dto/..., zmsautomation/src/test/java/zms/ataf/api/helpers/...
Package declarations updated from flat structure (e.g., package base;, package dto.zmsapi;) to fully qualified namespaces (e.g., package zms.ataf.api.base;, package zms.ataf.api.dto.zmsapi;) with corresponding import updates.
Miscellaneous Configuration
.gitignore, .resources/zms.sql, zmsdldb/src/Zmsdldb/Transformers/Munich.php, zmsapi/cron/cronjob.minutly, zmsdb/src/Zmsdb/Helper/UnconfirmedAppointmentDeleteByCron.php
Added ignore entries (eappointment-buergeransicht, zmsautomation/lib, zmsentities/schemavalidation.log). Removed preferences table data from SQL dump. Added service ID to DONT_SHOW_SERVICE_ON_START_PAGE. Added exit statement to cron job. Scope list initialization in PHP helper.

Sequence Diagram(s)

The changes do not warrant a sequence diagram. While the framework is comprehensive, it consists primarily of configuration, test infrastructure setup, and page object/step definition additions that are not characterized by complex multi-component sequential flows. The test execution is straightforward data-driven behavior rather than intricate interactions requiring visualization.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰 A framework sprouts with Cucumber's delight,
🌿 ATAF blossoms in the testing light,
🎭 Pages dance, steps align, migrations flow,
🏗️ Browser and API in harmony grow,
✨ From feature files to fixtures bright,
📊 Test automation takes its flight!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-zmskvr-1124-refactor-to-use-cucumber-and-ataf-compatibility-and-setup-first-test-case

Add test execution script for running ATAF/Cucumber tests on city laptop.
The script handles complete test environment setup including database
migrations, health checks, and test execution with the ATAF profile.

Changes:
- Created zmsautomation-test script adapted from zmsapiautomation-test
- Updated paths to use zmsautomation module and new Flyway location
- Configured to run tests with -Pataf Maven profile
- Added Cucumber report cleanup and display
- Updated README.md with script usage instructions

The script provides the same functionality as the original but:
- Uses zmsautomation/src/main/resources/db/migration for Flyway
- Runs tests with -Pataf profile for ATAF/Cucumber execution
- Supports Cucumber-specific Maven arguments (tags, features)
- Displays Cucumber report locations (JSON and HTML)

Usage:
  ./zmsautomation/zmsautomation-test
  ./zmsautomation/zmsautomation-test -Dcucumber.filter.tags="@smoke"
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In `@zmsautomation/pom.xml`:
- Around line 37-40: Update the AssertJ dependency to mitigate CVE-2026-24400 by
setting the assertj-core artifact (artifactId: assertj-core, groupId:
org.assertj) to version 3.27.7 or later; change the ${assertj.version} Maven
property value to "3.27.7" (or a newer safe release) so the pom uses the patched
AssertJ release and rerun the build to ensure tests pass.

In
`@zmsautomation/src/main/resources/db/migration/V3__organisation_test_data.sql`:
- Around line 4-10: The INSERT seed contains trailing spaces in the
Organisationsname values for organisation rows with OrganisationsID 10 and 11 in
the organisation table; remove those trailing whitespace characters from the
'Organisationsname' entries (e.g., "Referat für Stadtplanung und Bauordnung "
and "Münchner Stadtentwässerung (MSE) ") so they are stored without trailing
spaces and won't break string comparisons or tests.

In `@zmsautomation/src/test/java/dto/zmscitizenapi/Office.java`:
- Around line 13-54: The Office DTO currently only supports object JSON for
Office and address, but the schema allows ["array","object","null"]; update
Office to accept both by adding `@JsonDeserialize`(using =
OfficeDeserializer.class) on the Office class (and similarly
`@JsonDeserialize`(using = AddressDeserializer.class) on the address field or
Address class) and implement OfficeDeserializer to detect if incoming token is
START_ARRAY then read positional elements into the appropriate fields (map
indices observed in production — e.g., index 7 -> id, index 8 -> name — and
populate showAlternativeLocations, parentId, etc., as applicable), otherwise
delegate to default object deserialization; ensure AddressDeserializer also
handles array/object/null forms. Alternatively, if you prefer an index-capturing
approach, implement `@JsonAnySetter` in Office to accept unmapped array indices
into a List<Object> and then translate those index positions into the typed
fields.

In `@zmsautomation/src/test/java/zms/ataf/hooks/DatabaseHook.java`:
- Around line 20-24: The Flyway cleanup call is unguarded and will DROP ALL
OBJECTS because cleanDisabled(false) then calling flyway.clean(); modify the
DatabaseHook logic to prevent accidental runs against non-test DBs by checking
the target database name or an explicit opt-in before calling flyway.clean():
obtain the MYSQL_DATABASE (or equivalent) value used by Flyway and verify it
matches an allowed test pattern (e.g., contains "test" or a configured regex) OR
require an environment flag like ALLOW_FLYWAY_CLEAN=true to be set; only if the
check passes invoke flyway.clean(), otherwise log a clear warning and skip clean
while still running flyway.migrate(); update the code around the
cleanDisabled(false)/flyway.clean() calls in DatabaseHook.java to implement this
guard.

In `@zmsautomation/src/test/java/zms/ataf/steps/CitizenApiSteps.java`:
- Around line 149-165: The as(Response, TypeReference) method is duplicated and
creates a new ObjectMapper per call; extract it into a shared utility (e.g.,
ResponseHelper) or move to BaseRestSteps, create a single reusable private
static final ObjectMapper (MAPPER) and implement a static generic as(Response,
TypeReference) that uses MAPPER, plus a helper to build the same truncated error
message (used in the RuntimeException) so both CitizenApiSteps.as and
ZmsApiSteps.as call the shared method instead of duplicating logic.

In `@zmsautomation/src/test/java/zms/ataf/steps/ZmsApiSteps.java`:
- Around line 35-40: The step method theZmsApiIsAvailableWithLogging currently
ignores its loggingEnabled parameter; either remove this variant or use the
parameter to configure logging before calling theZmsApiIsAvailable(). To
implement, convert the loggingEnabled string to a boolean and apply it to the
test logging configuration (e.g., call a setter like
TestConfig.setLoggingEnabled(boolean) or otherwise configure the logger via
TestConfig), then call theZmsApiIsAvailable(); alternatively delete the
theZmsApiIsAvailableWithLogging method and any Gherkin that uses it to avoid the
unused parameter.

In `@zmsautomation/src/test/resources/features/zmsapi/availability.feature`:
- Around line 16-18: The scenario currently only checks the status code; add an
explicit step after "Then the response status code should be 200" to assert the
returned slots are empty (e.g., add "And the response contains an empty list of
available slots" or "Then the available slots list is empty") so the expectation
in the title "returns empty" is validated; locate the scenario with "When I
request available appointments for scope 99999" and update it to include this
new assertion step.

In `@zmsautomation/zmsapiautomation-test`:
- Around line 118-124: The generated DROP list in drop_sql can be truncated
because GROUP_CONCAT has a small default max length; fix by increasing
GROUP_CONCAT max length before building `@tables` — e.g. add "SET SESSION
group_concat_max_len = 1000000;" (or other sufficiently large value) at the
start of the drop_sql string so the SELECT using GROUP_CONCAT(...) to populate
`@tables` won’t truncate, keeping the rest of the logic (PREPARE/EXECUTE stmt, SET
FOREIGN_KEY_CHECKS) unchanged.
🧹 Nitpick comments (23)
zmsautomation/src/test/resources/log4j2-test.properties (1)

5-5: Consider removing %L from the pattern to avoid expensive location lookups.

Line numbers force location calculation and can slow logging even in tests; keep it only if you really need it.

♻️ Suggested tweak
-appender.console.layout.pattern=[%T] %d{HH:mm:ss} %-5p %c{1}:%L - %m%n
+appender.console.layout.pattern=[%T] %d{HH:mm:ss} %-5p %c{1} - %m%n
zmsautomation/src/test/resources/features/zmscitizenapi/offices-and-services.feature (1)

1-13: Add a scenario to validate unpublished access control.
Right now you only cover the happy path. Consider adding a scenario to ensure unpublished offices/services are filtered or denied so the contract is protected.

💡 Suggested scenario addition
+  Scenario: GET /offices-and-services/ hides unpublished entries
+    Given unpublished offices and services exist
+    When I request the offices and services endpoint
+    Then the response status code should be 200
+    And the response should not include unpublished offices or services

Based on learnings, the endpoints /offices, /services, /services-by-office, and /offices-by-service should implement the same unpublished access control as /offices-and-services to prevent internal appointments from being booked through these endpoints.

zmsautomation/src/main/resources/db/migration/V4__behoerde_test_data.sql (1)

9-43: Minor whitespace inconsistencies in test data.

Some entries have leading or trailing whitespace that could cause unexpected test behavior:

  • Line 9: 'Servicestelle Kita-Beiträge ' has trailing space
  • Line 9: 'Landsberger Straße 486 81241 München ' has trailing space
  • Line 19: ' Landsberger Str. 486...' has leading space
  • Line 39: ' PLAN-HAIV-13' has leading space

If tests compare these strings exactly, they may fail unexpectedly.

🔧 Proposed fix to trim whitespace
-(9, 3, 1, 'Führerscheinstelle - Sonderschalter', '', '', 0),
-(11, 5, 1, 'Servicestelle Kita-Beiträge ', 'Landsberger Straße 486 81241 München ', '', 0),
+(9, 3, 1, 'Führerscheinstelle - Sonderschalter', '', '', 0),
+(11, 5, 1, 'Servicestelle Kita-Beiträge', 'Landsberger Straße 486 81241 München', '', 0),
-(36, 3, 1, 'Standesamt München-Pasing', ' Landsberger Str. 486, 81241 München', 'Frau Binder', 0),
+(36, 3, 1, 'Standesamt München-Pasing', 'Landsberger Str. 486, 81241 München', 'Frau Binder', 0),
-(94, 10, 1, ' PLAN-HAIV-13', '', '', 0),
+(94, 10, 1, 'PLAN-HAIV-13', '', '', 0),
zmsautomation/src/test/java/zms/ataf/data/TestData.java (1)

22-24: Consider environment variable support for DEV URLs.

The LOCAL environment correctly uses environment variables with sensible defaults. For consistency and flexibility, the DEV environment could follow the same pattern, allowing configuration without code changes.

♻️ Proposed refactor for configurable DEV URLs
         // City DEV environment (optional)
-        DEV.addSystem("ZMS-API", "https://zms-dev.muenchen.de/terminvereinbarung/api/2");
-        DEV.addSystem("ZMS-Citizen-API", "https://zms-dev.muenchen.de/terminvereinbarung/api/citizen");
+        String devBaseUri = System.getenv().getOrDefault("DEV_BASE_URI", 
+            "https://zms-dev.muenchen.de/terminvereinbarung/api/2");
+        String devCitizenUri = System.getenv().getOrDefault("DEV_CITIZEN_API_BASE_URI",
+            "https://zms-dev.muenchen.de/terminvereinbarung/api/citizen");
+        
+        DEV.addSystem("ZMS-API", devBaseUri);
+        DEV.addSystem("ZMS-Citizen-API", devCitizenUri);
zmsautomation/src/test/java/zms/ataf/hooks/DatabaseHook.java (1)

9-13: Add logging for database operations visibility.

Adding log statements before clean/migrate operations would help with debugging test failures and provide visibility into which database is being modified.

📝 Proposed logging addition
 package zms.ataf.hooks;
 
 import org.testng.annotations.BeforeSuite;
 import org.flywaydb.core.Flyway;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class DatabaseHook {
+    private static final Logger LOG = LoggerFactory.getLogger(DatabaseHook.class);
     
     `@BeforeSuite`
     public static void setupDatabase() {
+        String host = System.getenv().getOrDefault("MYSQL_HOST", "db");
+        String port = System.getenv().getOrDefault("MYSQL_PORT", "3306");
+        String database = System.getenv().getOrDefault("MYSQL_DATABASE", "zmsbo");
+        
         String dbUrl = String.format("jdbc:mysql://%s:%s/%s",
-            System.getenv().getOrDefault("MYSQL_HOST", "db"),
-            System.getenv().getOrDefault("MYSQL_PORT", "3306"),
-            System.getenv().getOrDefault("MYSQL_DATABASE", "zmsbo"));
+            host, port, database);
+        
+        LOG.info("Setting up test database: {}", dbUrl);
zmsautomation/pom.xml (1)

144-159: Scope Flyway/MySQL dependencies to tests in the ATAF profile.
These appear only used from src/test/java, so leaving them compile-scope bloats the artifact and can leak transitive deps. Prefer <scope>test</scope>.

Proposed change
         <dependency>
           <groupId>org.flywaydb</groupId>
           <artifactId>flyway-core</artifactId>
           <version>${flyway.version}</version>
+          <scope>test</scope>
         </dependency>
         <dependency>
           <groupId>org.flywaydb</groupId>
           <artifactId>flyway-mysql</artifactId>
           <version>${flyway.version}</version>
+          <scope>test</scope>
         </dependency>
         <!-- MySQL connector -->
         <dependency>
           <groupId>com.mysql</groupId>
           <artifactId>mysql-connector-j</artifactId>
           <version>8.2.0</version>
+          <scope>test</scope>
         </dependency>
zmsautomation/src/test/java/base/BaseTest.java (1)

54-61: Wire the configured timeout into RestAssured to avoid hung tests.
TestConfig.getRequestTimeout() exists but isn’t applied; network hangs can stall CI. Consider configuring connection/socket timeouts in RestAssured’s HTTP client config.

Proposed change
 import io.restassured.config.ObjectMapperConfig;
 import io.restassured.config.RestAssuredConfig;
+import io.restassured.config.HttpClientConfig;
@@
-        RestAssuredConfig config = RestAssuredConfig.config()
-            .objectMapperConfig(new ObjectMapperConfig(ObjectMapperType.JACKSON_2));
+        int timeoutMs = TestConfig.getRequestTimeout();
+        RestAssuredConfig config = RestAssuredConfig.config()
+            .httpClient(HttpClientConfig.httpClientConfig()
+                .setParam("http.connection.timeout", timeoutMs)
+                .setParam("http.socket.timeout", timeoutMs))
+            .objectMapperConfig(new ObjectMapperConfig(ObjectMapperType.JACKSON_2));
@@
-        // Note: Timeout configuration is available via TestConfig.getRequestTimeout()
-        // but REST-assured 5.x timeout API may need to be configured differently
-        // For now, using default REST-assured timeouts
zmsautomation/zmsapiautomation-test (1)

64-66: Make the target module/migration path configurable (currently hard-coded to zmsapiautomation).

This script lives under zmsautomation but still points to zmsapiautomation for Flyway and Maven. If the intent is to drive the new ATAF module, this will run the legacy module/migrations instead. Consider parameterizing the module and Flyway path (or relocating/renaming the script if it’s meant for the legacy module) to avoid accidental mis-runs.

💡 Example: parameterize module & Flyway path
- FLYWAY_DIR="$PROJECT_ROOT/zmsapiautomation/flyway"
+ ZMS_TEST_MODULE="${ZMS_TEST_MODULE:-zmsapiautomation}"
+ FLYWAY_DIR="${FLYWAY_DIR:-$PROJECT_ROOT/$ZMS_TEST_MODULE/flyway}"
- rm -rf "$PROJECT_ROOT/zmsapiautomation/target/surefire-reports" 2>/dev/null || true
+ rm -rf "$PROJECT_ROOT/$ZMS_TEST_MODULE/target/surefire-reports" 2>/dev/null || true
- cd "$PROJECT_ROOT/zmsapiautomation"
+ cd "$PROJECT_ROOT/$ZMS_TEST_MODULE"

Also applies to: 217-222

zmsautomation/src/test/resources/features/zmsapi/scopes.feature (1)

10-28: Avoid duplicate coverage for the same scope IDs.

The single-scope scenario overlaps with the outline examples (141/99999). Consider keeping one form to keep the suite fast and reduce redundancy.

zmsautomation/src/test/java/helpers/zmsapi/StatusResponseBuilder.java (1)

22-27: Make the default generated value deterministic for tests.

Using Instant.now() ties the builder to wall-clock time; any tests that assert generated can become flaky. Consider injecting a Clock (defaulting to Clock.systemUTC()) so callers can supply a fixed time when needed.

♻️ Suggested refactor
 import java.time.Instant;
+import java.time.Clock;
 import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
@@
 public class StatusResponseBuilder {
+    private final Clock clock;
@@
-    public StatusResponseBuilder() {
-        // Set default values
-        this.generated = DateTimeFormatter.ISO_OFFSET_DATE_TIME
-            .format(Instant.now().atOffset(ZoneOffset.UTC));
-        this.server = "test-server";
-    }
+    public StatusResponseBuilder() {
+        this(Clock.systemUTC());
+    }
+
+    public StatusResponseBuilder(Clock clock) {
+        this.clock = clock;
+        this.generated = DateTimeFormatter.ISO_OFFSET_DATE_TIME
+            .format(Instant.now(clock).atOffset(ZoneOffset.UTC));
+        this.server = "test-server";
+    }

As per coding guidelines, keep configurable data at high levels.

zmsautomation/src/test/resources/features/zmsapi/appointments.feature (1)

16-20: Consider removing the TODO block from the feature file.

Feature files are easier to maintain when they only contain executable scenarios. Consider tracking these planned scenarios in an issue/backlog (or a dedicated @wip feature) instead of inline TODO comments.

✂️ Suggested cleanup
-  # TODO: Add more appointment scenarios as needed
-  # - Create appointment
-  # - Get appointment details
-  # - Update appointment
-  # - Cancel appointment

As per coding guidelines, comments should explain intent and avoid obvious noise.

zmsautomation/src/test/java/dto/zmscitizenapi/collections/OfficesAndServicesResponse.java (1)

19-26: Consider initializing lists to empty collections.

The List fields are uninitialized, which could result in null values if the JSON response omits these fields. While @JsonIgnoreProperties(ignoreUnknown = true) handles unknown fields, missing fields will remain null.

♻️ Defensive initialization
     `@JsonProperty`("offices")
-    private List<Office> offices;
+    private List<Office> offices = new ArrayList<>();

     `@JsonProperty`("services")
-    private List<Service> services;
+    private List<Service> services = new ArrayList<>();

     `@JsonProperty`("relations")
-    private List<OfficeServiceRelation> relations;
+    private List<OfficeServiceRelation> relations = new ArrayList<>();
zmsautomation/src/test/java/zmscitizenapi/OfficesAndServicesEndpointTest.java (1)

29-39: Catching generic Exception may mask real failures.

The broad catch (Exception e) will silently swallow unexpected errors (e.g., network issues, malformed JSON) and attempt the fallback path. Consider catching a more specific exception type.

♻️ Catch specific Jackson deserialization exceptions
         try {
             officesAndServices = response.as(OfficesAndServicesResponse.class);
-        } catch (Exception e) {
+        } catch (com.fasterxml.jackson.databind.JsonMappingException e) {
             // Fallback to wrapped response if unwrapped fails
             ApiResponse<OfficesAndServicesResponse> apiResponse = as(response,
                 new com.fasterxml.jackson.core.type.TypeReference<ApiResponse<OfficesAndServicesResponse>>() {});
zmsautomation/src/test/java/helpers/zmscitizenapi/OfficesAndServicesResponseBuilder.java (1)

59-65: Consider defensive copying in build() to prevent aliasing issues.

The builder assigns its internal lists directly to the response object. If the builder is reused or the lists are modified after build(), both the builder and the response will be affected.

♻️ Return new list copies
     public OfficesAndServicesResponse build() {
         OfficesAndServicesResponse response = new OfficesAndServicesResponse();
-        response.setOffices(offices);
-        response.setServices(services);
-        response.setRelations(relations);
+        response.setOffices(new ArrayList<>(offices));
+        response.setServices(new ArrayList<>(services));
+        response.setRelations(new ArrayList<>(relations));
         return response;
     }
zmsautomation/src/test/java/zms/ataf/steps/CitizenApiSteps.java (6)

1-16: Imports look reasonable; minor cleanup opportunity.

The static import on line 10 (org.hamcrest.Matchers.*) is only partially used (just notNullValue() and empty() in assertions), while AssertJ assertions are used elsewhere with fully qualified names. Consider choosing one assertion style consistently.


42-48: Extract repeated baseUri resolution to a helper method.

The pattern baseUri != null ? baseUri : TestConfig.getCitizenApiBaseUri() is repeated across multiple methods. Consider extracting to a private helper method for consistency and reduced duplication.

♻️ Proposed helper method
+    private String getEffectiveBaseUri() {
+        return baseUri != null ? baseUri : TestConfig.getCitizenApiBaseUri();
+    }
+
     `@When`("I request the offices and services endpoint")
     public void iRequestTheOfficesAndServicesEndpoint() {
         response = given()
-            .baseUri(baseUri != null ? baseUri : TestConfig.getCitizenApiBaseUri())
+            .baseUri(getEffectiveBaseUri())
         .when()
             .get("/offices-and-services/");
     }

50-60: Placeholder booking implementation will fail tests.

The empty JSON body "{}" will likely cause validation errors or unexpected behavior. The TODO indicates this needs implementation based on actual booking API structure.

Would you like me to help generate the booking request structure based on the DTOs available in the project, or open an issue to track this implementation?


67-87: Consider using static import for AssertJ assertions.

The fully qualified org.assertj.core.api.Assertions.assertThat() calls are verbose. A static import would improve readability while the fallback deserialization pattern is well-designed.

♻️ Add static import

Add to imports:

import static org.assertj.core.api.Assertions.assertThat;

Then simplify assertions:

-        org.assertj.core.api.Assertions.assertThat(officesAndServices).isNotNull();
+        assertThat(officesAndServices).isNotNull();

97-114: State management needed for confirmation numbers.

These placeholder methods set only baseUri but don't store confirmation number state. When implemented, you'll need instance variables to hold the confirmation numbers used in subsequent steps.

♻️ Add state variable for confirmation number
     private Response response;
     private String baseUri;
+    private String confirmationNumber;
     
     `@Given`("I have a valid appointment confirmation number")
     public void iHaveAValidAppointmentConfirmationNumber() {
-        // TODO: This would typically set up test data or retrieve a valid confirmation number
         baseUri = TestConfig.getCitizenApiBaseUri();
+        // TODO: Set confirmationNumber = <retrieve or create valid appointment>
     }

128-147: Assertions are too weak to verify actual behavior.

The assertions body(not(empty())) only verify the response has content, not that it contains the expected cancellation state or error details. This doesn't align with test quality guidelines (readable, one assert per test for specific behavior).

♻️ Proposed stronger assertions
     `@Then`("the appointment should be cancelled")
     public void theAppointmentShouldBeCancelled() {
-        response.then()
-            .body(not(empty()));
-        // TODO: Add specific assertions for cancellation confirmation
+        response.then()
+            .body("data.status", equalTo("cancelled"));
+        // Adjust based on actual API response structure
     }
     
     `@Then`("the response should indicate the appointment was not found")
     public void theResponseShouldIndicateAppointmentNotFound() {
-        response.then()
-            .body(not(empty()));
-        // TODO: Add specific assertions for not found error
+        response.then()
+            .statusCode(404)
+            .body("meta.error", equalTo(true));
     }
zmsautomation/src/test/java/zms/ataf/steps/ZmsApiSteps.java (3)

1-17: Unused import on line 8.

io.restassured.RestAssured on line 8 is imported but not used directly — the static import on line 10 (static io.restassured.RestAssured.*) provides the needed methods.

♻️ Remove unused import
 import io.restassured.response.Response;
-import io.restassured.RestAssured;

107-132: Consider adding GET and POST for completeness.

The generic iSendARequest method handles DELETE, PUT, and PATCH but omits GET and POST, which are handled by other specific methods. For a truly generic method, consider supporting all HTTP methods for maximum reusability in feature files.

♻️ Add missing HTTP methods
     switch (method.toUpperCase()) {
+        case "GET":
+            response = given()
+                .baseUri(baseUri)
+            .when()
+                .get(endpoint);
+            break;
+        case "POST":
+            response = given()
+                .baseUri(baseUri)
+                .contentType("application/json")
+            .when()
+                .post(endpoint);
+            break;
         case "DELETE":

152-162: Assertions don't verify actual error structure.

Both theResponseShouldContainAnErrorMessage() and theResponseShouldContainValidationErrors() have identical implementations that only check the body is not empty. Consider asserting on actual error fields (e.g., meta.error, errors array).

♻️ Proposed stronger assertions
     `@Then`("the response should contain an error message")
     public void theResponseShouldContainAnErrorMessage() {
-        response.then()
-            .body(not(empty()));
+        response.then()
+            .body("meta.error", equalTo(true))
+            .body("meta.message", notNullValue());
     }
     
     `@Then`("the response should contain validation errors")
     public void theResponseShouldContainValidationErrors() {
-        response.then()
-            .body(not(empty()));
+        response.then()
+            .body("meta.error", equalTo(true))
+            .body("errors", not(empty()));
     }

Comment on lines +37 to +40
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
<scope>test</scope>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

GHSA-rqfh-9r24-8c9r AssertJ XXE vulnerability

💡 Result:

GHSA-rqfh-9r24-8c9r (alias CVE-2026-24400) is an XML External Entity (XXE) issue in AssertJ Core (org.assertj:assertj-core) caused by insecure XML parser configuration in org.assertj.core.util.xml.XmlStringPrettyFormatter (it builds a DocumentBuilderFactory without disabling DTDs/external entities). This can be reached when parsing untrusted XML via either isXmlEqualTo(CharSequence) or xmlPrettyFormat(String). If reachable, it can enable arbitrary file read, SSRF, and DoS (Billion Laughs). [1][3]

  • Affected versions: >= 1.4.0 up to and including 3.27.6 (GitHub lists this as 1.4.0–1.7.1, 2.0.0–2.9.1, 3.0.0–3.27.6). [1]
  • Fixed version: 3.27.7. [1][2]
  • Mitigation (preferred order): replace isXmlEqualTo(CharSequence) with XMLUnit (deprecated since AssertJ 3.18.0), or upgrade to 3.27.7, or avoid using these APIs with untrusted XML. [1]

Sources: GitHub Security Advisory [1], AssertJ release notes for 3.27.7 [2], NVD CVE record [3].


🌐 Web query:

AssertJ GHSA-rqfh-9r24-8c9r 3.27.6 patched version

💡 Result:

For GHSA-rqfh-9r24-8c9r (CVE-2026-24400) in org.assertj:assertj-core, version 3.27.6 is vulnerable; the patched (fixed) version is 3.27.7 (upgrade to ≥ 3.27.7). [1] [2]

Sources: OSV advisory lists Fixed: 3.27.7 and affected versions up to 3.27.6. [1] [2]


Upgrade AssertJ to 3.27.7 to fix XXE vulnerability GHSA-rqfh-9r24-8c9r.
CVE-2026-24400 affects AssertJ Core 3.27.6 and earlier, exposing tests that parse untrusted XML via isXmlEqualTo() or xmlPrettyFormat() to XXE attacks (arbitrary file read, SSRF, DoS). Upgrade to AssertJ 3.27.7 or later, or replace XML assertions with XMLUnit.

🤖 Prompt for AI Agents
In `@zmsautomation/pom.xml` around lines 37 - 40, Update the AssertJ dependency to
mitigate CVE-2026-24400 by setting the assertj-core artifact (artifactId:
assertj-core, groupId: org.assertj) to version 3.27.7 or later; change the
${assertj.version} Maven property value to "3.27.7" (or a newer safe release) so
the pom uses the patched AssertJ release and rerun the build to ensure tests
pass.

Comment on lines +4 to +10
INSERT IGNORE INTO `organisation` (`OrganisationsID`, `InfoBezirkID`, `KundenID`, `Organisationsname`, `Anschrift`, `kioskpasswortschutz`) VALUES
(3, 14, 1, 'Kreisverwaltungsreferat', '', 1),
(5, 14, 1, 'Sozialreferat', '', 0),
(7, 14, 1, 'Referat für Bildung und Sport', '', 0),
(10, 14, 1, 'Referat für Stadtplanung und Bauordnung ', 'Blumenstraße 28b', 0),
(11, 14, 1, 'Münchner Stadtentwässerung (MSE) ', 'Friedenstraße 40', 0),
(14, 14, 1, 'Personal- und Organisationsreferat', 'Rosenheimer Straße 118, 81669 München', 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Trim trailing spaces in seeded organisation names.
Line 8–9 include trailing spaces in Organisationsname, which can break string comparisons and fixtures.

🩹 Suggested fix
-(10, 14, 1, 'Referat für Stadtplanung und Bauordnung ', 'Blumenstraße 28b', 0),
-(11, 14, 1, 'Münchner Stadtentwässerung (MSE) ', 'Friedenstraße 40', 0),
+(10, 14, 1, 'Referat für Stadtplanung und Bauordnung', 'Blumenstraße 28b', 0),
+(11, 14, 1, 'Münchner Stadtentwässerung (MSE)', 'Friedenstraße 40', 0),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
INSERT IGNORE INTO `organisation` (`OrganisationsID`, `InfoBezirkID`, `KundenID`, `Organisationsname`, `Anschrift`, `kioskpasswortschutz`) VALUES
(3, 14, 1, 'Kreisverwaltungsreferat', '', 1),
(5, 14, 1, 'Sozialreferat', '', 0),
(7, 14, 1, 'Referat für Bildung und Sport', '', 0),
(10, 14, 1, 'Referat für Stadtplanung und Bauordnung ', 'Blumenstraße 28b', 0),
(11, 14, 1, 'Münchner Stadtentwässerung (MSE) ', 'Friedenstraße 40', 0),
(14, 14, 1, 'Personal- und Organisationsreferat', 'Rosenheimer Straße 118, 81669 München', 0);
INSERT IGNORE INTO `organisation` (`OrganisationsID`, `InfoBezirkID`, `KundenID`, `Organisationsname`, `Anschrift`, `kioskpasswortschutz`) VALUES
(3, 14, 1, 'Kreisverwaltungsreferat', '', 1),
(5, 14, 1, 'Sozialreferat', '', 0),
(7, 14, 1, 'Referat für Bildung und Sport', '', 0),
(10, 14, 1, 'Referat für Stadtplanung und Bauordnung', 'Blumenstraße 28b', 0),
(11, 14, 1, 'Münchner Stadtentwässerung (MSE)', 'Friedenstraße 40', 0),
(14, 14, 1, 'Personal- und Organisationsreferat', 'Rosenheimer Straße 118, 81669 München', 0);
🤖 Prompt for AI Agents
In `@zmsautomation/src/main/resources/db/migration/V3__organisation_test_data.sql`
around lines 4 - 10, The INSERT seed contains trailing spaces in the
Organisationsname values for organisation rows with OrganisationsID 10 and 11 in
the organisation table; remove those trailing whitespace characters from the
'Organisationsname' entries (e.g., "Referat für Stadtplanung und Bauordnung "
and "Münchner Stadtentwässerung (MSE) ") so they are stored without trailing
spaces and won't break string comparisons or tests.

Comment on lines +13 to +54
@JsonProperty("id")
private Integer id;

@JsonProperty("name")
private String name;

@JsonProperty("showAlternativeLocations")
private Boolean showAlternativeLocations;

@JsonProperty("address")
private Address address;

@JsonProperty("displayNameAlternatives")
private List<String> displayNameAlternatives;

@JsonProperty("organization")
private String organization;

@JsonProperty("organizationUnit")
private String organizationUnit;

@JsonProperty("slotTimeInMinutes")
private Integer slotTimeInMinutes;

@JsonProperty("version")
private Double version;

@JsonProperty("geo")
private GeoCoordinates geo;

@JsonProperty("parentId")
private Integer parentId;

@JsonProperty("scope")
private Object scope; // Complex nested structure - using Object for now

@JsonProperty("disabledByServices")
private List<Integer> disabledByServices;

@JsonProperty("slotsPerAppointment")
private String slotsPerAppointment;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Search for Office-related schema files, test fixtures, or API documentation
find . -type f \( -name "*.json" -o -name "*office*" -o -name "*schema*" \) | grep -iE "(office|schema|fixture)" | head -20

Repository: it-at-m/eappointment

Length of output: 1412


🏁 Script executed:

# Look for Office model usage, deserialization, or any custom deserializers
rg -l "Office" --type java | head -10

Repository: it-at-m/eappointment

Length of output: 847


🏁 Script executed:

# Check for any test data or example payloads
rg -i "office.*response\|api.*office\|office.*payload" -A 5 | head -50

Repository: it-at-m/eappointment

Length of output: 46


🏁 Script executed:

# Examine if Address is defined as array or object elsewhere
rg "class Address" -A 10 --type java

Repository: it-at-m/eappointment

Length of output: 1923


🏁 Script executed:

# Look for JsonDeserialize annotations on Office or Address
rg "@JsonDeserialize|@JsonAnySetter" --type java -B 2 -A 2 | head -30

Repository: it-at-m/eappointment

Length of output: 46


🏁 Script executed:

# Examine the OfficesAndServicesResponseBuilder to see what structure it builds
cat zmsautomation/src/test/java/helpers/zmscitizenapi/OfficesAndServicesResponseBuilder.java

Repository: it-at-m/eappointment

Length of output: 2184


🏁 Script executed:

# Look at test cases in OfficesAndServicesEndpointTest to see actual responses
head -100 zmsautomation/src/test/java/zmscitizenapi/OfficesAndServicesEndpointTest.java

Repository: it-at-m/eappointment

Length of output: 1916


🏁 Script executed:

# Search for actual Office object instantiation or mock data
rg "new Office\|\.withId\|\.withName\|\.withAddress" -A 5 --type java | head -60

Repository: it-at-m/eappointment

Length of output: 46


🏁 Script executed:

# Look for any JSON fixtures containing "office" data
fd -e json | xargs rg -l -i "office" | head -10

Repository: it-at-m/eappointment

Length of output: 718


🏁 Script executed:

cat zmsentities/schema/citizenapi/office.json

Repository: it-at-m/eappointment

Length of output: 4371


Address property and Office structure must support array deserialization formats per schema.

The schema at zmsentities/schema/citizenapi/office.json defines both Office and address as potentially ["array", "object", "null"] types. The current model only handles object representation and will fail deserialization when the API returns array-based structures. Learnings confirm this occurs in production (ID and name fields at indices 7 and 8 respectively). Implement a custom @JsonDeserialize or use Jackson's array/collection handling to support both formats, or add @JsonAnySetter to capture unmapped array indices if only object format is intended.

🤖 Prompt for AI Agents
In `@zmsautomation/src/test/java/dto/zmscitizenapi/Office.java` around lines 13 -
54, The Office DTO currently only supports object JSON for Office and address,
but the schema allows ["array","object","null"]; update Office to accept both by
adding `@JsonDeserialize`(using = OfficeDeserializer.class) on the Office class
(and similarly `@JsonDeserialize`(using = AddressDeserializer.class) on the
address field or Address class) and implement OfficeDeserializer to detect if
incoming token is START_ARRAY then read positional elements into the appropriate
fields (map indices observed in production — e.g., index 7 -> id, index 8 ->
name — and populate showAlternativeLocations, parentId, etc., as applicable),
otherwise delegate to default object deserialization; ensure AddressDeserializer
also handles array/object/null forms. Alternatively, if you prefer an
index-capturing approach, implement `@JsonAnySetter` in Office to accept unmapped
array indices into a List<Object> and then translate those index positions into
the typed fields.

Comment on lines +20 to +24
.cleanDisabled(false)
.load();

flyway.clean();
flyway.migrate();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "DatabaseHook.java" | head -5

Repository: it-at-m/eappointment

Length of output: 126


🏁 Script executed:

cat -n zmsautomation/src/test/java/zms/ataf/hooks/DatabaseHook.java

Repository: it-at-m/eappointment

Length of output: 1093


🌐 Web query:

Flyway 10 cleanDisabled configuration API behavior

💡 Result:

In Flyway 10, cleanDisabled is a safety switch that controls whether clean is allowed to run:

  • Default is true (i.e., clean is disabled unless you explicitly enable it). (documentation.red-gate.com)
  • If cleanDisabled is true, attempting to run flyway clean (CLI) or flyway.clean() (API) will be blocked (Flyway errors rather than wiping the schema). (documentation.red-gate.com)
  • To allow cleaning, you must set it to false, e.g. via the Java API:
    Flyway.configure().cleanDisabled(false).load() (documentation.red-gate.com)
    or on the CLI for a one-off run: flyway -cleanDisabled=false clean. (documentation.red-gate.com)

Citations:


Add a safeguard to prevent accidental cleaning of non-test databases.

With cleanDisabled(false), the flyway.clean() call will DROP ALL OBJECTS in the target database. While appropriate for test hooks, this could be catastrophic if the MYSQL_DATABASE environment variable is misconfigured to point to a shared or production database.

Add a check to ensure the database name matches expected test patterns, or require explicit opt-in via an environment variable:

Safeguard example
+        String database = System.getenv().getOrDefault("MYSQL_DATABASE", "zmsbo");
+        
+        // Safety check: only allow clean on known test databases
+        if (!database.equals("zmsbo") && !database.contains("test")) {
+            String allowClean = System.getenv().getOrDefault("FLYWAY_ALLOW_CLEAN", "false");
+            if (!"true".equals(allowClean)) {
+                throw new IllegalStateException(
+                    "Refusing to clean database '" + database + "'. Set FLYWAY_ALLOW_CLEAN=true to override.");
+            }
+        }
+
         flyway.clean();
         flyway.migrate();
🤖 Prompt for AI Agents
In `@zmsautomation/src/test/java/zms/ataf/hooks/DatabaseHook.java` around lines 20
- 24, The Flyway cleanup call is unguarded and will DROP ALL OBJECTS because
cleanDisabled(false) then calling flyway.clean(); modify the DatabaseHook logic
to prevent accidental runs against non-test DBs by checking the target database
name or an explicit opt-in before calling flyway.clean(): obtain the
MYSQL_DATABASE (or equivalent) value used by Flyway and verify it matches an
allowed test pattern (e.g., contains "test" or a configured regex) OR require an
environment flag like ALLOW_FLYWAY_CLEAN=true to be set; only if the check
passes invoke flyway.clean(), otherwise log a clear warning and skip clean while
still running flyway.migrate(); update the code around the
cleanDisabled(false)/flyway.clean() calls in DatabaseHook.java to implement this
guard.

Comment on lines +149 to +165
private <T> T as(Response response, TypeReference<T> typeReference) {
try {
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
String responseBody = response.asString();
return mapper.readValue(responseBody, typeReference);
} catch (Exception e) {
String responseBody = response.asString();
String errorMsg = String.format(
"Failed to deserialize response. Status: %d, Response body (first 500 chars): %s",
response.getStatusCode(),
responseBody != null && responseBody.length() > 500
? responseBody.substring(0, 500) + "..."
: responseBody
);
throw new RuntimeException(errorMsg, e);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Extract shared as() helper to avoid duplication.

This method is duplicated in ZmsApiSteps.java. Additionally, creating a new ObjectMapper on each call is inefficient. Consider:

  1. Moving to a shared utility class (or BaseRestSteps if appropriate)
  2. Reusing a single ObjectMapper instance
♻️ Proposed shared utility

Create a shared helper class:

public final class ResponseHelper {
    private static final ObjectMapper MAPPER = new ObjectMapper();
    
    private ResponseHelper() {}
    
    public static <T> T as(Response response, TypeReference<T> typeReference) {
        try {
            return MAPPER.readValue(response.asString(), typeReference);
        } catch (Exception e) {
            throw new RuntimeException(buildErrorMessage(response), e);
        }
    }
    
    private static String buildErrorMessage(Response response) {
        String body = response.asString();
        return String.format(
            "Failed to deserialize response. Status: %d, Body (first 500 chars): %s",
            response.getStatusCode(),
            body != null && body.length() > 500 ? body.substring(0, 500) + "..." : body
        );
    }
}
🤖 Prompt for AI Agents
In `@zmsautomation/src/test/java/zms/ataf/steps/CitizenApiSteps.java` around lines
149 - 165, The as(Response, TypeReference) method is duplicated and creates a
new ObjectMapper per call; extract it into a shared utility (e.g.,
ResponseHelper) or move to BaseRestSteps, create a single reusable private
static final ObjectMapper (MAPPER) and implement a static generic as(Response,
TypeReference) that uses MAPPER, plus a helper to build the same truncated error
message (used in the RuntimeException) so both CitizenApiSteps.as and
ZmsApiSteps.as call the shared method instead of duplicating logic.

Comment on lines +35 to +40
@Given("the ZMS API is available with logging {string}")
public void theZmsApiIsAvailableWithLogging(String loggingEnabled) {
baseUri = TestConfig.getBaseUri();
// Logging configuration would be set here if needed
theZmsApiIsAvailable();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Unused parameter loggingEnabled.

The loggingEnabled parameter is ignored. Either implement the logging configuration using TestConfig.isLoggingEnabled() or remove this variant step.

🐛 Proposed implementation
     `@Given`("the ZMS API is available with logging {string}")
     public void theZmsApiIsAvailableWithLogging(String loggingEnabled) {
         baseUri = TestConfig.getBaseUri();
-        // Logging configuration would be set here if needed
-        theZmsApiIsAvailable();
+        if (Boolean.parseBoolean(loggingEnabled)) {
+            RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
+        }
+        given()
+            .baseUri(baseUri)
+        .when()
+            .get("/status/")
+        .then()
+            .statusCode(200);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Given("the ZMS API is available with logging {string}")
public void theZmsApiIsAvailableWithLogging(String loggingEnabled) {
baseUri = TestConfig.getBaseUri();
// Logging configuration would be set here if needed
theZmsApiIsAvailable();
}
`@Given`("the ZMS API is available with logging {string}")
public void theZmsApiIsAvailableWithLogging(String loggingEnabled) {
baseUri = TestConfig.getBaseUri();
if (Boolean.parseBoolean(loggingEnabled)) {
RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
}
theZmsApiIsAvailable();
}
🤖 Prompt for AI Agents
In `@zmsautomation/src/test/java/zms/ataf/steps/ZmsApiSteps.java` around lines 35
- 40, The step method theZmsApiIsAvailableWithLogging currently ignores its
loggingEnabled parameter; either remove this variant or use the parameter to
configure logging before calling theZmsApiIsAvailable(). To implement, convert
the loggingEnabled string to a boolean and apply it to the test logging
configuration (e.g., call a setter like TestConfig.setLoggingEnabled(boolean) or
otherwise configure the logger via TestConfig), then call
theZmsApiIsAvailable(); alternatively delete the theZmsApiIsAvailableWithLogging
method and any Gherkin that uses it to avoid the unused parameter.

Comment on lines +16 to +18
Scenario: Get availability for invalid scope returns empty
When I request available appointments for scope 99999
Then the response status code should be 200
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add an explicit assertion for the “returns empty” expectation.

The scenario title says the result is empty, but it only checks the status code. Add a step that asserts no slots are returned.

✅ Suggested step
 Scenario: Get availability for invalid scope returns empty
   When I request available appointments for scope 99999
   Then the response status code should be 200
+  And the response should contain no available slots
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Scenario: Get availability for invalid scope returns empty
When I request available appointments for scope 99999
Then the response status code should be 200
Scenario: Get availability for invalid scope returns empty
When I request available appointments for scope 99999
Then the response status code should be 200
And the response should contain no available slots
🤖 Prompt for AI Agents
In `@zmsautomation/src/test/resources/features/zmsapi/availability.feature` around
lines 16 - 18, The scenario currently only checks the status code; add an
explicit step after "Then the response status code should be 200" to assert the
returned slots are empty (e.g., add "And the response contains an empty list of
available slots" or "Then the available slots list is empty") so the expectation
in the title "returns empty" is validated; locate the scenario with "When I
request available appointments for scope 99999" and update it to include this
new assertion step.

Comment on lines +118 to +124
local drop_sql="SET FOREIGN_KEY_CHECKS = 0;
SET @tables = (SELECT GROUP_CONCAT(CONCAT('\`', table_name, '\`')) FROM information_schema.tables WHERE table_schema = '$DB_NAME' AND table_type = 'BASE TABLE');
SET @sql = IF(@tables IS NULL OR @tables = '', 'DO 0', CONCAT('DROP TABLE IF EXISTS ', @tables));
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET FOREIGN_KEY_CHECKS = 1;"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Prevent truncated DROP list for larger schemas.

GROUP_CONCAT defaults to a small max length; on larger schemas the generated DROP list can truncate and leave tables behind.

🔧 Suggested fix
-  local drop_sql="SET FOREIGN_KEY_CHECKS = 0;
+  local drop_sql="SET FOREIGN_KEY_CHECKS = 0;
+SET SESSION group_concat_max_len = 102400;
 SET `@tables` = (SELECT GROUP_CONCAT(CONCAT('\`', table_name, '\`')) FROM information_schema.tables WHERE table_schema = '$DB_NAME' AND table_type = 'BASE TABLE');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
local drop_sql="SET FOREIGN_KEY_CHECKS = 0;
SET @tables = (SELECT GROUP_CONCAT(CONCAT('\`', table_name, '\`')) FROM information_schema.tables WHERE table_schema = '$DB_NAME' AND table_type = 'BASE TABLE');
SET @sql = IF(@tables IS NULL OR @tables = '', 'DO 0', CONCAT('DROP TABLE IF EXISTS ', @tables));
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET FOREIGN_KEY_CHECKS = 1;"
local drop_sql="SET FOREIGN_KEY_CHECKS = 0;
SET SESSION group_concat_max_len = 102400;
SET `@tables` = (SELECT GROUP_CONCAT(CONCAT('\`', table_name, '\`')) FROM information_schema.tables WHERE table_schema = '$DB_NAME' AND table_type = 'BASE TABLE');
SET `@sql` = IF(`@tables` IS NULL OR `@tables` = '', 'DO 0', CONCAT('DROP TABLE IF EXISTS ', `@tables`));
PREPARE stmt FROM `@sql`;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET FOREIGN_KEY_CHECKS = 1;"
🤖 Prompt for AI Agents
In `@zmsautomation/zmsapiautomation-test` around lines 118 - 124, The generated
DROP list in drop_sql can be truncated because GROUP_CONCAT has a small default
max length; fix by increasing GROUP_CONCAT max length before building `@tables` —
e.g. add "SET SESSION group_concat_max_len = 1000000;" (or other sufficiently
large value) at the start of the drop_sql string so the SELECT using
GROUP_CONCAT(...) to populate `@tables` won’t truncate, keeping the rest of the
logic (PREPARE/EXECUTE stmt, SET FOREIGN_KEY_CHECKS) unchanged.

@ThomasAFink ThomasAFink changed the title refactor(ZMSKVR-686): Add zmsautomation module with ATAF integration refactor(ZMSKVR-686): zmsapiautomation to zmsautomation module with ATAF integration Feb 5, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@zmsautomation/zmsautomation-test`:
- Around line 89-95: The shell functions exec_sql and exec_sql_root currently
pass credentials via --password which exposes them in the process list; change
them to set MYSQL_PWD in the environment for the invoked command (or use a MySQL
option file) instead of passing --password, e.g. export/inline MYSQL_PWD for the
mysql invocation in exec_sql and exec_sql_root, and apply the same change to the
mysqldump invocation so neither mysql nor mysqldump receives passwords as
command-line arguments.
🧹 Nitpick comments (10)
zmsautomation/zmsautomation-test (6)

2-2: Inconsistent indentation on set directive.

The set -euo pipefail line has extra leading whitespace inconsistent with the rest of the script.

🧹 Proposed fix
 #!/usr/bin/env bash
-  set -euo pipefail
+set -euo pipefail

59-59: Port extraction may be redundant or incorrect.

If MYSQL_PORT is already a plain port number (e.g., 3306), the ##*: expansion returns the original value—which works. However, the comment suggests it handles tcp://host:port, but MYSQL_HOST defaults to db, not extracted from this variable. Document the expected format or simplify if only numeric ports are expected.


169-169: Hardcoded version threshold is fragile.

Deleting from flyway_schema_history WHERE version > 1 assumes the base schema is version 1. If the baseline version changes, this logic breaks silently. Consider using a named constant or Flyway's clean command for test resets.


200-211: Inconsistent indentation in health check blocks.

The if/else blocks have extra leading whitespace that differs from the rest of the script.

🧹 Proposed fix
 info "Pinging citizenapi: $CITIZEN_API_BASE_URI/offices-and-services/"
 if curl -sf --max-time 10 "$CITIZEN_API_BASE_URI/offices-and-services/" >/dev/null; then
-    info "✓ citizenapi offices-and-services responding"
-  else
-    warn "✗ citizenapi offices-and-services NOT responding"
-  fi
+  info "✓ citizenapi offices-and-services responding"
+else
+  warn "✗ citizenapi offices-and-services NOT responding"
+fi

 info "Pinging zmsapi: $BASE_URI/status/"
 if curl -sf --max-time 10 "$BASE_URI/status/" >/dev/null; then
-    info "✓ zmsapi status responding"
-  else
-    warn "✗ zmsapi status NOT responding"
-  fi
+  info "✓ zmsapi status responding"
+else
+  warn "✗ zmsapi status NOT responding"
+fi

286-286: Inconsistent indentation on final exit.

Extra leading whitespace before exit "$TEST_EXIT".

🧹 Proposed fix
-  exit "$TEST_EXIT"
+exit "$TEST_EXIT"

129-131: Consider order of trap handlers.

The ERR trap attempts to restore the DB on failure, but the EXIT trap always runs clear_caches and cleanup_data_folder. If restore_db fails, the EXIT trap still runs cleanup—which is correct. However, if tests fail (non-zero exit), the ERR trap triggers restore before the explicit restore in section 12. This could cause a double-restore. Consider either removing the ERR trap restore (relying on the explicit restore path) or skipping section 12 restore on failure.

zmsautomation/pom.xml (3)

145-154: ATAF dependencies missing <scope>test</scope>.

The de.muenchen.ataf.java:core and de.muenchen.ataf.java:rest dependencies default to compile scope. If these are only needed for tests, add <scope>test</scope> to prevent them from being included in production artifacts.

🔧 Proposed fix
         <dependency>
           <groupId>de.muenchen.ataf.java</groupId>
           <artifactId>core</artifactId>
           <version>0.2</version>
+          <scope>test</scope>
         </dependency>
         <dependency>
           <groupId>de.muenchen.ataf.java</groupId>
           <artifactId>rest</artifactId>
           <version>0.2</version>
+          <scope>test</scope>
         </dependency>

176-191: Consider test scope for Flyway and MySQL connector.

If Flyway and the MySQL connector are only used for test database setup, they should have <scope>test</scope>. This prevents them from being bundled with production artifacts.

🔧 Proposed fix
         <dependency>
           <groupId>org.flywaydb</groupId>
           <artifactId>flyway-core</artifactId>
           <version>${flyway.version}</version>
+          <scope>test</scope>
         </dependency>
         <dependency>
           <groupId>org.flywaydb</groupId>
           <artifactId>flyway-mysql</artifactId>
           <version>${flyway.version}</version>
+          <scope>test</scope>
         </dependency>
         <!-- MySQL connector -->
         <dependency>
           <groupId>com.mysql</groupId>
           <artifactId>mysql-connector-j</artifactId>
           <version>8.2.0</version>
+          <scope>test</scope>
         </dependency>

112-121: updatePolicy=always may slow builds.

Using <updatePolicy>always</updatePolicy> for both releases and snapshots forces Maven to check for updates on every build. Consider daily or interval:X for releases to reduce network overhead during local development.

zmsautomation/README.md (1)

97-105: Clarify default value source.

The defaults mentioned here differ from those in the script. For example, BASE_URI default here shows port 8080, but the script (line 67) defaults to port 80. Consider aligning the documentation with the actual script defaults or clarifying that these are example values rather than actual defaults.

Comment on lines +89 to +95
exec_sql() {
mysql --ssl=0 -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" --password="$DB_PASSWORD" "$DB_NAME" "$@"
}

exec_sql_root() {
mysql --ssl=0 -h "$DB_HOST" -P "$DB_PORT" -u root --password="${MYSQL_ROOT_PASSWORD:-root}" "$@"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Passwords visible in process list via command-line arguments.

Using --password="$DB_PASSWORD" exposes credentials in ps output. Consider using environment variables or a MySQL option file instead.

🔐 Recommended alternative using MYSQL_PWD
 exec_sql() {
-  mysql --ssl=0 -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" --password="$DB_PASSWORD" "$DB_NAME" "$@"
+  MYSQL_PWD="$DB_PASSWORD" mysql --ssl=0 -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" "$DB_NAME" "$@"
 }

 exec_sql_root() {
-  mysql --ssl=0 -h "$DB_HOST" -P "$DB_PORT" -u root --password="${MYSQL_ROOT_PASSWORD:-root}" "$@"
+  MYSQL_PWD="${MYSQL_ROOT_PASSWORD:-root}" mysql --ssl=0 -h "$DB_HOST" -P "$DB_PORT" -u root "$@"
 }

Apply the same pattern to mysqldump on line 100.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
exec_sql() {
mysql --ssl=0 -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" --password="$DB_PASSWORD" "$DB_NAME" "$@"
}
exec_sql_root() {
mysql --ssl=0 -h "$DB_HOST" -P "$DB_PORT" -u root --password="${MYSQL_ROOT_PASSWORD:-root}" "$@"
}
exec_sql() {
MYSQL_PWD="$DB_PASSWORD" mysql --ssl=0 -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" "$DB_NAME" "$@"
}
exec_sql_root() {
MYSQL_PWD="${MYSQL_ROOT_PASSWORD:-root}" mysql --ssl=0 -h "$DB_HOST" -P "$DB_PORT" -u root "$@"
}
🤖 Prompt for AI Agents
In `@zmsautomation/zmsautomation-test` around lines 89 - 95, The shell functions
exec_sql and exec_sql_root currently pass credentials via --password which
exposes them in the process list; change them to set MYSQL_PWD in the
environment for the invoked command (or use a MySQL option file) instead of
passing --password, e.g. export/inline MYSQL_PWD for the mysql invocation in
exec_sql and exec_sql_root, and apply the same change to the mysqldump
invocation so neither mysql nor mysqldump receives passwords as command-line
arguments.

@ThomasAFink ThomasAFink force-pushed the feat-zmskvr-1124-refactor-to-use-cucumber-and-ataf-compatibility-and-setup-first-test-case branch from e2ff22d to bd35b2c Compare February 5, 2026 08:18
…d-ataf-compatibility-and-setup-first-test-case
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In
`@zmsdb/migrations/91770274084-restore-waiting-statistics-from-archive-table-since-2026-02-05.sql`:
- Around line 274-298: The average wait-time assignments (e.g.,
w.echte_zeit_ab_00_spontan ... w.echte_zeit_ab_23_spontan) currently use
COALESCE(p.<col>, 0) which masks “no data” as zero; change these to preserve
NULL instead of forcing 0—either assign p.echte_zeit_ab_XX_spontan directly or
use COALESCE(p.<col>, NULL) (effectively remove the COALESCE fallback), and
apply the same change for the corresponding termin and wegezeit average columns
so downstream consumers can distinguish “no data” (NULL) from an actual 0
average.
- Around line 1-16: The migration deletes rows via "DELETE FROM wartenrstatistik
WHERE datum >= `@start_date`;" without transactional safety and has a
filename/date mismatch with "@start_date := '2026-02-04'"; wrap the entire
migration (temp table creation/use/INSERTs and the DELETE/REBUILD steps that
reference tmp_ba_raw, tmp_ba_agg, tmp_pivot and `@start_date`) in an explicit
transaction (START TRANSACTION; ... COMMIT; with error handling to ROLLBACK on
failure) so the DELETE is atomic with the rebuild, and either update the
filename to reflect '2026-02-04' or change `@start_date` to match the filename
'2026-02-05' to remove the mismatch.
- Around line 24-34: The temporary table creation uses STR_TO_DATE on a TIME
column and a MariaDB-specific engine and also converts zero wait times to NULL;
update the SELECT for tmp_ba_raw to use HOUR(Timestamp) directly (referencing
the Timestamp column and HOUR(...) expression), change or remove the ENGINE=Aria
clause to match the codebase (use ENGINE=InnoDB or omit engine specification),
and preserve wartezeit zeros instead of converting them to NULL (adjust the CASE
that sets waited_minutes so it keeps 0 values or otherwise distinguishes missing
data from a 0-minute wait while still rounding as needed).

…igration

Remove the defaultabholerstandort column from V5__standort_test_data.sql
migration file. This column has been removed from the database schema
and should not be included in the test data migration.

Changes:
- Removed defaultabholerstandort from INSERT column list
- Removed corresponding values from all VALUES rows
- Removed accidentally copied zmsapiautomation-test file
…test_data.sql

- Removed ausgabeschaltername from column list in INSERT statement
- Removed corresponding values from all INSERT rows
- Column was causing schema mismatch errors
…test_data.sql

Removed both defaultabholerstandort and ausgabeschaltername columns
from the INSERT statement column list and their corresponding values
from all data rows to match the updated schema.
…ndort_test_data.sql

Removed wartezeitveroeffentlichen column along with defaultabholerstandort
and ausgabeschaltername from the INSERT statement column list and their
corresponding values from all data rows to match the updated schema.
Removed qtv_url column along with defaultabholerstandort, ausgabeschaltername,
and wartezeitveroeffentlichen from the INSERT statement column list and their
corresponding values from all data rows to match the updated schema.
… last row

Fixed trailing semicolon issue in the last INSERT row that was causing
SQL syntax error during Flyway migration.
- Updated workflow from zmsapiautomation to zmsautomation
- Updated Flyway path to zmsautomation/src/main/resources/db/migration
- Updated Maven cache key and working directories
- Added placeholder job that always succeeds until ATAF is open source
- Full test job is commented out with TODO to enable when ATAF is open source
…d of zmsapiautomation

- Updated API Testing section to reference zmsautomation
- Updated script names and paths from zmsapiautomation to zmsautomation
- Updated description to mention Cucumber and ATAF framework
- Added information about running Cucumber feature files
…rence zmsautomation

- Updated Dockerfile comment to reference zmsautomation instead of zmsapiautomation
- Updated zmsautomation README migration notes
The zmsapiautomation module has been replaced by zmsautomation which uses
Cucumber and ATAF. The old module is no longer needed.
Removed outdated note about zmsapiautomation module since it has been
deleted and replaced by zmsautomation.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@zmsautomation/README.md`:
- Around line 97-98: The two default URIs in zmsautomation/README.md are
inconsistent with the main README: update the `BASE_URI` and
`CITIZEN_API_BASE_URI` entries to match the main README by removing the explicit
:8080 port so they read `http://localhost/terminvereinbarung/api/2` and
`http://localhost/terminvereinbarung/api/citizen` respectively; ensure the
variable names `BASE_URI` and `CITIZEN_API_BASE_URI` are the ones edited so both
READMEs present the same defaults.
🧹 Nitpick comments (1)
.github/workflows/zmsautomation-tests.yaml (1)

25-310: Consider extracting commented workflow to a separate file.

The 250+ lines of commented code, while documented with a TODO, could be better managed. Per Clean Code guidelines, commented-out code should generally be removed. Consider one of these alternatives:

  1. Move to a separate file like .github/workflows/zmsautomation-tests.yaml.ataf-template that can be swapped in when ATAF is open-sourced
  2. Document in the module README with the full workflow YAML for future reference
  3. Create a GitHub issue with the workflow content to track the ATAF integration work

This keeps the active workflow clean while preserving the implementation for future use.

Comment on lines +97 to +98
- `BASE_URI` - Base URI for ZMS API (default: `http://localhost:8080/terminvereinbarung/api/2`)
- `CITIZEN_API_BASE_URI` - Base URI for Citizen API (default: `http://localhost:8080/terminvereinbarung/api/citizen`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent default port in documentation.

The default URIs show port 8080 here, but the main README.md (lines 471-472) shows no port (implying port 80). Consider aligning these defaults for consistency.

Lines 97-98:

- `BASE_URI` - Base URI for ZMS API (default: `http://localhost:8080/terminvereinbarung/api/2`)
- `CITIZEN_API_BASE_URI` - Base URI for Citizen API (default: `http://localhost:8080/terminvereinbarung/api/citizen`)

Main README defaults:

- `BASE_URI`: `http://localhost/terminvereinbarung/api/2`
- `CITIZEN_API_BASE_URI`: `http://localhost/terminvereinbarung/api/citizen`
📝 Proposed fix to align defaults
 ### API Endpoints
-- `BASE_URI` - Base URI for ZMS API (default: `http://localhost:8080/terminvereinbarung/api/2`)
-- `CITIZEN_API_BASE_URI` - Base URI for Citizen API (default: `http://localhost:8080/terminvereinbarung/api/citizen`)
+- `BASE_URI` - Base URI for ZMS API (default: `http://localhost/terminvereinbarung/api/2`)
+- `CITIZEN_API_BASE_URI` - Base URI for Citizen API (default: `http://localhost/terminvereinbarung/api/citizen`)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- `BASE_URI` - Base URI for ZMS API (default: `http://localhost:8080/terminvereinbarung/api/2`)
- `CITIZEN_API_BASE_URI` - Base URI for Citizen API (default: `http://localhost:8080/terminvereinbarung/api/citizen`)
- `BASE_URI` - Base URI for ZMS API (default: `http://localhost/terminvereinbarung/api/2`)
- `CITIZEN_API_BASE_URI` - Base URI for Citizen API (default: `http://localhost/terminvereinbarung/api/citizen`)
🤖 Prompt for AI Agents
In `@zmsautomation/README.md` around lines 97 - 98, The two default URIs in
zmsautomation/README.md are inconsistent with the main README: update the
`BASE_URI` and `CITIZEN_API_BASE_URI` entries to match the main README by
removing the explicit :8080 port so they read
`http://localhost/terminvereinbarung/api/2` and
`http://localhost/terminvereinbarung/api/citizen` respectively; ensure the
variable names `BASE_URI` and `CITIZEN_API_BASE_URI` are the ones edited so both
READMEs present the same defaults.

- Removed StatusEndpointTest.java (replaced by status.feature)
- Removed OfficesAndServicesEndpointTest.java (replaced by offices-and-services.feature)
- Updated pom.xml comments to reflect migration to Cucumber/ATAF
- Tests now run exclusively via Cucumber feature files with ATAF framework
- Use -Pataf profile to run tests (zmsautomation-test script does this automatically)
- Applied code formatting fixes
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@zmsautomation/pom.xml`:
- Around line 82-88: The Surefire configuration in the POM is inconsistent with
the comment: it includes the pattern **/*Test.java while the comment says "No
JUnit tests"; update the configuration to match intent by either removing the
<includes> entry for **/*Test.java or explicitly skipping those tests (e.g., add
<skipTests>true</skipTests> or add an
<excludes><exclude>**/*Test.java</exclude></excludes>) in the Surefire plugin
configuration, or alternatively update the comment to accurately describe why
the include is present; refer to the Surefire configuration block
(useSystemClassLoader, includes) to locate and change the settings.

In `@zmsautomation/src/test/java/zms/ataf/steps/CitizenApiSteps.java`:
- Around line 116-126: In iSubmitACancellationRequest, replace the hard-coded
confirmation placeholder with the real confirmation number captured from the
booking step (e.g., confirmationNumber or
bookingResponse.get("confirmationNumber")) and ensure it is present
(throw/failed assertion if null/empty) before sending the request; additionally
fetch/inspect the booked appointment's status (appointmentStatus) and
appointmentTime (appointmentTime) from bookingResponse (or by GET
/appointments/{confirmationNumber}) and assert appointmentStatus == "CONFIRMED"
and appointmentTime is after Instant.now(ZoneOffset.UTC) before issuing the
cancellation, then submit the DELETE using the confirmation number (either as
path param /appointments/{confirmationNumber} or in the request body) and assign
the result to response.
- Around line 34-40: The step method iHaveSelectedAValidServiceAndLocation in
class CitizenApiSteps is a placeholder that only sets baseUri which can mislead
tests; either mark it explicitly pending by throwing
io.cucumber.java.PendingException (e.g., throw new PendingException()) or
implement proper test-data setup by populating the service and location test
variables (e.g., set serviceId and locationId or call a test fixture/setup
helper) in addition to baseUri; apply the same change to the other placeholder
step methods in CitizenApiSteps so scenarios are either clearly pending or use
real test data.
- Around line 50-60: The step method iSubmitABookingRequestWithValidData
currently sends an empty JSON body; mark it pending instead until a proper
booking DTO/payload exists by replacing the HTTP call in that method with
throwing a Cucumber pending exception (e.g., throw new PendingException("Booking
request payload not implemented")); ensure you import the appropriate
PendingException class (io.cucumber.java.PendingException or
cucumber.api.PendingException) and keep a short TODO comment referencing
creation of the booking request DTO so future work can locate this spot.
🧹 Nitpick comments (10)
zmsautomation/src/test/java/zms/ataf/hooks/DatabaseHook.java (1)

10-13: Consider extracting default values to named constants.

The hardcoded defaults ("db", "3306", "zmsbo") are repeated inline. Extracting them to private static final constants improves readability and makes future changes easier.

Suggested refactor
 public class DatabaseHook {
+    private static final String DEFAULT_HOST = "db";
+    private static final String DEFAULT_PORT = "3306";
+    private static final String DEFAULT_DATABASE = "zmsbo";
+    private static final String DEFAULT_USER = "zmsbo";
+    private static final String DEFAULT_PASSWORD = "zmsbo";
     
     `@BeforeSuite`
     public static void setupDatabase() {
         String dbUrl = String.format("jdbc:mysql://%s:%s/%s",
-            System.getenv().getOrDefault("MYSQL_HOST", "db"),
-            System.getenv().getOrDefault("MYSQL_PORT", "3306"),
-            System.getenv().getOrDefault("MYSQL_DATABASE", "zmsbo"));
+            System.getenv().getOrDefault("MYSQL_HOST", DEFAULT_HOST),
+            System.getenv().getOrDefault("MYSQL_PORT", DEFAULT_PORT),
+            System.getenv().getOrDefault("MYSQL_DATABASE", DEFAULT_DATABASE));
         
         Flyway flyway = Flyway.configure()
             .dataSource(dbUrl, 
-                System.getenv().getOrDefault("MYSQL_USER", "zmsbo"),
-                System.getenv().getOrDefault("MYSQL_PASSWORD", "zmsbo"))
+                System.getenv().getOrDefault("MYSQL_USER", DEFAULT_USER),
+                System.getenv().getOrDefault("MYSQL_PASSWORD", DEFAULT_PASSWORD))

As per coding guidelines: "Replace magic numbers with named constants" and "Use searchable names."

zmsautomation/src/test/java/zms/ataf/steps/ZmsApiSteps.java (5)

43-44: Extract repeated baseUri resolution to a helper method.

The pattern baseUri != null ? baseUri : TestConfig.getBaseUri() is duplicated 7 times throughout the class. This violates the DRY principle.

♻️ Proposed refactor to extract helper method

Add a private helper method:

private String resolveBaseUri() {
    return baseUri != null ? baseUri : TestConfig.getBaseUri();
}

Then replace all occurrences, e.g., line 44:

     response = given()
-        .baseUri(baseUri != null ? baseUri : TestConfig.getBaseUri())
+        .baseUri(resolveBaseUri())
     .when()

Also applies to: 51-52, 85-86, 100-101, 107-108, 135-136, 143-144


151-161: Duplicate implementations with weak assertions.

theResponseShouldContainAnErrorMessage() and theResponseShouldContainValidationErrors() have identical implementations that only verify the body is not empty. This doesn't validate actual error structure or validation error content.

Consider asserting on specific error response fields (e.g., error code, message field, or validation error array).

♻️ Proposed improvement
 `@Then`("the response should contain an error message")
 public void theResponseShouldContainAnErrorMessage() {
     response.then()
-        .body(not(empty()));
+        .body("meta.error", equalTo(true))
+        .body("meta.message", notNullValue());
 }

 `@Then`("the response should contain validation errors")
 public void theResponseShouldContainValidationErrors() {
     response.then()
-        .body(not(empty()));
+        .body("errors", not(empty()));
 }

Adjust the JSON paths to match your actual API error response structure.


106-131: Consider adding GET and POST to iSendARequest for consistency.

The step handles DELETE, PUT, and PATCH but not GET or POST. While other dedicated steps exist for these methods, having an incomplete generic step may confuse feature authors. Either extend the switch to cover all methods or document the intended usage in a comment.

♻️ Proposed extension
     switch (method.toUpperCase()) {
+        case "GET":
+            response = given()
+                .baseUri(baseUri)
+            .when()
+                .get(endpoint);
+            break;
+        case "POST":
+            response = given()
+                .baseUri(baseUri)
+            .when()
+                .post(endpoint);
+            break;
         case "DELETE":

182-198: Instantiate ObjectMapper once as a class field.

Creating a new ObjectMapper on every as() invocation is unnecessary overhead. ObjectMapper is thread-safe for read operations after configuration and can be reused.

♻️ Proposed refactor
 public class ZmsApiSteps extends BaseRestSteps {
     
     private Response response;
     private String baseUri;
+    private static final com.fasterxml.jackson.databind.ObjectMapper OBJECT_MAPPER = 
+        new com.fasterxml.jackson.databind.ObjectMapper();
     
     // ... other methods ...
     
     private <T> T as(Response response, TypeReference<T> typeReference) {
         try {
-            com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
             String responseBody = response.asString();
-            return mapper.readValue(responseBody, typeReference);
+            return OBJECT_MAPPER.readValue(responseBody, typeReference);
         } catch (Exception e) {

63-75: Consider using static import for AssertJ assertions.

Fully qualified org.assertj.core.api.Assertions.assertThat(...) calls reduce readability. Add a static import to align with the existing Hamcrest static imports.

♻️ Proposed improvement

Add to imports:

import static org.assertj.core.api.Assertions.assertThat;

Then simplify the method:

-        org.assertj.core.api.Assertions.assertThat(apiResponse).isNotNull();
-        org.assertj.core.api.Assertions.assertThat(apiResponse.getMeta()).isNotNull();
-        org.assertj.core.api.Assertions.assertThat(apiResponse.getMeta().getError()).isFalse();
+        assertThat(apiResponse).isNotNull();
+        assertThat(apiResponse.getMeta()).isNotNull();
+        assertThat(apiResponse.getMeta().getError()).isFalse();
         
         StatusResponse statusData = apiResponse.getData();
-        org.assertj.core.api.Assertions.assertThat(statusData).isNotNull();
-        org.assertj.core.api.Assertions.assertThat(statusData.getVersion()).isNotNull();
+        assertThat(statusData).isNotNull();
+        assertThat(statusData.getVersion()).isNotNull();
zmsautomation/pom.xml (1)

113-161: Consider adding <scope>test</scope> to ATAF profile dependencies.

ATAF core/rest, Flyway, and MySQL connector dependencies lack explicit scope and default to compile. If these are only needed for test execution, adding <scope>test</scope> prevents them from being packaged into the final artifact.

♻️ Proposed scope additions
         <dependency>
           <groupId>de.muenchen.ataf.java</groupId>
           <artifactId>core</artifactId>
           <version>0.2</version>
+          <scope>test</scope>
         </dependency>
         <dependency>
           <groupId>de.muenchen.ataf.java</groupId>
           <artifactId>rest</artifactId>
           <version>0.2</version>
+          <scope>test</scope>
         </dependency>

Apply similarly to Flyway and MySQL connector if they're test-only.

zmsautomation/src/test/java/zms/ataf/steps/CitizenApiSteps.java (3)

67-87: Consider asserting non-empty collections, not just non-null.
Null checks allow empty lists to pass, which can hide regressions in test data or API behavior.

♻️ Suggested tightening
 org.assertj.core.api.Assertions.assertThat(officesAndServices).isNotNull();
-org.assertj.core.api.Assertions.assertThat(officesAndServices.getOffices()).isNotNull();
-org.assertj.core.api.Assertions.assertThat(officesAndServices.getServices()).isNotNull();
-org.assertj.core.api.Assertions.assertThat(officesAndServices.getRelations()).isNotNull();
+org.assertj.core.api.Assertions.assertThat(officesAndServices.getOffices()).isNotEmpty();
+org.assertj.core.api.Assertions.assertThat(officesAndServices.getServices()).isNotEmpty();
+org.assertj.core.api.Assertions.assertThat(officesAndServices.getRelations()).isNotEmpty();

89-95: Capture the confirmation number for later cancellation steps.
Right now it’s asserted but not stored, so subsequent steps can’t use it reliably.

🧩 Suggested change
 response.then()
     .body("confirmationNumber", notNullValue());
+String confirmationNumber = response.then().extract().path("confirmationNumber");
+org.assertj.core.api.Assertions.assertThat(confirmationNumber).isNotBlank();
+this.confirmationNumber = confirmationNumber;

You’ll also need a field near the other members:

private String confirmationNumber;

128-133: Add explicit cancellation assertions, not just “body not empty.”
A non-empty body can still represent an error; assert the expected success field or status to avoid false positives.

Comment on lines +34 to +40
@Given("I have selected a valid service and location")
public void iHaveSelectedAValidServiceAndLocation() {
// This step is a placeholder for test data setup
// In a real scenario, this would set up test data or select specific service/location
// For now, we'll assume the booking endpoint handles the validation
baseUri = TestConfig.getCitizenApiBaseUri();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find and examine the CitizenApiSteps.java file
fd "CitizenApiSteps.java" -x cat -n {}

Repository: it-at-m/eappointment

Length of output: 8306


Mark this placeholder step as pending (or implement data setup).
The step name promises a valid selection but only sets baseUri, which can make scenarios misleading and cause flaky failures later. Multiple similar placeholder steps exist in this class (lines 98–102, 104–108, 110–114).

Suggested change
 `@Given`("I have selected a valid service and location")
 public void iHaveSelectedAValidServiceAndLocation() {
     // This step is a placeholder for test data setup
     // In a real scenario, this would set up test data or select specific service/location
     // For now, we'll assume the booking endpoint handles the validation
     baseUri = TestConfig.getCitizenApiBaseUri();
+    throw new io.cucumber.java.PendingException("TODO: implement service/location setup");
 }
🤖 Prompt for AI Agents
In `@zmsautomation/src/test/java/zms/ataf/steps/CitizenApiSteps.java` around lines
34 - 40, The step method iHaveSelectedAValidServiceAndLocation in class
CitizenApiSteps is a placeholder that only sets baseUri which can mislead tests;
either mark it explicitly pending by throwing io.cucumber.java.PendingException
(e.g., throw new PendingException()) or implement proper test-data setup by
populating the service and location test variables (e.g., set serviceId and
locationId or call a test fixture/setup helper) in addition to baseUri; apply
the same change to the other placeholder step methods in CitizenApiSteps so
scenarios are either clearly pending or use real test data.

Comment on lines +50 to +60
@When("I submit a booking request with valid data")
public void iSubmitABookingRequestWithValidData() {
// TODO: Implement booking request step
// This will need to be implemented based on the actual booking API structure
response = given()
.baseUri(baseUri != null ? baseUri : TestConfig.getCitizenApiBaseUri())
.contentType("application/json")
.body("{}") // Placeholder - needs actual booking data
.when()
.post("/appointments/");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's locate and read the CitizenApiSteps.java file
fd "CitizenApiSteps.java" -type f

Repository: it-at-m/eappointment

Length of output: 234


🏁 Script executed:

#!/bin/bash
# Check the structure and content around lines 50-60
if [ -f "zmsautomation/src/test/java/zms/ataf/steps/CitizenApiSteps.java" ]; then
  wc -l "zmsautomation/src/test/java/zms/ataf/steps/CitizenApiSteps.java"
fi

Repository: it-at-m/eappointment

Length of output: 131


🏁 Script executed:

#!/bin/bash
# Read the specific section mentioned in the review
if [ -f "zmsautomation/src/test/java/zms/ataf/steps/CitizenApiSteps.java" ]; then
  sed -n '40,70p' "zmsautomation/src/test/java/zms/ataf/steps/CitizenApiSteps.java" | cat -n
fi

Repository: it-at-m/eappointment

Length of output: 1537


🏁 Script executed:

#!/bin/bash
# Search for TestData classes or booking/appointment related data structures
fd -e java | xargs rg -l "TestData|BookingData|AppointmentData|bookingRequest" --type java | head -20

Repository: it-at-m/eappointment

Length of output: 236


🏁 Script executed:

#!/bin/bash
# Look for TestConfig class and getCitizenApiBaseUri method
rg "getCitizenApiBaseUri|class TestConfig" --type java -A 3

Repository: it-at-m/eappointment

Length of output: 4277


🏁 Script executed:

#!/bin/bash
# Find other booking/appointment related step methods in CitizenApiSteps
if [ -f "zmsautomation/src/test/java/zms/ataf/steps/CitizenApiSteps.java" ]; then
  rg "@When|@Given|@Then" "zmsautomation/src/test/java/zms/ataf/steps/CitizenApiSteps.java" -A 2
fi

Repository: it-at-m/eappointment

Length of output: 2428


🏁 Script executed:

#!/bin/bash
# Read the TestData class to understand what test data is available
if [ -f "zmsautomation/src/test/java/zms/ataf/data/TestData.java" ]; then
  cat -n "zmsautomation/src/test/java/zms/ataf/data/TestData.java"
fi

Repository: it-at-m/eappointment

Length of output: 1366


🏁 Script executed:

#!/bin/bash
# Check TestDataBuilder for booking/appointment related data
if [ -f "zmsautomation/src/test/java/helpers/TestDataBuilder.java" ]; then
  cat -n "zmsautomation/src/test/java/helpers/TestDataBuilder.java"
fi

Repository: it-at-m/eappointment

Length of output: 1955


🏁 Script executed:

#!/bin/bash
# Look for any models/DTOs related to booking or appointment requests
fd -e java | xargs rg "class.*Booking|class.*Appointment|class.*Request" | grep -i "booking\|appointment\|request" | head -20

Repository: it-at-m/eappointment

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Check if there are any booking/appointment request models or DTOs anywhere in the codebase
rg "Booking.*Request|Appointment.*Request" --type java -l

Repository: it-at-m/eappointment

Length of output: 127


🏁 Script executed:

#!/bin/bash
# Look at the entire CitizenApiSteps.java to understand the pattern across all steps
cat -n "zmsautomation/src/test/java/zms/ataf/steps/CitizenApiSteps.java" | head -80

Repository: it-at-m/eappointment

Length of output: 3858


🏁 Script executed:

#!/bin/bash
# Check the helpers/zmscitizenapi directory for any existing booking/appointment builders
fd "." "zmsautomation/src/test/java/helpers/zmscitizenapi" -type f 2>/dev/null || echo "Directory may not exist or be empty"

Repository: it-at-m/eappointment

Length of output: 99


🏁 Script executed:

#!/bin/bash
# Look for any request DTOs in the dto package structure
fd -e java "dto" | xargs rg "class.*Request|interface.*Request" | grep -i booking -i appointment

Repository: it-at-m/eappointment

Length of output: 108


🏁 Script executed:

#!/bin/bash
# Check what DTOs exist in the dto package
fd -e java "dto" | head -30

Repository: it-at-m/eappointment

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Check the structure of dto directories
find . -path "*/dto/*" -name "*.java" 2>/dev/null | head -30

Repository: it-at-m/eappointment

Length of output: 470


🏁 Script executed:

#!/bin/bash
# Look for all files in dto packages
ls -la zmsautomation/src/test/java/dto/ 2>/dev/null || ls -la zmsautomation/src/*/dto/ 2>/dev/null || find . -type d -name "dto" 2>/dev/null

Repository: it-at-m/eappointment

Length of output: 344


🏁 Script executed:

#!/bin/bash
# Check the entire CitizenApiSteps file to see all the placeholder/TODO patterns
grep -n "TODO\|Placeholder\|needs\|placeholder" "zmsautomation/src/test/java/zms/ataf/steps/CitizenApiSteps.java"

Repository: it-at-m/eappointment

Length of output: 1012


🏁 Script executed:

#!/bin/bash
# Check the feature files to understand what the booking test scenario expects
fd "\.feature" | xargs rg "submit a booking request with valid data" -B 5 -A 5

Repository: it-at-m/eappointment

Length of output: 1229


Mark this step pending until booking request payload is implemented.

The step currently sends an empty JSON object {} instead of valid booking data. This causes the test to either fail unexpectedly or pass without validating the actual booking behavior. No request DTO exists in the codebase yet to support this implementation.

Until proper booking request data is created (or discovered), use a pending exception to signal the incomplete step:

Recommended change
 `@When`("I submit a booking request with valid data")
 public void iSubmitABookingRequestWithValidData() {
-    // TODO: Implement booking request step
-    // This will need to be implemented based on the actual booking API structure
-    response = given()
-        .baseUri(baseUri != null ? baseUri : TestConfig.getCitizenApiBaseUri())
-        .contentType("application/json")
-        .body("{}") // Placeholder - needs actual booking data
-    .when()
-        .post("/appointments/");
+    throw new io.cucumber.java.PendingException("TODO: implement valid booking request payload");
 }
🤖 Prompt for AI Agents
In `@zmsautomation/src/test/java/zms/ataf/steps/CitizenApiSteps.java` around lines
50 - 60, The step method iSubmitABookingRequestWithValidData currently sends an
empty JSON body; mark it pending instead until a proper booking DTO/payload
exists by replacing the HTTP call in that method with throwing a Cucumber
pending exception (e.g., throw new PendingException("Booking request payload not
implemented")); ensure you import the appropriate PendingException class
(io.cucumber.java.PendingException or cucumber.api.PendingException) and keep a
short TODO comment referencing creation of the booking request DTO so future
work can locate this spot.

Comment on lines +116 to +126
@When("I submit a cancellation request")
public void iSubmitACancellationRequest() {
// TODO: Implement cancellation request
// This will need to be implemented based on the actual cancellation API structure
response = given()
.baseUri(baseUri != null ? baseUri : TestConfig.getCitizenApiBaseUri())
.contentType("application/json")
.body("{\"confirmationNumber\": \"placeholder\"}") // Placeholder - needs actual confirmation number
.when()
.delete("/appointments/");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Cancellation request uses a hard-coded placeholder confirmation number.
This won’t exercise real cancellation logic. Use the confirmation number captured from the booking step and ensure it’s present before issuing the request.

🧩 Suggested change
 `@When`("I submit a cancellation request")
 public void iSubmitACancellationRequest() {
     // TODO: Implement cancellation request
     // This will need to be implemented based on the actual cancellation API structure
+    if (confirmationNumber == null || confirmationNumber.isBlank()) {
+        throw new IllegalStateException("confirmationNumber not set");
+    }
     response = given()
         .baseUri(baseUri != null ? baseUri : TestConfig.getCitizenApiBaseUri())
         .contentType("application/json")
-        .body("{\"confirmationNumber\": \"placeholder\"}") // Placeholder - needs actual confirmation number
+        .body(java.util.Map.of("confirmationNumber", confirmationNumber))
     .when()
         .delete("/appointments/");
 }

Based on learnings: When testing appointment cancellation in zmscitizenapi, ensure the process is in confirmed status and use UTC timezone for timestamp comparisons. The canBeCancelled() validation requires both a future timestamp and confirmed status.

🤖 Prompt for AI Agents
In `@zmsautomation/src/test/java/zms/ataf/steps/CitizenApiSteps.java` around lines
116 - 126, In iSubmitACancellationRequest, replace the hard-coded confirmation
placeholder with the real confirmation number captured from the booking step
(e.g., confirmationNumber or bookingResponse.get("confirmationNumber")) and
ensure it is present (throw/failed assertion if null/empty) before sending the
request; additionally fetch/inspect the booked appointment's status
(appointmentStatus) and appointmentTime (appointmentTime) from bookingResponse
(or by GET /appointments/{confirmationNumber}) and assert appointmentStatus ==
"CONFIRMED" and appointmentTime is after Instant.now(ZoneOffset.UTC) before
issuing the cancellation, then submit the DELETE using the confirmation number
(either as path param /appointments/{confirmationNumber} or in the request body)
and assign the result to response.

…form

- Replace cucumber-testng with cucumber-junit-platform-engine
- Remove TestNG dependency and testng.xml configuration
- Update TestRunner to use JUnit Platform @suite annotation
- Add junit-platform-suite-api dependency
- Update surefire plugin configuration for JUnit Platform
…tests

- Add junit-platform-suite runtime dependency (required for suite execution)
- Add @SelectClasspathResource annotation to explicitly select feature files
- This fixes the 'TestEngine with ID junit-jupiter failed to discover tests' error
…or suite discovery

- Add junit-platform-suite-engine dependency explicitly
- Configure surefire to exclude base test classes
- Set failIfNoTests=false to handle JUnit Jupiter discovery issues
- This attempts to fix the 'junit-jupiter failed to discover tests' error
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@zmsautomation/src/test/java/zms/ataf/runner/TestRunner.java`:
- Around line 12-16: The TestRunner class currently calls TestData.init() in a
static block and lacks Cucumber glue config; move TestData.init() into a
Cucumber global hook class (e.g., create zms.ataf.hooks.GlobalSetupHook with a
public static method annotated with io.cucumber.java.BeforeAll that calls
TestData.init()), remove the static initializer from TestRunner, and configure
the TestRunner to set the glue property by importing and using
GLUE_PROPERTY_NAME so Cucumber discovers hooks; also replace or adapt
TestNG-specific DatabaseHook annotations (e.g., `@BeforeSuite`) to
Cucumber-compatible hooks (io.cucumber.java.Before or BeforeAll) or ensure
TestNG compatibility as appropriate.
🧹 Nitpick comments (1)
zmsautomation/pom.xml (1)

43-45: Centralize remaining hard‑coded dependency versions in properties.

Versions for jackson-databind (Line 45), ATAF core/rest (Line 118 and Line 123), junit-platform-suite-api (Line 142), and mysql-connector-j (Line 160) are literals while others use properties. Moving them to <properties> keeps upgrades consistent and centralized.

♻️ Suggested refactor
   <properties>
     <maven.compiler.source>21</maven.compiler.source>
     <maven.compiler.target>21</maven.compiler.target>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <junit.jupiter.version>6.0.1</junit.jupiter.version>
     <restassured.version>5.5.7</restassured.version>
     <assertj.version>3.27.6</assertj.version>
     <surefire.version>3.5.4</surefire.version>
     <cucumber.version>7.18.0</cucumber.version>
     <testng.version>7.8.0</testng.version>
     <flyway.version>10.0.1</flyway.version>
+    <jackson.version>2.20.1</jackson.version>
+    <junit.platform.version>1.10.2</junit.platform.version>
+    <mysql.version>8.2.0</mysql.version>
+    <ataf.version>0.2</ataf.version>
   </properties>
...
-      <version>2.20.1</version>
+      <version>${jackson.version}</version>
...
-          <version>0.2</version>
+          <version>${ataf.version}</version>
...
-          <version>0.2</version>
+          <version>${ataf.version}</version>
...
-          <version>1.10.2</version>
+          <version>${junit.platform.version}</version>
...
-          <version>8.2.0</version>
+          <version>${mysql.version}</version>

As per coding guidelines, keep configurable data at high levels.

Also applies to: 116-124, 139-143, 157-160

Comment on lines +12 to +16
@Suite
@IncludeEngines("cucumber")
public class TestRunner {
static {
TestData.init();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

io.cucumber.java.BeforeAll @BeforeAll cucumber-junit-platform-engine 7.18.0 global test initialization

💡 Result:

For global (once-per-test-run) initialization with cucumber-junit-platform-engine 7.18.0, don’t use JUnit Jupiter’s org.junit.jupiter.api.BeforeAll on the suite/runner class—those lifecycle callbacks generally won’t fire because the suite class is only configuration for the JUnit Platform Cucumber engine. Instead, use Cucumber’s global hooks:

1) Put a Cucumber global hook in glue code

package com.mycompany.cucumber;

import io.cucumber.java.BeforeAll;
import io.cucumber.java.AfterAll;

public class GlobalHooks {

  `@BeforeAll`
  public static void globalSetup() {
    // runs once before any scenario
  }

  `@AfterAll`
  public static void globalTearDown() {
    // runs once after all scenarios
  }
}

Cucumber documents io.cucumber.java.BeforeAll / AfterAll as global hooks that run once before/after all scenarios. [1]

2) Make sure your glue package is configured (so the hook is discovered)

Example JUnit Platform suite:

import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.Suite;

import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;

`@Suite`
`@IncludeEngines`("cucumber")
`@SelectClasspathResource`("features")
`@ConfigurationParameter`(key = GLUE_PROPERTY_NAME, value = "com.mycompany.cucumber")
public class CucumberIT {}

This is the standard way to point the engine at your step defs/hooks package via GLUE_PROPERTY_NAME ("cucumber.glue"). [2][3]

Notes

  • The @BeforeAll/@AfterAll hook methods must be static. [1]
  • If your build runs tests in multiple JVM forks, “once” means once per JVM fork (not once for the entire CI job). (That’s a build-level concern, not a Cucumber hook behavior.)

Sources:
[1] Cucumber “Global hooks” (BeforeAll / AfterAll) documentation. [2] Example JUnit Platform suite configuration for Cucumber engine. [3] GLUE_PROPERTY_NAME constant (cucumber.glue) in Cucumber JUnit Platform Engine constants.


🏁 Script executed:

cat -n zmsautomation/src/test/java/zms/ataf/runner/TestRunner.java

Repository: it-at-m/eappointment

Length of output: 707


🏁 Script executed:

fd -t f -name "*Hook*" -o -name "*Bootstrap*" zmsautomation/src/test/java/

Repository: it-at-m/eappointment

Length of output: 294


🏁 Script executed:

rg "TestData.init" --type java -A 3 -B 3

Repository: it-at-m/eappointment

Length of output: 532


🏁 Script executed:

fd -t f -name "TestData.java" zmsautomation/

Repository: it-at-m/eappointment

Length of output: 294


🏁 Script executed:

fd "TestData.java" zmsautomation/src/

Repository: it-at-m/eappointment

Length of output: 119


🏁 Script executed:

fd "Hook" zmsautomation/src/test/java/

Repository: it-at-m/eappointment

Length of output: 124


🏁 Script executed:

find zmsautomation/src/test/java -type f -name "*Hook*" -o -type f -name "*Setup*"

Repository: it-at-m/eappointment

Length of output: 124


🏁 Script executed:

cat zmsautomation/src/test/java/zms/ataf/hooks/DatabaseHook.java

Repository: it-at-m/eappointment

Length of output: 911


🏁 Script executed:

cat zmsautomation/src/test/java/zms/ataf/data/TestData.java

Repository: it-at-m/eappointment

Length of output: 1184


🏁 Script executed:

rg "GLUE_PROPERTY_NAME|cucumber\.glue|ConfigurationParameter" zmsautomation/src/test/java/ -A 2 -B 2

Repository: it-at-m/eappointment

Length of output: 46


🏁 Script executed:

find zmsautomation/src/test/java/zms/ataf -type f -name "*.java" | head -20

Repository: it-at-m/eappointment

Length of output: 364


🏁 Script executed:

cat zmsautomation/src/test/java/zms/ataf/steps/ZmsApiSteps.java | head -30

Repository: it-at-m/eappointment

Length of output: 855


Configure glue path and move initialization to Cucumber global hook.

The static initializer creates a hidden dependency: TestData.init() only runs when TestRunner is loaded; tests run through other entrypoints will skip setup and fail. Additionally, TestRunner does not configure the glue path, so Cucumber won't discover hooks at all.

Move TestData.init() to a Cucumber global hook and configure the glue path:

✏️ Update TestRunner with glue configuration
 `@Suite`
 `@IncludeEngines`("cucumber")
 `@SelectClasspathResource`("features")
+@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "zms.ataf.hooks:zms.ataf.steps")
 public class TestRunner {
-    static {
-        TestData.init();
-    }
 }

Add import: import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;

✨ Create Cucumber global hook (new or update existing hooks package)
package zms.ataf.hooks;

import io.cucumber.java.BeforeAll;
import zms.ataf.data.TestData;

public class GlobalSetupHook {
    `@BeforeAll`
    public static void globalSetup() {
        TestData.init();
    }
}

Note: Use io.cucumber.java.BeforeAll (Cucumber), not JUnit's @org.junit.jupiter.api.BeforeAll.

Also note: DatabaseHook currently uses @BeforeSuite (TestNG), which won't work with JUnit Platform/Cucumber engine. Consider replacing with a Cucumber @Before hook or ensuring TestNG compatibility.

🤖 Prompt for AI Agents
In `@zmsautomation/src/test/java/zms/ataf/runner/TestRunner.java` around lines 12
- 16, The TestRunner class currently calls TestData.init() in a static block and
lacks Cucumber glue config; move TestData.init() into a Cucumber global hook
class (e.g., create zms.ataf.hooks.GlobalSetupHook with a public static method
annotated with io.cucumber.java.BeforeAll that calls TestData.init()), remove
the static initializer from TestRunner, and configure the TestRunner to set the
glue property by importing and using GLUE_PROPERTY_NAME so Cucumber discovers
hooks; also replace or adapt TestNG-specific DatabaseHook annotations (e.g.,
`@BeforeSuite`) to Cucumber-compatible hooks (io.cucumber.java.Before or
BeforeAll) or ensure TestNG compatibility as appropriate.

@ThomasAFink ThomasAFink force-pushed the feat-zmskvr-1124-refactor-to-use-cucumber-and-ataf-compatibility-and-setup-first-test-case branch from d3a92a5 to 6f0a7a0 Compare February 5, 2026 12:42
…etup

- Replace JUnit Platform Suite with TestNG (like zms-test-automation)
- Use BasicTestNGRunner from ATAF (same as zms-test-automation)
- Replace cucumber-junit-platform-engine with cucumber-testng
- Add surefire-testng dependency to use TestNG provider
- Remove JUnit Platform Suite dependencies
- This should fix the 'junit-jupiter failed to discover tests' error
@ThomasAFink
Copy link
Member Author

[SECTION] 11. Running ATAF Tests
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[INFO] Running Cucumber/ATAF tests with -Pataf profile...
[INFO] Note: You can filter tests by tags using: -Dcucumber.filter.tags=@tagName
[INFO] Example: bash zmsautomation-test -Dcucumber.filter.tags=@smoke
[INFO] Keycloak config rewritten (8091→443): /var/www/html/zmsstatistic/keycloak.json
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------< de.muenchen.eappointment:zmsautomation >---------------
[INFO] Building zmsautomation 0.1.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ zmsautomation ---
[INFO] Copying 18 resources from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.14.1:compile (default-compile) @ zmsautomation ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ zmsautomation ---
[INFO] Copying 42 resources from src/test/resources to target/test-classes
[INFO]
[INFO] --- compiler:3.14.1:testCompile (default-testCompile) @ zmsautomation ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 58 source files with javac [debug target 21] to target/test-classes
[INFO] Annotation processing is enabled because one or more processors were found
  on the class path. A future release of javac may disable annotation processing
  unless at least one processor is specified by name (-processor), or a search
  path is specified (--processor-path, --processor-module-path), or annotation
  processing is enabled explicitly (-proc:only, -proc:full).
  Use -Xlint:-options to suppress this message.
  Use -proc:none to disable annotation processing.
[INFO] /var/www/html/zmsautomation/src/test/java/zms/ataf/data/TestData.java: Some input files use or override a deprecated API.
[INFO] /var/www/html/zmsautomation/src/test/java/zms/ataf/data/TestData.java: Recompile with -Xlint:deprecation for details.
[INFO]
[INFO] --- surefire:3.5.4:test (default-test) @ zmsautomation ---
[INFO] Using configured provider org.apache.maven.surefire.testng.TestNGProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running zms.ataf.runner.UiTestRunner
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[1] 15:37:26 INFO  TestData:17 - Initializing ZMS API test environments
[1] 15:37:26 INFO  RunnerUtils:87 - Start of Test Suite!
[1] 15:37:26 INFO  TestProperties:36 - >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Start of loading properties>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[1] 15:37:26 INFO  TestProperties:150 - (string) Property [features] with value [src/test/resources/features] has been successfully loaded!
[1] 15:37:26 INFO  TestProperties:150 - (string) Property [userName] with value [ataf] has been successfully loaded!
[1] 15:37:26 INFO  TestProperties:150 - (string) Property [noProxy] with value [keycloak] has been successfully loaded!
[1] 15:37:26 INFO  TestProperties:150 - (string) Property [logLevel] with value [INFO] has been successfully loaded!
[1] 15:37:26 INFO  TestProperties:150 - (string) Property [publish.quiet] with value [true] has been successfully loaded!
[1] 15:37:26 INFO  TestProperties:150 - (string) Property [filter.tags] with value [@web and @zmsstatistic] has been successfully loaded!
[1] 15:37:26 INFO  TestProperties:142 - (string) Property [testDataEncryptionPassword] has been successfully loaded!
[1] 15:37:26 INFO  TestProperties:150 - (string) Property [plugin] with value [json:target/cucumber.json,html:target/site/cucumber-pretty] has been successfully loaded!
[1] 15:37:26 INFO  TestProperties:150 - (string) Property [glue] with value [zms.ataf.steps,zms.ataf.api.steps,zms.ataf.ui.steps,zms.ataf.hooks,ataf.rest.steps,ataf.web.steps] has been successfully loaded!
[1] 15:37:26 INFO  TestProperties:142 - (string) Property [userPassword] has been successfully loaded!
[1] 15:37:26 INFO  TestProperties:150 - (string) Property [publish.enabled] with value [false] has been successfully loaded!
[1] 15:37:26 INFO  TestProperties:150 - (string) Property [test.execution.test.environment] with value [LOCAL] has been successfully loaded!
[1] 15:37:26 INFO  TestProperties:63 - <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Finished loading of properties<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
[1] 15:37:26 INFO  RunnerUtils:116 - Log Level set to "INFO"
[1] 15:37:26 WARN  RunnerUtils:228 - Cannot create JQL: No property value for "issueKeys" or "filterId" has been provided. Also see: https://docs.getxray.app/display/XRAY/Exporting+Cucumber+Tests+-+REST
[1] 15:37:26 INFO  Hook:25 - Hook: BeforeAll (Framework-API)
[1] 15:37:27 INFO  Hook:142 - >>>>>>>Start of test scenario: Kundenstatistik>>>>>>>
[1] 15:37:27 INFO  DriverUtil:132 - Test will be executed locally!
[1] 15:37:27 INFO  DriverUtil:217 - Proxy set
[1] 15:37:35 INFO  Hook:209 - Wait for 250 ms...
[1] 15:37:36 INFO  BasePage:1977 - Trying to navigate to "https://localhost:443/terminvereinbarung/statistic/"
[1] 15:38:01 INFO  BasePage:1984 - Waiting until page title is "Anmeldung - ZMS Statistik"
[1] 15:38:01 INFO  WindowControls:78 - Window added to the list: [0] Window{windowHandle='762dade9-f881-4536-8e99-9466342f6360', windowTitle='Anmeldung - ZMS Statistik', windowType=ataf.web.model.WindowType@3e5fd2b1}
[1] 15:38:01 INFO  Hook:209 - Wait for 250 ms...
[1] 15:38:01 INFO  StatisticsPage:71 - Trying to click on "Login" button...
[1] 15:38:02 INFO  StatisticsPage:74 - SSO-Login page detected!
[1] 15:38:02 INFO  StatisticsPage:94 - Trying to enter user name...
[1] 15:38:02 INFO  StatisticsPage:97 - Trying to enter password...
[1] 15:38:02 INFO  StatisticsPage:100 - Trying to click on "Login" button...
[1] 15:38:32 ERROR StatisticsPage:104 - Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [82e99723-1534-484b-adac-79c60494beff, clickElement {id=b177c13f-d5d2-413b-b0ff-5e3198cfa204}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 9619, moz:profile: /tmp/rust_mozprofileERpSpF, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (82e99723-1534-484b-adac-79c60494beff)] -> id: kc-login]
Session ID: 82e99723-1534-484b-adac-79c60494beff
org.openqa.selenium.TimeoutException: Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [82e99723-1534-484b-adac-79c60494beff, clickElement {id=b177c13f-d5d2-413b-b0ff-5e3198cfa204}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 9619, moz:profile: /tmp/rust_mozprofileERpSpF, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (82e99723-1534-484b-adac-79c60494beff)] -> id: kc-login]
Session ID: 82e99723-1534-484b-adac-79c60494beff
        at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:169) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:142) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:223) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:216) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:174) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:223) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebElement.click(RemoteWebElement.java:76) ~[selenium-remote-driver-4.40.0.jar:?]
        at ataf.web.pages.BasePage.lambda$clickOnWebElement$4(BasePage.java:867) ~[web-0.2.jar:?]
        at org.openqa.selenium.support.ui.FluentWait.until(FluentWait.java:207) ~[selenium-support-4.40.0.jar:?]
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:852) ~[web-0.2.jar:?]
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:912) ~[web-0.2.jar:?]
        at zms.ataf.ui.pages.statistics.StatisticsPage.clickOnLoginButton(StatisticsPage.java:101) ~[test-classes/:?]
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_in_der_statistik_auf_die_schaltflaeche_string_klicken(StatisticsSteps.java:60) ~[test-classes/:?]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
        at io.cucumber.java.Invoker.doInvoke(Invoker.java:66) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.Invoker.invoke(Invoker.java:24) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.AbstractGlueDefinition.invokeMethod(AbstractGlueDefinition.java:47) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.JavaStepDefinition.execute(JavaStepDefinition.java:29) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.core.runner.CoreStepDefinition.execute(CoreStepDefinition.java:66) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.PickleStepDefinitionMatch.runStep(PickleStepDefinitionMatch.java:62) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.ExecutionMode$1.execute(ExecutionMode.java:10) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestStep.executeStep(TestStep.java:81) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestStep.run(TestStep.java:53) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.PickleStepTestStep.run(PickleStepTestStep.java:55) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestCase.run(TestCase.java:87) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.Runner.runPickle(Runner.java:75) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.testng.TestNGCucumberRunner.lambda$runScenario$1(TestNGCucumberRunner.java:132) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at io.cucumber.core.runtime.CucumberExecutionContext.lambda$runTestCase$5(CucumberExecutionContext.java:136) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runtime.RethrowingThrowableCollector.executeAndThrow(RethrowingThrowableCollector.java:23) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runtime.CucumberExecutionContext.runTestCase(CucumberExecutionContext.java:136) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.testng.TestNGCucumberRunner.runScenario(TestNGCucumberRunner.java:129) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at io.cucumber.testng.AbstractTestNGCucumberTests.runScenario(AbstractTestNGCucumberTests.java:35) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
        at org.testng.internal.invokers.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:139) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeMethod(TestInvoker.java:664) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeTestMethod(TestInvoker.java:227) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.MethodRunner.runInSequence(MethodRunner.java:50) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:957) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeTestMethods(TestInvoker.java:200) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:148) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestMethodWorker.run(TestMethodWorker.java:128) ~[testng-7.8.0.jar:7.8.0]
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) [?:?]
        at org.testng.TestRunner.privateRun(TestRunner.java:848) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestRunner.run(TestRunner.java:621) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.runTest(SuiteRunner.java:443) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:437) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.privateRun(SuiteRunner.java:397) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.run(SuiteRunner.java:336) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:95) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuitesSequentially(TestNG.java:1280) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuitesLocally(TestNG.java:1200) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuites(TestNG.java:1114) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.run(TestNG.java:1082) [testng-7.8.0.jar:7.8.0]
        at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:155) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeSingleClass(TestNGDirectoryTestSuite.java:102) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:91) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:137) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495) [surefire-booter-3.5.4.jar:3.5.4]
[1] 15:38:32 ERROR Hook:174 - Test failed: Kundenstatistik
[1] 15:38:32 INFO  WindowControls:554 - Window list cleared!
[1] 15:38:32 INFO  Hook:189 - <<<<<<<End of test scenario: Kundenstatistik<<<<<<<
[1] 15:38:32 INFO  Hook:142 - >>>>>>>Start of test scenario: Dienstleistungsstatistik>>>>>>>
[1] 15:38:32 INFO  DriverUtil:132 - Test will be executed locally!
[1] 15:38:32 INFO  DriverUtil:217 - Proxy set
[1] 15:38:40 INFO  Hook:209 - Wait for 250 ms...
[1] 15:38:40 INFO  BasePage:1977 - Trying to navigate to "https://localhost:443/terminvereinbarung/statistic/"
[1] 15:39:10 ERROR BasePage:1980 - Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [5ddbfed1-debf-4c5f-bd65-6bfd11173473, get {url=https://localhost:443/terminvereinbarung/statistic/}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 10081, moz:profile: /tmp/rust_mozprofileg2iTbA, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Session ID: 5ddbfed1-debf-4c5f-bd65-6bfd11173473
org.openqa.selenium.TimeoutException: Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [5ddbfed1-debf-4c5f-bd65-6bfd11173473, get {url=https://localhost:443/terminvereinbarung/statistic/}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 10081, moz:profile: /tmp/rust_mozprofileg2iTbA, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Session ID: 5ddbfed1-debf-4c5f-bd65-6bfd11173473
        at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:169) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:142) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:223) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:216) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:174) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebDriver.get(RemoteWebDriver.java:369) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebDriver$RemoteNavigation.to(RemoteWebDriver.java:1179) ~[selenium-remote-driver-4.40.0.jar:?]
        at ataf.web.pages.BasePage.navigateToPageByUrl(BasePage.java:1978) ~[web-0.2.jar:?]
        at zms.ataf.ui.pages.statistics.StatisticsPageContext.navigateToPage(StatisticsPageContext.java:39) ~[test-classes/:?]
        at zms.ataf.ui.pages.statistics.StatisticsPage.navigateToPage(StatisticsPage.java:67) ~[test-classes/:?]
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_zur_webseite_der_administration_navigieren(StatisticsSteps.java:45) ~[test-classes/:?]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
        at io.cucumber.java.Invoker.doInvoke(Invoker.java:66) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.Invoker.invoke(Invoker.java:24) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.AbstractGlueDefinition.invokeMethod(AbstractGlueDefinition.java:47) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.JavaStepDefinition.execute(JavaStepDefinition.java:29) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.core.runner.CoreStepDefinition.execute(CoreStepDefinition.java:66) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.PickleStepDefinitionMatch.runStep(PickleStepDefinitionMatch.java:62) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.ExecutionMode$1.execute(ExecutionMode.java:10) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestStep.executeStep(TestStep.java:81) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestStep.run(TestStep.java:53) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.PickleStepTestStep.run(PickleStepTestStep.java:55) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestCase.run(TestCase.java:87) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.Runner.runPickle(Runner.java:75) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.testng.TestNGCucumberRunner.lambda$runScenario$1(TestNGCucumberRunner.java:132) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at io.cucumber.core.runtime.CucumberExecutionContext.lambda$runTestCase$5(CucumberExecutionContext.java:136) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runtime.RethrowingThrowableCollector.executeAndThrow(RethrowingThrowableCollector.java:23) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runtime.CucumberExecutionContext.runTestCase(CucumberExecutionContext.java:136) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.testng.TestNGCucumberRunner.runScenario(TestNGCucumberRunner.java:129) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at io.cucumber.testng.AbstractTestNGCucumberTests.runScenario(AbstractTestNGCucumberTests.java:35) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
        at org.testng.internal.invokers.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:139) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeMethod(TestInvoker.java:664) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeTestMethod(TestInvoker.java:227) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.MethodRunner.runInSequence(MethodRunner.java:50) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:957) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeTestMethods(TestInvoker.java:200) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:148) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestMethodWorker.run(TestMethodWorker.java:128) ~[testng-7.8.0.jar:7.8.0]
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) [?:?]
        at org.testng.TestRunner.privateRun(TestRunner.java:848) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestRunner.run(TestRunner.java:621) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.runTest(SuiteRunner.java:443) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:437) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.privateRun(SuiteRunner.java:397) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.run(SuiteRunner.java:336) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:95) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuitesSequentially(TestNG.java:1280) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuitesLocally(TestNG.java:1200) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuites(TestNG.java:1114) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.run(TestNG.java:1082) [testng-7.8.0.jar:7.8.0]
        at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:155) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeSingleClass(TestNGDirectoryTestSuite.java:102) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:91) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:137) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495) [surefire-booter-3.5.4.jar:3.5.4]
[1] 15:39:11 ERROR Hook:174 - Test failed: Dienstleistungsstatistik
[1] 15:39:11 INFO  Hook:189 - <<<<<<<End of test scenario: Dienstleistungsstatistik<<<<<<<
[1] 15:39:11 INFO  Hook:68 - Hook: AfterAll (Framework-API)
[1] 15:39:11 INFO  AuthenticationHelper:125 - User name disposed!
[1] 15:39:11 INFO  AuthenticationHelper:132 - Password disposed!
[1] 15:39:11 WARN  AuthenticationHelper:143 - Authorization token cannot be disposed! It has not been set yet.
[ERROR] Tests run: 2, Failures: 2, Errors: 0, Skipped: 0, Time elapsed: 105.9 s <<< FAILURE! -- in zms.ataf.runner.UiTestRunner
[ERROR] zms.ataf.runner.UiTestRunner.runScenario["Kundenstatistik", "Aufbau ZMS-Testautomatisierung"](1) -- Time elapsed: 65.89 s <<< FAILURE!
org.openqa.selenium.TimeoutException:
Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [82e99723-1534-484b-adac-79c60494beff, clickElement {id=b177c13f-d5d2-413b-b0ff-5e3198cfa204}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 9619, moz:profile: /tmp/rust_mozprofileERpSpF, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (82e99723-1534-484b-adac-79c60494beff)] -> id: kc-login]
Session ID: 82e99723-1534-484b-adac-79c60494beff
        at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:169)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:142)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49)
        at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:223)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:216)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:174)
        at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601)
        at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:223)
        at org.openqa.selenium.remote.RemoteWebElement.click(RemoteWebElement.java:76)
        at ataf.web.pages.BasePage.lambda$clickOnWebElement$4(BasePage.java:867)
        at org.openqa.selenium.support.ui.FluentWait.until(FluentWait.java:207)
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:852)
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:912)
        at zms.ataf.ui.pages.statistics.StatisticsPage.clickOnLoginButton(StatisticsPage.java:101)
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_in_der_statistik_auf_die_schaltflaeche_string_klicken(StatisticsSteps.java:60)
        at ?.Sie in der Statistik auf die Schaltfl?che "Anmelden" klicken.(file:///var/www/html/zmsautomation/src/test/resources/features/ui/zmsstatistic/ZMS-1558.feature:8)

[ERROR] zms.ataf.runner.UiTestRunner.runScenario["Dienstleistungsstatistik", "Default"](2) -- Time elapsed: 38.11 s <<< FAILURE!
java.lang.AssertionError: Could not navigate to statistics page!
        at org.testng.Assert.fail(Assert.java:111)
        at zms.ataf.ui.pages.statistics.StatisticsPageContext.navigateToPage(StatisticsPageContext.java:43)
        at zms.ataf.ui.pages.statistics.StatisticsPage.navigateToPage(StatisticsPage.java:67)
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_zur_webseite_der_administration_navigieren(StatisticsSteps.java:45)
        at ?.Sie zur Webseite der Statistik navigieren.(file:///var/www/html/zmsautomation/src/test/resources/features/ui/zmsstatistic/ZMS-1559.feature:7)

[INFO]
[INFO] Results:
[INFO]
[ERROR] Failures:
[ERROR]   UiTestRunner.runScenario Could not navigate to statistics page!
[ERROR]   UiTestRunner.runScenario ? Timeout Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [82e99723-1534-484b-adac-79c60494beff, clickElement {id=b177c13f-d5d2-413b-b0ff-5e3198cfa204}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 9619, moz:profile: /tmp/rust_mozprofileERpSpF, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (82e99723-1534-484b-adac-79c60494beff)] -> id: kc-login]
Session ID: 82e99723-1534-484b-adac-79c60494beff
[INFO]
[ERROR] Tests run: 2, Failures: 2, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:51 min
[INFO] Finished at: 2026-03-06T15:39:11+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.5.4:test (default-test) on project zmsautomation: There are test failures.
[ERROR]
[ERROR] See /var/www/html/zmsautomation/target/surefire-reports for the individual test results.
[ERROR] See dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[SECTION] 12. Test Results
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[INFO] Collecting surefire reports...
total 272
drwxr-xr-x 5 root root  4096 Mar  6 15:39  .
drwxr-xr-x 9 root root 12288 Mar  6 15:39  ..
drwxr-xr-x 2 root root  4096 Mar  6 15:39 'Surefire suite'
-rw-r--r-- 1 root root 55497 Mar  6 15:39  TEST-zms.ataf.runner.UiTestRunner.xml
-rw-r--r-- 1 root root   356 Mar  6 15:39  bullet_point.png
-rw-r--r-- 1 root root   157 Mar  6 15:39  collapseall.gif
-rw-r--r-- 1 root root  6088 Mar  6 15:39  emailable-report.html
-rw-r--r-- 1 root root   977 Mar  6 15:39  failed.png
-rw-r--r-- 1 root root 18422 Mar  6 15:39  index.html
-rw-r--r-- 1 root root 89501 Mar  6 15:39  jquery-3.6.0.min.js
drwxr-xr-x 2 root root  4096 Mar  6 15:39  junitreports
-rw-r--r-- 1 root root   352 Mar  6 15:39  navigator-bullet.png
drwxr-xr-x 3 root root  4096 Mar  6 15:39  old
-rw-r--r-- 1 root root  1019 Mar  6 15:39  passed.png
-rw-r--r-- 1 root root   967 Mar  6 15:39  skipped.png
-rw-r--r-- 1 root root   714 Mar  6 15:39  testng-failed.xml
-rw-r--r-- 1 root root  5355 Mar  6 15:39  testng-reports.css
-rw-r--r-- 1 root root  3612 Mar  6 15:39  testng-reports.js
-rw-r--r-- 1 root root  7050 Mar  6 15:39  testng-reports1.css
-rw-r--r-- 1 root root  3618 Mar  6 15:39  testng-reports2.js
-rw-r--r-- 1 root root  9279 Mar  6 15:39  testng-results.xml
-rw-r--r-- 1 root root   303 Mar  6 15:39  testng.css
-rw-r--r-- 1 root root  3879 Mar  6 15:39  zms.ataf.runner.UiTestRunner.txt
==== /var/www/html/zmsautomation/target/surefire-reports/zms.ataf.runner.UiTestRunner.txt ====
-------------------------------------------------------------------------------
Test set: zms.ataf.runner.UiTestRunner
-------------------------------------------------------------------------------
Tests run: 2, Failures: 2, Errors: 0, Skipped: 0, Time elapsed: 105.9 s <<< FAILURE! -- in zms.ataf.runner.UiTestRunner
zms.ataf.runner.UiTestRunner.runScenario["Kundenstatistik", "Aufbau ZMS-Testautomatisierung"](1) -- Time elapsed: 65.89 s <<< FAILURE!
org.openqa.selenium.TimeoutException:
Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [82e99723-1534-484b-adac-79c60494beff, clickElement {id=b177c13f-d5d2-413b-b0ff-5e3198cfa204}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 9619, moz:profile: /tmp/rust_mozprofileERpSpF, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (82e99723-1534-484b-adac-79c60494beff)] -> id: kc-login]
Session ID: 82e99723-1534-484b-adac-79c60494beff
        at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:169)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:142)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49)
        at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:223)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:216)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:174)
        at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601)
        at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:223)
        at org.openqa.selenium.remote.RemoteWebElement.click(RemoteWebElement.java:76)
        at ataf.web.pages.BasePage.lambda$clickOnWebElement$4(BasePage.java:867)
        at org.openqa.selenium.support.ui.FluentWait.until(FluentWait.java:207)
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:852)
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:912)
        at zms.ataf.ui.pages.statistics.StatisticsPage.clickOnLoginButton(StatisticsPage.java:101)
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_in_der_statistik_auf_die_schaltflaeche_string_klicken(StatisticsSteps.java:60)
        at ✽.Sie in der Statistik auf die Schaltfläche "Anmelden" klicken.(file:///var/www/html/zmsautomation/src/test/resources/features/ui/zmsstatistic/ZMS-1558.feature:8)

zms.ataf.runner.UiTestRunner.runScenario["Dienstleistungsstatistik", "Default"](2) -- Time elapsed: 38.11 s <<< FAILURE!
java.lang.AssertionError: Could not navigate to statistics page!
        at org.testng.Assert.fail(Assert.java:111)
        at zms.ataf.ui.pages.statistics.StatisticsPageContext.navigateToPage(StatisticsPageContext.java:43)
        at zms.ataf.ui.pages.statistics.StatisticsPage.navigateToPage(StatisticsPage.java:67)
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_zur_webseite_der_administration_navigieren(StatisticsSteps.java:45)
        at ✽.Sie zur Webseite der Statistik navigieren.(file:///var/www/html/zmsautomation/src/test/resources/features/ui/zmsstatistic/ZMS-1559.feature:7)


[INFO] Cucumber reports available at:
[INFO]   - JSON: /var/www/html/zmsautomation/target/cucumber.json
[INFO]   - HTML: /var/www/html/zmsautomation/target/site/cucumber-pretty/index.html

Increase navigation timeouts for the statistics start page and treat slow Keycloak redirects as non-fatal so the zmsstatistic UI scenarios are less sensitive to transient SSO and environment delays.
@ThomasAFink
Copy link
Member Author

[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running zms.ataf.runner.UiTestRunner
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[1] 15:50:44 INFO  TestData:17 - Initializing ZMS API test environments
[1] 15:50:44 INFO  RunnerUtils:87 - Start of Test Suite!
[1] 15:50:44 INFO  TestProperties:36 - >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Start of loading properties>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[1] 15:50:44 INFO  TestProperties:150 - (string) Property [features] with value [src/test/resources/features] has been successfully loaded!
[1] 15:50:44 INFO  TestProperties:150 - (string) Property [userName] with value [ataf] has been successfully loaded!
[1] 15:50:44 INFO  TestProperties:150 - (string) Property [noProxy] with value [keycloak] has been successfully loaded!
[1] 15:50:44 INFO  TestProperties:150 - (string) Property [logLevel] with value [INFO] has been successfully loaded!
[1] 15:50:44 INFO  TestProperties:150 - (string) Property [publish.quiet] with value [true] has been successfully loaded!
[1] 15:50:44 INFO  TestProperties:150 - (string) Property [filter.tags] with value [@web and @zmsstatistic] has been successfully loaded!
[1] 15:50:44 INFO  TestProperties:142 - (string) Property [testDataEncryptionPassword] has been successfully loaded!
[1] 15:50:44 INFO  TestProperties:150 - (string) Property [plugin] with value [json:target/cucumber.json,html:target/site/cucumber-pretty] has been successfully loaded!
[1] 15:50:44 INFO  TestProperties:150 - (string) Property [glue] with value [zms.ataf.steps,zms.ataf.api.steps,zms.ataf.ui.steps,zms.ataf.hooks,ataf.rest.steps,ataf.web.steps] has been successfully loaded!
[1] 15:50:44 INFO  TestProperties:142 - (string) Property [userPassword] has been successfully loaded!
[1] 15:50:44 INFO  TestProperties:150 - (string) Property [publish.enabled] with value [false] has been successfully loaded!
[1] 15:50:44 INFO  TestProperties:150 - (string) Property [test.execution.test.environment] with value [LOCAL] has been successfully loaded!
[1] 15:50:44 INFO  TestProperties:63 - <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Finished loading of properties<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
[1] 15:50:44 INFO  RunnerUtils:116 - Log Level set to "INFO"
[1] 15:50:44 WARN  RunnerUtils:228 - Cannot create JQL: No property value for "issueKeys" or "filterId" has been provided. Also see: https://docs.getxray.app/display/XRAY/Exporting+Cucumber+Tests+-+REST
[1] 15:50:45 INFO  Hook:25 - Hook: BeforeAll (Framework-API)
[1] 15:50:45 INFO  Hook:142 - >>>>>>>Start of test scenario: Kundenstatistik>>>>>>>
[1] 15:50:45 INFO  DriverUtil:132 - Test will be executed locally!
[1] 15:50:45 INFO  DriverUtil:217 - Proxy set
[1] 15:50:56 INFO  Hook:209 - Wait for 250 ms...
[1] 15:50:56 INFO  BasePage:1977 - Trying to navigate to "https://localhost:443/terminvereinbarung/statistic/"
[1] 15:51:26 ERROR BasePage:1980 - Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [a881e54b-b794-4de1-9282-3ebbedc0830b, get {url=https://localhost:443/terminvereinbarung/statistic/}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 362, moz:profile: /tmp/rust_mozprofileHspnYB, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Session ID: a881e54b-b794-4de1-9282-3ebbedc0830b
org.openqa.selenium.TimeoutException: Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [a881e54b-b794-4de1-9282-3ebbedc0830b, get {url=https://localhost:443/terminvereinbarung/statistic/}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 362, moz:profile: /tmp/rust_mozprofileHspnYB, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Session ID: a881e54b-b794-4de1-9282-3ebbedc0830b
        at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:169) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:142) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:223) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:216) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:174) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebDriver.get(RemoteWebDriver.java:369) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebDriver$RemoteNavigation.to(RemoteWebDriver.java:1179) ~[selenium-remote-driver-4.40.0.jar:?]
        at ataf.web.pages.BasePage.navigateToPageByUrl(BasePage.java:1978) ~[web-0.2.jar:?]
        at zms.ataf.ui.pages.statistics.StatisticsPageContext.navigateToPage(StatisticsPageContext.java:40) ~[test-classes/:?]
        at zms.ataf.ui.pages.statistics.StatisticsPage.navigateToPage(StatisticsPage.java:67) ~[test-classes/:?]
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_zur_webseite_der_administration_navigieren(StatisticsSteps.java:45) ~[test-classes/:?]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
        at io.cucumber.java.Invoker.doInvoke(Invoker.java:66) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.Invoker.invoke(Invoker.java:24) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.AbstractGlueDefinition.invokeMethod(AbstractGlueDefinition.java:47) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.JavaStepDefinition.execute(JavaStepDefinition.java:29) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.core.runner.CoreStepDefinition.execute(CoreStepDefinition.java:66) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.PickleStepDefinitionMatch.runStep(PickleStepDefinitionMatch.java:62) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.ExecutionMode$1.execute(ExecutionMode.java:10) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestStep.executeStep(TestStep.java:81) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestStep.run(TestStep.java:53) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.PickleStepTestStep.run(PickleStepTestStep.java:55) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestCase.run(TestCase.java:87) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.Runner.runPickle(Runner.java:75) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.testng.TestNGCucumberRunner.lambda$runScenario$1(TestNGCucumberRunner.java:132) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at io.cucumber.core.runtime.CucumberExecutionContext.lambda$runTestCase$5(CucumberExecutionContext.java:136) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runtime.RethrowingThrowableCollector.executeAndThrow(RethrowingThrowableCollector.java:23) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runtime.CucumberExecutionContext.runTestCase(CucumberExecutionContext.java:136) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.testng.TestNGCucumberRunner.runScenario(TestNGCucumberRunner.java:129) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at io.cucumber.testng.AbstractTestNGCucumberTests.runScenario(AbstractTestNGCucumberTests.java:35) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
        at org.testng.internal.invokers.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:139) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeMethod(TestInvoker.java:664) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeTestMethod(TestInvoker.java:227) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.MethodRunner.runInSequence(MethodRunner.java:50) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:957) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeTestMethods(TestInvoker.java:200) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:148) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestMethodWorker.run(TestMethodWorker.java:128) ~[testng-7.8.0.jar:7.8.0]
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) [?:?]
        at org.testng.TestRunner.privateRun(TestRunner.java:848) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestRunner.run(TestRunner.java:621) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.runTest(SuiteRunner.java:443) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:437) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.privateRun(SuiteRunner.java:397) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.run(SuiteRunner.java:336) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:95) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuitesSequentially(TestNG.java:1280) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuitesLocally(TestNG.java:1200) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuites(TestNG.java:1114) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.run(TestNG.java:1082) [testng-7.8.0.jar:7.8.0]
        at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:155) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeSingleClass(TestNGDirectoryTestSuite.java:102) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:91) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:137) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495) [surefire-booter-3.5.4.jar:3.5.4]
[1] 15:51:26 ERROR Hook:174 - Test failed: Kundenstatistik
[1] 15:51:26 INFO  Hook:189 - <<<<<<<End of test scenario: Kundenstatistik<<<<<<<
[1] 15:51:26 INFO  Hook:142 - >>>>>>>Start of test scenario: Dienstleistungsstatistik>>>>>>>
[1] 15:51:26 INFO  DriverUtil:132 - Test will be executed locally!
[1] 15:51:26 INFO  DriverUtil:217 - Proxy set
[1] 15:51:34 INFO  Hook:209 - Wait for 250 ms...
[1] 15:51:34 INFO  BasePage:1977 - Trying to navigate to "https://localhost:443/terminvereinbarung/statistic/"

Increase navigation and window switch timeout for statistics context to 60s to better tolerate slow loads.
@ThomasAFink
Copy link
Member Author

@coderabbitai

[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running zms.ataf.runner.UiTestRunner
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[1] 01:33:37 INFO  TestData:17 - Initializing ZMS API test environments
[1] 01:33:38 INFO  RunnerUtils:87 - Start of Test Suite!
[1] 01:33:38 INFO  TestProperties:36 - >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Start of loading properties>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[1] 01:33:38 INFO  TestProperties:150 - (string) Property [features] with value [src/test/resources/features] has been successfully loaded!
[1] 01:33:38 INFO  TestProperties:150 - (string) Property [userName] with value [ataf] has been successfully loaded!
[1] 01:33:38 INFO  TestProperties:150 - (string) Property [noProxy] with value [keycloak] has been successfully loaded!
[1] 01:33:38 INFO  TestProperties:150 - (string) Property [logLevel] with value [INFO] has been successfully loaded!
[1] 01:33:38 INFO  TestProperties:150 - (string) Property [publish.quiet] with value [true] has been successfully loaded!
[1] 01:33:38 INFO  TestProperties:150 - (string) Property [filter.tags] with value [@web and @zmsstatistic] has been successfully loaded!
[1] 01:33:38 INFO  TestProperties:142 - (string) Property [testDataEncryptionPassword] has been successfully loaded!
[1] 01:33:38 INFO  TestProperties:150 - (string) Property [plugin] with value [json:target/cucumber.json,html:target/site/cucumber-pretty] has been successfully loaded!
[1] 01:33:38 INFO  TestProperties:150 - (string) Property [glue] with value [zms.ataf.steps,zms.ataf.api.steps,zms.ataf.ui.steps,zms.ataf.hooks,ataf.rest.steps,ataf.web.steps] has been successfully loaded!
[1] 01:33:38 INFO  TestProperties:142 - (string) Property [userPassword] has been successfully loaded!
[1] 01:33:38 INFO  TestProperties:150 - (string) Property [publish.enabled] with value [false] has been successfully loaded!
[1] 01:33:38 INFO  TestProperties:150 - (string) Property [test.execution.test.environment] with value [LOCAL] has been successfully loaded!
[1] 01:33:38 INFO  TestProperties:63 - <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Finished loading of properties<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
[1] 01:33:38 INFO  RunnerUtils:116 - Log Level set to "INFO"
[1] 01:33:38 WARN  RunnerUtils:228 - Cannot create JQL: No property value for "issueKeys" or "filterId" has been provided. Also see: https://docs.getxray.app/display/XRAY/Exporting+Cucumber+Tests+-+REST
[1] 01:33:38 INFO  Hook:25 - Hook: BeforeAll (Framework-API)
[1] 01:33:38 INFO  Hook:142 - >>>>>>>Start of test scenario: Kundenstatistik>>>>>>>
[1] 01:33:38 INFO  DriverUtil:132 - Test will be executed locally!
[1] 01:33:38 INFO  DriverUtil:217 - Proxy set
Mar 07, 2026 1:34:21 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Problem reading geckodriver versions: error sending request for url (https://raw.githubusercontent.com/SeleniumHQ/selenium/trunk/common/geckodriver/geckodriver-support.json). Using latest geckodriver version
Mar 07, 2026 1:34:21 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Exception managing firefox: error sending request for url (https://github.com/mozilla/geckodriver/releases/latest)
Mar 07, 2026 1:34:21 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Error sending stats to Plausible: error sending request for url (https://plausible.io/api/event)
[1] 01:34:30 INFO  Hook:209 - Wait for 250 ms...
[1] 01:34:31 INFO  BasePage:1977 - Trying to navigate to "https://localhost:443/terminvereinbarung/statistic/"
[1] 01:34:53 INFO  BasePage:1984 - Waiting until page title is "Anmeldung - ZMS Statistik"
[1] 01:34:53 INFO  WindowControls:78 - Window added to the list: [0] Window{windowHandle='63da8679-9c64-43bc-ad19-009284b34533', windowTitle='Anmeldung - ZMS Statistik', windowType=ataf.web.model.WindowType@2f4ba1ae}
[1] 01:34:53 INFO  Hook:209 - Wait for 250 ms...
[1] 01:34:53 INFO  StatisticsPage:71 - Trying to click on "Login" button...
[1] 01:34:54 INFO  StatisticsPage:74 - SSO-Login page detected!
[1] 01:34:54 INFO  StatisticsPage:94 - Trying to enter user name...
[1] 01:34:54 INFO  StatisticsPage:97 - Trying to enter password...
[1] 01:34:54 INFO  StatisticsPage:100 - Trying to click on "Login" button (Keycloak)...
[1] 01:35:18 INFO  StatisticsPage:103 - SSO login submitted successfully.
[1] 01:35:18 INFO  Hook:209 - Wait for 250 ms...
[1] 01:35:18 INFO  StatisticsPage:125 - Trying to select location "Gewerbeamt (KVR-III/23) Verkehr"
[1] 01:35:19 INFO  BasePage:1613 - Text "Gewerbeamt (KVR-III/23) Verkehr" found in Dropdown
[1] 01:35:20 INFO  Hook:209 - Wait for 250 ms...
[1] 01:35:20 INFO  StatisticsPage:131 - Trying to click on "Auswahl best?tigen" button...
[1] 01:35:44 INFO  Hook:209 - Wait for 250 ms...
[1] 01:35:44 INFO  StatisticsPage:136 - Checking if the overview page is visible.
[1] 01:35:44 INFO  Hook:209 - Wait for 250 ms...
[1] 01:35:45 INFO  StatisticsPage:145 - Trying to click on "Kundenstatistik" button in the sidebar...
[1] 01:36:07 INFO  Hook:209 - Wait for 250 ms...
[1] 01:36:07 INFO  StatisticsPage:150 - Checking if the Kundenstatistik statistics page is visible.
[1] 01:36:07 INFO  Hook:209 - Wait for 250 ms...
[1] 01:36:08 INFO  StatisticsPage:165 - Attempting to apply Standort filter on statistics sub-page with value: "Gewerbeamt (KVR-III/23) Verkehr"
[1] 01:36:08 INFO  StatisticsPage:175 - Location/date filter panel detected. Applying location for statistics sub-page...
[1] 01:36:08 INFO  StatisticsPage:183 - Selecting statistics location in multi-select scope picker: Gewerbeamt (KVR-III/23) Verkehr
[1] 01:36:09 INFO  StatisticsPage:207 - Location "Gewerbeamt (KVR-III/23) Verkehr" selected in scope-select.
[1] 01:36:09 INFO  Hook:209 - Wait for 250 ms...
[1] 01:36:09 INFO  StatisticsPage:219 - Attempting to apply date range filter on statistics sub-page...
[1] 01:36:09 INFO  StatisticsPage:231 - Setting statistics date range from 2026-02-21 to 2026-03-07
[1] 01:36:09 INFO  StatisticsPage:259 - Date input 'from' set to 2026-02-21
[1] 01:36:09 INFO  StatisticsPage:259 - Date input 'to' set to 2026-03-07
[1] 01:36:09 INFO  StatisticsPage:235 - Submitting statistics filter with ?bernehmen button via JavaScript click...
[1] 01:36:09 INFO  Hook:209 - Wait for 250 ms...
[1] 01:36:10 INFO  CustomerStatisticsPage:45 - Verifying the number of customers who appeared on 06.03.2026. Expected number: 2
[1] 01:36:11 INFO  CustomerStatisticsPage:56 - Verifying the number of customers who didn't appear on 06.03.2026. Expected number: 2
[1] 01:36:12 INFO  CustomerStatisticsPage:67 - Verifying the number of appointment customers who appeard on 06.03.2026. Expected number: 1
[1] 01:36:12 INFO  CustomerStatisticsPage:79 - Verifying the number of appointment customers who didn't appear on 06.03.2026. Expected number: 1
[1] 01:36:12 INFO  CustomerStatisticsPage:90 - Verifying the number of spontaneous customers who appeard on 06.03.2026. Expected number: 1
[1] 01:36:13 INFO  CustomerStatisticsPage:102 - Verifying the number of spontaneous customers who didn't appear on 06.03.2026. Expected number: 1
[1] 01:36:13 INFO  Hook:209 - Wait for 250 ms...
[1] 01:36:13 INFO  StatisticsPage:325 - Trying to click the download button in the customer statistics...
[1] 01:36:13 INFO  Hook:209 - Wait for 250 ms...
[1] 01:36:14 INFO  StatisticsPage:325 - Trying to click the download button in the customer statistics...
[1] 01:36:14 INFO  Hook:179 - Test passed: Kundenstatistik
[1] 01:36:14 INFO  WindowControls:554 - Window list cleared!
[1] 01:36:14 INFO  Hook:189 - <<<<<<<End of test scenario: Kundenstatistik<<<<<<<
[1] 01:36:14 INFO  Hook:142 - >>>>>>>Start of test scenario: Dienstleistungsstatistik>>>>>>>
[1] 01:36:14 INFO  DriverUtil:132 - Test will be executed locally!
[1] 01:36:14 INFO  DriverUtil:217 - Proxy set
Mar 07, 2026 1:36:56 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Problem reading geckodriver versions: error sending request for url (https://raw.githubusercontent.com/SeleniumHQ/selenium/trunk/common/geckodriver/geckodriver-support.json). Using latest geckodriver version
Mar 07, 2026 1:36:56 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Exception managing firefox: error sending request for url (https://github.com/mozilla/geckodriver/releases/latest)
[1] 01:37:03 INFO  Hook:209 - Wait for 250 ms...
[1] 01:37:03 INFO  BasePage:1977 - Trying to navigate to "https://localhost:443/terminvereinbarung/statistic/"
[1] 01:37:25 INFO  BasePage:1984 - Waiting until page title is "Anmeldung - ZMS Statistik"
[1] 01:37:25 INFO  WindowControls:78 - Window added to the list: [0] Window{windowHandle='ad205f11-ef63-4e62-97d3-0d6139b7b572', windowTitle='Anmeldung - ZMS Statistik', windowType=ataf.web.model.WindowType@46a795de}
[1] 01:37:25 INFO  Hook:209 - Wait for 250 ms...
[1] 01:37:25 INFO  StatisticsPage:71 - Trying to click on "Login" button...
[1] 01:37:26 INFO  StatisticsPage:74 - SSO-Login page detected!
[1] 01:37:26 INFO  StatisticsPage:94 - Trying to enter user name...
[1] 01:37:26 INFO  StatisticsPage:97 - Trying to enter password...
[1] 01:37:26 INFO  StatisticsPage:100 - Trying to click on "Login" button (Keycloak)...
[1] 01:37:56 WARN  StatisticsPage:105 - SSO login navigation took longer than expected; proceeding with test anyway.
org.openqa.selenium.TimeoutException: Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [8663537f-0716-40a6-a0a7-e632c83b6b47, clickElement {id=94bef931-2fc7-4976-880b-665bb91aa914}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 920, moz:profile: /tmp/rust_mozprofileGG4nMj, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (8663537f-0716-40a6-a0a7-e632c83b6b47)] -> id: kc-login]
Session ID: 8663537f-0716-40a6-a0a7-e632c83b6b47
        at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:169) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:142) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:223) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:216) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:174) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:223) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebElement.click(RemoteWebElement.java:76) ~[selenium-remote-driver-4.40.0.jar:?]
        at ataf.web.pages.BasePage.lambda$clickOnWebElement$4(BasePage.java:867) ~[web-0.2.jar:?]
        at org.openqa.selenium.support.ui.FluentWait.until(FluentWait.java:207) ~[selenium-support-4.40.0.jar:?]
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:852) ~[web-0.2.jar:?]
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:912) ~[web-0.2.jar:?]
        at zms.ataf.ui.pages.statistics.StatisticsPage.clickOnLoginButton(StatisticsPage.java:102) ~[test-classes/:?]
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_in_der_statistik_auf_die_schaltflaeche_string_klicken(StatisticsSteps.java:60) ~[test-classes/:?]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
        at io.cucumber.java.Invoker.doInvoke(Invoker.java:66) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.Invoker.invoke(Invoker.java:24) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.AbstractGlueDefinition.invokeMethod(AbstractGlueDefinition.java:47) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.JavaStepDefinition.execute(JavaStepDefinition.java:29) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.core.runner.CoreStepDefinition.execute(CoreStepDefinition.java:66) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.PickleStepDefinitionMatch.runStep(PickleStepDefinitionMatch.java:62) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.ExecutionMode$1.execute(ExecutionMode.java:10) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestStep.executeStep(TestStep.java:81) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestStep.run(TestStep.java:53) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.PickleStepTestStep.run(PickleStepTestStep.java:55) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestCase.run(TestCase.java:87) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.Runner.runPickle(Runner.java:75) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.testng.TestNGCucumberRunner.lambda$runScenario$1(TestNGCucumberRunner.java:132) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at io.cucumber.core.runtime.CucumberExecutionContext.lambda$runTestCase$5(CucumberExecutionContext.java:136) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runtime.RethrowingThrowableCollector.executeAndThrow(RethrowingThrowableCollector.java:23) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runtime.CucumberExecutionContext.runTestCase(CucumberExecutionContext.java:136) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.testng.TestNGCucumberRunner.runScenario(TestNGCucumberRunner.java:129) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at io.cucumber.testng.AbstractTestNGCucumberTests.runScenario(AbstractTestNGCucumberTests.java:35) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
        at org.testng.internal.invokers.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:139) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeMethod(TestInvoker.java:664) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeTestMethod(TestInvoker.java:227) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.MethodRunner.runInSequence(MethodRunner.java:50) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:957) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeTestMethods(TestInvoker.java:200) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:148) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestMethodWorker.run(TestMethodWorker.java:128) ~[testng-7.8.0.jar:7.8.0]
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) [?:?]
        at org.testng.TestRunner.privateRun(TestRunner.java:848) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestRunner.run(TestRunner.java:621) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.runTest(SuiteRunner.java:443) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:437) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.privateRun(SuiteRunner.java:397) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.run(SuiteRunner.java:336) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:95) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuitesSequentially(TestNG.java:1280) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuitesLocally(TestNG.java:1200) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuites(TestNG.java:1114) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.run(TestNG.java:1082) [testng-7.8.0.jar:7.8.0]
        at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:155) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeSingleClass(TestNGDirectoryTestSuite.java:102) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:91) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:137) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495) [surefire-booter-3.5.4.jar:3.5.4]
[1] 01:37:56 INFO  Hook:209 - Wait for 250 ms...
[1] 01:37:56 INFO  StatisticsPage:125 - Trying to select location "Gewerbeamt (KVR-III/23) Verkehr"
[1] 01:37:57 INFO  BasePage:1613 - Text "Gewerbeamt (KVR-III/23) Verkehr" found in Dropdown
[1] 01:37:58 INFO  Hook:209 - Wait for 250 ms...
[1] 01:37:58 INFO  StatisticsPage:131 - Trying to click on "Auswahl best?tigen" button...
[1] 01:38:28 ERROR Hook:174 - Test failed: Dienstleistungsstatistik
[1] 01:38:28 INFO  WindowControls:554 - Window list cleared!
[1] 01:38:28 INFO  Hook:189 - <<<<<<<End of test scenario: Dienstleistungsstatistik<<<<<<<
[1] 01:38:28 INFO  Hook:68 - Hook: AfterAll (Framework-API)
[1] 01:38:28 INFO  AuthenticationHelper:125 - User name disposed!
[1] 01:38:28 INFO  AuthenticationHelper:132 - Password disposed!
[1] 01:38:28 WARN  AuthenticationHelper:143 - Authorization token cannot be disposed! It has not been set yet.
[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 291.6 s <<< FAILURE! -- in zms.ataf.runner.UiTestRunner
[ERROR] zms.ataf.runner.UiTestRunner.runScenario["Dienstleistungsstatistik", "Default"](2) -- Time elapsed: 134.3 s <<< FAILURE!
org.openqa.selenium.TimeoutException:
Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [8663537f-0716-40a6-a0a7-e632c83b6b47, clickElement {id=98e43e6a-875b-48f4-87f6-0baedcaf7b0b}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 920, moz:profile: /tmp/rust_mozprofileGG4nMj, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (8663537f-0716-40a6-a0a7-e632c83b6b47)] -> xpath: //button[@type='submit' and @value='weiter']]
Session ID: 8663537f-0716-40a6-a0a7-e632c83b6b47
        at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:169)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:142)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49)
        at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:223)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:216)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:174)
        at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601)
        at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:223)
        at org.openqa.selenium.remote.RemoteWebElement.click(RemoteWebElement.java:76)
        at ataf.web.pages.BasePage.lambda$clickOnWebElement$4(BasePage.java:867)
        at org.openqa.selenium.support.ui.FluentWait.until(FluentWait.java:207)
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:852)
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:899)
        at zms.ataf.ui.pages.statistics.StatisticsPage.clickOnApplySelectionButton(StatisticsPage.java:132)
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_in_der_statistik_auf_die_schaltflaeche_string_klicken(StatisticsSteps.java:63)
        at ?.Sie in der Statistik auf die Schaltfl?che "Auswahl best?tigen" klicken.(file:///var/www/html/zmsautomation/src/test/resources/features/ui/zmsstatistic/ZMS-1559.feature:10)

[INFO]
[INFO] Results:
[INFO]
[ERROR] Failures:
[ERROR]   UiTestRunner.runScenario ? Timeout Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [8663537f-0716-40a6-a0a7-e632c83b6b47, clickElement {id=98e43e6a-875b-48f4-87f6-0baedcaf7b0b}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 920, moz:profile: /tmp/rust_mozprofileGG4nMj, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (8663537f-0716-40a6-a0a7-e632c83b6b47)] -> xpath: //button[@type='submit' and @value='weiter']]
Session ID: 8663537f-0716-40a6-a0a7-e632c83b6b47
[INFO]
[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  04:54 min
[INFO] Finished at: 2026-03-07T01:38:29+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.5.4:test (default-test) on project zmsautomation: There are test failures.
[ERROR]
[ERROR] See /var/www/html/zmsautomation/target/surefire-reports for the individual test results.
[ERROR] See dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[SECTION] 12. Test Results
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[INFO] Collecting surefire reports...
total 268
drwxr-xr-x 5 root root  4096 Mar  7 01:38  .
drwxr-xr-x 9 root root 12288 Mar  7 01:38  ..
drwxr-xr-x 2 root root  4096 Mar  7 01:38 'Surefire suite'
-rw-r--r-- 1 root root 50009 Mar  7 01:38  TEST-zms.ataf.runner.UiTestRunner.xml
-rw-r--r-- 1 root root   356 Mar  7 01:38  bullet_point.png
-rw-r--r-- 1 root root   157 Mar  7 01:38  collapseall.gif
-rw-r--r-- 1 root root  5693 Mar  7 01:38  emailable-report.html
-rw-r--r-- 1 root root   977 Mar  7 01:38  failed.png
-rw-r--r-- 1 root root 19070 Mar  7 01:38  index.html
-rw-r--r-- 1 root root 89501 Mar  7 01:38  jquery-3.6.0.min.js
drwxr-xr-x 2 root root  4096 Mar  7 01:38  junitreports
-rw-r--r-- 1 root root   352 Mar  7 01:38  navigator-bullet.png
drwxr-xr-x 3 root root  4096 Mar  7 01:38  old
-rw-r--r-- 1 root root  1019 Mar  7 01:38  passed.png
-rw-r--r-- 1 root root   967 Mar  7 01:38  skipped.png
-rw-r--r-- 1 root root   712 Mar  7 01:38  testng-failed.xml
-rw-r--r-- 1 root root  5355 Mar  7 01:38  testng-reports.css
-rw-r--r-- 1 root root  3612 Mar  7 01:38  testng-reports.js
-rw-r--r-- 1 root root  7050 Mar  7 01:38  testng-reports1.css
-rw-r--r-- 1 root root  3618 Mar  7 01:38  testng-reports2.js
-rw-r--r-- 1 root root  8511 Mar  7 01:38  testng-results.xml
-rw-r--r-- 1 root root   303 Mar  7 01:38  testng.css
-rw-r--r-- 1 root root  3240 Mar  7 01:38  zms.ataf.runner.UiTestRunner.txt
==== /var/www/html/zmsautomation/target/surefire-reports/zms.ataf.runner.UiTestRunner.txt ====
-------------------------------------------------------------------------------
Test set: zms.ataf.runner.UiTestRunner
-------------------------------------------------------------------------------
Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 291.6 s <<< FAILURE! -- in zms.ataf.runner.UiTestRunner
zms.ataf.runner.UiTestRunner.runScenario["Dienstleistungsstatistik", "Default"](2) -- Time elapsed: 134.3 s <<< FAILURE!
org.openqa.selenium.TimeoutException:
Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [8663537f-0716-40a6-a0a7-e632c83b6b47, clickElement {id=98e43e6a-875b-48f4-87f6-0baedcaf7b0b}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 920, moz:profile: /tmp/rust_mozprofileGG4nMj, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (8663537f-0716-40a6-a0a7-e632c83b6b47)] -> xpath: //button[@type='submit' and @value='weiter']]
Session ID: 8663537f-0716-40a6-a0a7-e632c83b6b47
        at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:169)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:142)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49)
        at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:223)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:216)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:174)
        at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601)
        at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:223)
        at org.openqa.selenium.remote.RemoteWebElement.click(RemoteWebElement.java:76)
        at ataf.web.pages.BasePage.lambda$clickOnWebElement$4(BasePage.java:867)
        at org.openqa.selenium.support.ui.FluentWait.until(FluentWait.java:207)
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:852)
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:899)
        at zms.ataf.ui.pages.statistics.StatisticsPage.clickOnApplySelectionButton(StatisticsPage.java:132)
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_in_der_statistik_auf_die_schaltflaeche_string_klicken(StatisticsSteps.java:63)
        at ✽.Sie in der Statistik auf die Schaltfläche "Auswahl bestätigen" klicken.(file:///var/www/html/zmsautomation/src/test/resources/features/ui/zmsstatistic/ZMS-1559.feature:10)


[INFO] Cucumber reports available at:
[INFO]   - JSON: /var/www/html/zmsautomation/target/cucumber.json
[INFO]   - HTML: /var/www/html/zmsautomation/target/site/cucumber-pretty/index.html

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 2026

Tip

For best results, initiate chat on the files or code changes.

@ThomasAFink

Great progress — Kundenstatistik passes end‑to‑end now. The remaining failure is isolated to Dienstleistungsstatistik and consistently happens while clicking the location submit (“Auswahl bestätigen”/Weiter). The stack shows a 30s TimeoutException on clicking the submit button. This is almost always caused by one of:

  • the spinner is still overlaying the form when we click,
  • the control is rendered as either button or input with differing labels (“Weiter”, “Auswahl bestätigen”), and
  • the full-page navigation after submit takes longer than our local click wait.

Drop‑in hardening for StatisticsPage

  • Broaden the locator (button or input; “Weiter” or “Auswahl bestätigen” or value='weiter').
  • Ensure spinners are gone before clicking.
  • Scroll into view and try a normal click; if intercepted or slow, fall back to JS click.
  • After clicking, wait up to 60s for a stable post‑condition: either the overview heading, the sidebar links (“Kundenstatistik”/“Dienstleistungsstatistik”), or the sub‑page filter’s “Übernehmen” button.

Java patch (replace method + add two tiny helpers)

// In zms.ataf.ui.pages.statistics.StatisticsPage

import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;

// Helper: wait for any of the given locators to become visible/clickable
private WebElement waitFirstClickable(By[] locators, int seconds) {
    WebDriverWait wait = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
    for (By by : locators) {
        try {
            return wait.until(ExpectedConditions.elementToBeClickable(by));
        } catch (Exception ignored) {}
    }
    return null;
}
private boolean waitAnyVisible(By[] locators, int seconds) {
    WebDriverWait wait = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
    for (By by : locators) {
        try {
            wait.until(ExpectedConditions.presenceOfElementLocated(by));
            return true;
        } catch (Exception ignored) {}
    }
    return false;
}

public void clickOnApplySelectionButton() {
    ScenarioLogManager.getLogger().info("Trying to click on \"Auswahl bestätigen\" button...");

    // Make sure the context window is correct and spinners are gone
    CONTEXT.set();
    if (CONTEXT instanceof StatisticsPageContext s) {
        s.waitForSpinners();
    }

    // Support both <button> and <input type='submit'> variants and labels
    By[] buttonCandidates = new By[] {
        By.xpath("//button[`@type`='submit' and (normalize-space()='Weiter' or normalize-space()='Auswahl bestätigen')]"),
        By.xpath("//input[`@type`='submit' and (translate(`@value`,'WEITER','weiter')='weiter' or contains(translate(`@value`,'ÄAUSW','äausw'),'auswahl'))]"),
        By.cssSelector("form button[type='submit'], form input[type='submit']")
    };

    WebElement submit = waitFirstClickable(buttonCandidates, 20);
    Assert.assertNotNull(submit, "Could not find the statistics location submit button.");

    try {
        scrollToCenterByVisibleElement(submit);
        submit.click();
    } catch (Exception e) {
        ScenarioLogManager.getLogger().warn("Normal click failed (" + e.getClass().getSimpleName() + "). Falling back to JS click.");
        ((JavascriptExecutor) DRIVER).executeScript("arguments[0].click();", submit);
    }

    // After submit, wait for a stable post‑condition (overview, sidebar, or sub‑page filter)
    By[] postConditions = new By[] {
        By.xpath("//h1[contains(normalize-space(),'Übersicht')]"),
        By.xpath("//aside//a[contains(normalize-space(),'Kundenstatistik')]"),
        By.xpath("//aside//a[contains(normalize-space(),'Dienstleistungsstatistik')]"),
        By.xpath("//*[self::button or self::input][normalize-space()='Übernehmen' or translate(`@value`,'ÜBERNEHMEN','übernehmen')='übernehmen']")
    };
    boolean ok = waitAnyVisible(postConditions, 60);
    Assert.assertTrue(ok, "Could not navigate to statistics page after clicking \"Auswahl bestätigen\".");
}

Why this will unstick Dienstleistungsstatistik

  • We no longer depend on a single exact locator (“value='weiter'”).
  • We wait out the spinner and use a JS‑click fallback if the normal click is intercepted.
  • We wait for any of the valid “next state” signals instead of a single brittle breadcrumb.

If you want, I can also push the same “post‑click wait” pattern into clickOnLoginButton to turn the Keycloak 30s timeout into a warning consistently (we already do this partially).

On the earlier strategic question (expiring opening-hours test data)

  • Simplest to implement and maintain: freeze “today” in the test harness.
    • You already call zmsautomation-test; keep forcing a fixed “today” there (the script contains time mocking hooks). This makes all date-sensitive UI and SQL fixtures deterministic and avoids future drift.
    • Typical setup: export a single env var in zmsautomation-test (e.g., MOCK_TODAY=2026-03-06) and ensure the webapps read it (via Apache SetEnv or process env) to override now()/time() in the app’s “now provider.”
  • Alternative (works well too): make the Flyway opening-hours fixtures relative to CURDATE().
    • If you prefer data-only, change the oeffnungszeit inserts to use CURDATE() and DATE_ADD(CURDATE(), INTERVAL 30 DAY) for Datum_von/Datum_bis. This keeps availability fresh on every clean+migrate without needing app changes.
    • This is a low-risk SQL change and pairs nicely with time freezing (belt-and-suspenders).

Recommendation: freeze “today” in zmsautomation-test (fastest path, single knob), and also change the Flyway V10 opening-hours inserts to CURDATE()…DATE_ADD(CURDATE(), INTERVAL N DAY) to remove any residual drift. That combination has been robust in CI for similar suites.

Would you like me to stage the StatisticsPage.java change above and (optionally) a tiny Flyway tweak that replaces hardcoded opening-hour dates with CURDATE()/DATE_ADD()? If yes, I’ll prepare a minimal plan next.

@ThomasAFink
Copy link
Member Author

If you want, I can also push the same “post‑click wait” pattern into clickOnLoginButton to turn the Keycloak 30s timeout into a warning consistently (we already do this partially). The setup should be like in zmsadmin @coderabbitai

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 2026

Tip

For best results, initiate chat on the files or code changes.

@ThomasAFink thanks — yes, let me align StatisticsPage.clickOnLoginButton with the same post-click wait pattern we use in zmsadmin. Below is a drop‑in hardening that treats the 30s Keycloak navigation timeout as a warning if the post‑login page is already there, adds a JS‑click fallback, and waits for “good” post‑conditions (Standort select, “Auswahl bestätigen/Weiter”, or the statistics sidebar).

Java patch (StatisticsPage.java)

  • Keeps your current detection of the SSO page.
  • Wraps kc-login click with timeout handling like in zmsadmin.
  • Adds helpers to probe post‑login state and to wait for spinners to vanish.
// imports to add if missing
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;

// Replace/strengthen this method
public void clickOnLoginButton() throws Exception {
    ScenarioLogManager.getLogger().info("Trying to click on \"Login\" button (Keycloak)...");
    CONTEXT.set();

    // If we’re not yet on the SSO page, click the entry “Anmelden” on the Statistik landing
    if (!isWebElementVisible(5, "//*[`@id`='username' or `@id`='kc-login']", LocatorType.XPATH, false)) {
        // robust entry login (button or link)
        By[] entryCandidates = new By[] {
            By.xpath("//a[contains(normalize-space(),'Anmelden') or contains(.,'Login')]"),
            By.cssSelector("a.button--login, a[href*='keycloak'], a[href*='oidc']"),
            By.xpath("//button[contains(normalize-space(),'Anmelden') or contains(normalize-space(),'Login')]")
        };
        WebElement entry = waitFirstClickable(entryCandidates, 15);
        Assert.assertNotNull(entry, "Could not find the statistics SSO entry button/link.");
        scrollToCenterByVisibleElement(entry);
        try {
            entry.click();
        } catch (Exception e) {
            ((JavascriptExecutor) DRIVER).executeScript("arguments[0].click();", entry);
        }
    }

    // Now on Keycloak form: fill credentials
    enterTextInWebElement(DEFAULT_EXPLICIT_WAIT_TIME, "username", LocatorType.ID,
            TestPropertiesHelper.getString("userName"));
    enterTextInWebElement(DEFAULT_EXPLICIT_WAIT_TIME, "password", LocatorType.ID,
            TestPropertiesHelper.getString("userPassword"));

    WebElement submit = waitFirstClickable(new By[] { By.id("kc-login") }, 20);
    Assert.assertNotNull(submit, "Could not find Keycloak submit button (kc-login).");

    // Click + tolerate slow navigation: warn on timeout if post-login UI is present
    try {
        scrollToCenterByVisibleElement(submit);
        submit.click();
        waitAfterSsoSubmit(); // normal fast path
    } catch (TimeoutException te) {
        ScenarioLogManager.getLogger().warn("SSO login navigation took longer than expected; probing post-login state...");
        if (!waitForStatisticsHomeOrFilter(30)) {
            // still nowhere → propagate the failure
            throw te;
        }
    } catch (Exception e) {
        ScenarioLogManager.getLogger().warn("Normal click failed (" + e.getClass().getSimpleName() + "). Falling back to JS click.");
        ((JavascriptExecutor) DRIVER).executeScript("arguments[0].click();", submit);
        if (!waitForStatisticsHomeOrFilter(60)) {
            Assert.fail("Could not navigate to statistics page after SSO JS click.");
        }
    }
}

// Waits up to 60s for any “good” post-login signal in Statistik
private void waitAfterSsoSubmit() {
    Assert.assertTrue(waitForStatisticsHomeOrFilter(60),
            "Post-login statistics page did not appear in time.");
}

// Any-of post conditions: Standort select, submit button, or sidebar links
private boolean waitForStatisticsHomeOrFilter(int seconds) {
    WebDriverWait wait = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
    // Let overlays finish if present
    try { wait.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector(".spinner, div.spinner, .loading"))); }
    catch (Exception ignored) { }

    By[] goodSignals = new By[] {
        By.name("scope"), By.id("scope"),
        By.xpath("//button[normalize-space()='Auswahl bestätigen' or normalize-space()='Weiter']"),
        By.cssSelector("form button[type='submit'], form input[type='submit']"),
        By.xpath("//aside//a[contains(normalize-space(),'Kundenstatistik') or contains(normalize-space(),'Dienstleistungsstatistik')]")
    };
    for (By by : goodSignals) {
        try {
            wait.until(ExpectedConditions.presenceOfElementLocated(by));
            return true;
        } catch (Exception ignored) { }
    }
    return false;
}

// Small utility to mirror zmsadmin pattern
private WebElement waitFirstClickable(By[] locators, int seconds) {
    WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
    for (By by : locators) {
        try { return w.until(ExpectedConditions.elementToBeClickable(by)); }
        catch (Exception ignored) { }
    }
    return null;
}

Notes

  • This mirrors the zmsadmin approach: we handle kc-login click, accept a driver TimeoutException as non-fatal when the app already progressed, and rely on concrete post‑conditions instead of a single page title.
  • No XPath backticks; all attribute checks use @… syntax.
  • If you prefer, we can also extract the SSO flow into a tiny helper to keep AdminPage and StatisticsPage in sync.

Confirm and I’ll proceed to stage these updates (and, if you like, I can also port the same post‑click wait into clickOnApplySelectionButton for Dienstleistungsstatistik, but from your latest runs only the login path was still occasionally timing out).

  • Harden StatisticsPage.clickOnLoginButton with a post-click wait pattern aligned to zmsadmin; add JS‑click fallback and “any‑of” post‑conditions (scope select, submit, or sidebar).

✏️ Learnings added
Learnt from: ThomasAFink
URL: https://github.com/it-at-m/eappointment/pull/1892

Timestamp: 2026-03-07T00:50:11.738Z
Learning: In zmsautomation statistics UI tests, Keycloak login (kc-login) can trigger 30s navigation timeouts even when the app has already advanced. Like zmsadmin, StatisticsPage.clickOnLoginButton should treat this timeout as a warning and then wait for post‑login signals (scope select, “Auswahl bestätigen/Weiter”, or the statistics sidebar). Use JS‑click fallback and avoid brittle single-condition waits.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


💡 Use @coderabbitai plan to create a plan for code changes

…st-click wait and JS fallback

- Add waitForSpinners() to StatisticsPageContext (align with zmsadmin)
- clickOnLoginButton: waitFirstClickable for kc-login, JS fallback, post-login
  wait for scope/sidebar; treat Keycloak navigation timeout as non-fatal when
  post-condition appears
- clickOnApplySelectionButton: broad locators (Weiter/Auswahl bestätigen),
  wait for spinners, JS click fallback, wait up to 60s for overview/sidebar/Übernehmen
@ThomasAFink
Copy link
Member Author

@coderabbitai I don't understand why @zmsstatistic tests are so flacky compared to @zmsadmin where we don't have this issue with timeouts.

[SECTION] 11. Running ATAF Tests
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[INFO] Running Cucumber/ATAF tests with -Pataf profile...
[INFO] Note: You can filter tests by tags using: -Dcucumber.filter.tags=@tagName
[INFO] Example: bash zmsautomation-test -Dcucumber.filter.tags=@smoke
[INFO] Keycloak config rewritten (8091→443): /var/www/html/zmsstatistic/keycloak.json
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------< de.muenchen.eappointment:zmsautomation >---------------
[INFO] Building zmsautomation 0.1.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ zmsautomation ---
[INFO] Copying 18 resources from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.14.1:compile (default-compile) @ zmsautomation ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ zmsautomation ---
[INFO] Copying 42 resources from src/test/resources to target/test-classes
[INFO]
[INFO] --- compiler:3.14.1:testCompile (default-testCompile) @ zmsautomation ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 58 source files with javac [debug target 21] to target/test-classes
[INFO] Annotation processing is enabled because one or more processors were found
  on the class path. A future release of javac may disable annotation processing
  unless at least one processor is specified by name (-processor), or a search
  path is specified (--processor-path, --processor-module-path), or annotation
  processing is enabled explicitly (-proc:only, -proc:full).
  Use -Xlint:-options to suppress this message.
  Use -proc:none to disable annotation processing.
[INFO] /var/www/html/zmsautomation/src/test/java/zms/ataf/data/TestData.java: Some input files use or override a deprecated API.
[INFO] /var/www/html/zmsautomation/src/test/java/zms/ataf/data/TestData.java: Recompile with -Xlint:deprecation for details.
[INFO]
[INFO] --- surefire:3.5.4:test (default-test) @ zmsautomation ---
[INFO] Using configured provider org.apache.maven.surefire.testng.TestNGProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running zms.ataf.runner.UiTestRunner
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[1] 01:58:05 INFO  TestData:17 - Initializing ZMS API test environments
[1] 01:58:05 INFO  RunnerUtils:87 - Start of Test Suite!
[1] 01:58:05 INFO  TestProperties:36 - >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Start of loading properties>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[1] 01:58:05 INFO  TestProperties:150 - (string) Property [features] with value [src/test/resources/features] has been successfully loaded!
[1] 01:58:05 INFO  TestProperties:150 - (string) Property [userName] with value [ataf] has been successfully loaded!
[1] 01:58:05 INFO  TestProperties:150 - (string) Property [noProxy] with value [keycloak] has been successfully loaded!
[1] 01:58:05 INFO  TestProperties:150 - (string) Property [logLevel] with value [INFO] has been successfully loaded!
[1] 01:58:05 INFO  TestProperties:150 - (string) Property [publish.quiet] with value [true] has been successfully loaded!
[1] 01:58:05 INFO  TestProperties:150 - (string) Property [filter.tags] with value [@web and @zmsstatistic] has been successfully loaded!
[1] 01:58:05 INFO  TestProperties:142 - (string) Property [testDataEncryptionPassword] has been successfully loaded!
[1] 01:58:05 INFO  TestProperties:150 - (string) Property [plugin] with value [json:target/cucumber.json,html:target/site/cucumber-pretty] has been successfully loaded!
[1] 01:58:05 INFO  TestProperties:150 - (string) Property [glue] with value [zms.ataf.steps,zms.ataf.api.steps,zms.ataf.ui.steps,zms.ataf.hooks,ataf.rest.steps,ataf.web.steps] has been successfully loaded!
[1] 01:58:05 INFO  TestProperties:142 - (string) Property [userPassword] has been successfully loaded!
[1] 01:58:05 INFO  TestProperties:150 - (string) Property [publish.enabled] with value [false] has been successfully loaded!
[1] 01:58:05 INFO  TestProperties:150 - (string) Property [test.execution.test.environment] with value [LOCAL] has been successfully loaded!
[1] 01:58:05 INFO  TestProperties:63 - <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Finished loading of properties<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
[1] 01:58:05 INFO  RunnerUtils:116 - Log Level set to "INFO"
[1] 01:58:05 WARN  RunnerUtils:228 - Cannot create JQL: No property value for "issueKeys" or "filterId" has been provided. Also see: https://docs.getxray.app/display/XRAY/Exporting+Cucumber+Tests+-+REST
[1] 01:58:06 INFO  Hook:25 - Hook: BeforeAll (Framework-API)
[1] 01:58:06 INFO  Hook:142 - >>>>>>>Start of test scenario: Kundenstatistik>>>>>>>
[1] 01:58:06 INFO  DriverUtil:132 - Test will be executed locally!
[1] 01:58:06 INFO  DriverUtil:217 - Proxy set
Mar 07, 2026 1:58:48 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Problem reading geckodriver versions: error sending request for url (https://raw.githubusercontent.com/SeleniumHQ/selenium/trunk/common/geckodriver/geckodriver-support.json). Using latest geckodriver version
Mar 07, 2026 1:58:48 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Exception managing firefox: error sending request for url (https://github.com/mozilla/geckodriver/releases/latest)
[1] 01:58:56 INFO  Hook:209 - Wait for 250 ms...
[1] 01:58:56 INFO  BasePage:1977 - Trying to navigate to "https://localhost:443/terminvereinbarung/statistic/"
[1] 01:59:18 INFO  BasePage:1984 - Waiting until page title is "Anmeldung - ZMS Statistik"
[1] 01:59:18 INFO  WindowControls:78 - Window added to the list: [0] Window{windowHandle='0f9fd404-e230-4301-9640-8229c8e4ccbd', windowTitle='Anmeldung - ZMS Statistik', windowType=ataf.web.model.WindowType@43984213}
[1] 01:59:18 INFO  Hook:209 - Wait for 250 ms...
[1] 01:59:18 INFO  StatisticsPage:117 - Trying to click on "Login" button...
[1] 01:59:18 INFO  StatisticsPage:121 - SSO-Login page detected!
[1] 01:59:18 INFO  StatisticsPage:139 - Trying to enter user name...
[1] 01:59:18 INFO  StatisticsPage:142 - Trying to enter password...
[1] 01:59:19 INFO  StatisticsPage:145 - Trying to click on "Login" button (Keycloak)...
[1] 01:59:45 INFO  StatisticsPage:153 - SSO login submitted successfully.
[1] 01:59:45 INFO  Hook:209 - Wait for 250 ms...
[1] 01:59:45 INFO  StatisticsPage:184 - Trying to select location "Gewerbeamt (KVR-III/23) Verkehr"
[1] 01:59:46 INFO  BasePage:1613 - Text "Gewerbeamt (KVR-III/23) Verkehr" found in Dropdown
[1] 01:59:46 INFO  Hook:209 - Wait for 250 ms...
[1] 01:59:47 INFO  StatisticsPage:203 - Trying to click on "Auswahl best?tigen" button...
[1] 02:00:18 WARN  StatisticsPage:223 - Normal click failed (TimeoutException). Falling back to JS click.
[1] 02:00:18 ERROR Hook:174 - Test failed: Kundenstatistik
[1] 02:00:18 INFO  WindowControls:554 - Window list cleared!
[1] 02:00:18 INFO  Hook:189 - <<<<<<<End of test scenario: Kundenstatistik<<<<<<<
[1] 02:00:18 INFO  Hook:142 - >>>>>>>Start of test scenario: Dienstleistungsstatistik>>>>>>>
[1] 02:00:18 INFO  DriverUtil:132 - Test will be executed locally!
[1] 02:00:18 INFO  DriverUtil:217 - Proxy set
Mar 07, 2026 2:01:02 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Problem reading geckodriver versions: error sending request for url (https://raw.githubusercontent.com/SeleniumHQ/selenium/trunk/common/geckodriver/geckodriver-support.json). Using latest geckodriver version
Mar 07, 2026 2:01:02 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Exception managing firefox: error sending request for url (https://github.com/mozilla/geckodriver/releases/latest)
[1] 02:01:09 INFO  Hook:209 - Wait for 250 ms...
[1] 02:01:09 INFO  BasePage:1977 - Trying to navigate to "https://localhost:443/terminvereinbarung/statistic/"
[1] 02:01:31 INFO  BasePage:1984 - Waiting until page title is "Anmeldung - ZMS Statistik"
[1] 02:01:31 INFO  WindowControls:78 - Window added to the list: [0] Window{windowHandle='b49acdb7-9657-4347-a760-a2c46936ef66', windowTitle='Anmeldung - ZMS Statistik', windowType=ataf.web.model.WindowType@4735d6e5}
[1] 02:01:31 INFO  Hook:209 - Wait for 250 ms...
[1] 02:01:31 INFO  StatisticsPage:117 - Trying to click on "Login" button...
[1] 02:01:31 INFO  StatisticsPage:121 - SSO-Login page detected!
[1] 02:01:31 INFO  StatisticsPage:139 - Trying to enter user name...
[1] 02:01:32 INFO  StatisticsPage:142 - Trying to enter password...
[1] 02:01:32 INFO  StatisticsPage:145 - Trying to click on "Login" button (Keycloak)...
[1] 02:02:02 WARN  StatisticsPage:155 - SSO login navigation took longer than expected; probing post-login state...
org.openqa.selenium.TimeoutException: Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [fe67ab7c-639e-4fc3-a807-9a584e131a74, clickElement {id=0f9de569-34de-47ee-a791-3f621a4629e0}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 2368, moz:profile: /tmp/rust_mozprofilecji5vX, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (fe67ab7c-639e-4fc3-a807-9a584e131a74)] -> id: kc-login]
Session ID: fe67ab7c-639e-4fc3-a807-9a584e131a74
        at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:169) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:142) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:223) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:216) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:174) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:223) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebElement.click(RemoteWebElement.java:76) ~[selenium-remote-driver-4.40.0.jar:?]
        at zms.ataf.ui.pages.statistics.StatisticsPage.clickOnLoginButton(StatisticsPage.java:151) ~[test-classes/:?]
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_in_der_statistik_auf_die_schaltflaeche_string_klicken(StatisticsSteps.java:59) ~[test-classes/:?]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
        at io.cucumber.java.Invoker.doInvoke(Invoker.java:66) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.Invoker.invoke(Invoker.java:24) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.AbstractGlueDefinition.invokeMethod(AbstractGlueDefinition.java:47) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.JavaStepDefinition.execute(JavaStepDefinition.java:29) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.core.runner.CoreStepDefinition.execute(CoreStepDefinition.java:66) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.PickleStepDefinitionMatch.runStep(PickleStepDefinitionMatch.java:62) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.ExecutionMode$1.execute(ExecutionMode.java:10) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestStep.executeStep(TestStep.java:81) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestStep.run(TestStep.java:53) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.PickleStepTestStep.run(PickleStepTestStep.java:55) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestCase.run(TestCase.java:87) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.Runner.runPickle(Runner.java:75) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.testng.TestNGCucumberRunner.lambda$runScenario$1(TestNGCucumberRunner.java:132) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at io.cucumber.core.runtime.CucumberExecutionContext.lambda$runTestCase$5(CucumberExecutionContext.java:136) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runtime.RethrowingThrowableCollector.executeAndThrow(RethrowingThrowableCollector.java:23) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runtime.CucumberExecutionContext.runTestCase(CucumberExecutionContext.java:136) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.testng.TestNGCucumberRunner.runScenario(TestNGCucumberRunner.java:129) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at io.cucumber.testng.AbstractTestNGCucumberTests.runScenario(AbstractTestNGCucumberTests.java:35) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
        at org.testng.internal.invokers.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:139) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeMethod(TestInvoker.java:664) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeTestMethod(TestInvoker.java:227) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.MethodRunner.runInSequence(MethodRunner.java:50) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:957) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeTestMethods(TestInvoker.java:200) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:148) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestMethodWorker.run(TestMethodWorker.java:128) ~[testng-7.8.0.jar:7.8.0]
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) [?:?]
        at org.testng.TestRunner.privateRun(TestRunner.java:848) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestRunner.run(TestRunner.java:621) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.runTest(SuiteRunner.java:443) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:437) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.privateRun(SuiteRunner.java:397) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.run(SuiteRunner.java:336) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:95) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuitesSequentially(TestNG.java:1280) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuitesLocally(TestNG.java:1200) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuites(TestNG.java:1114) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.run(TestNG.java:1082) [testng-7.8.0.jar:7.8.0]
        at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:155) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeSingleClass(TestNGDirectoryTestSuite.java:102) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:91) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:137) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495) [surefire-booter-3.5.4.jar:3.5.4]
[1] 02:02:02 INFO  StatisticsPage:160 - SSO login completed (post-condition detected).
[1] 02:02:02 INFO  Hook:209 - Wait for 250 ms...
[1] 02:02:03 INFO  StatisticsPage:184 - Trying to select location "Gewerbeamt (KVR-III/23) Verkehr"
[1] 02:02:04 INFO  BasePage:1613 - Text "Gewerbeamt (KVR-III/23) Verkehr" found in Dropdown
[1] 02:02:04 INFO  Hook:209 - Wait for 250 ms...
[1] 02:02:04 INFO  StatisticsPage:203 - Trying to click on "Auswahl best?tigen" button...
[1] 02:02:31 INFO  Hook:209 - Wait for 250 ms...
[1] 02:02:31 INFO  StatisticsPage:238 - Checking if the overview page is visible.
[1] 02:02:31 INFO  Hook:209 - Wait for 250 ms...
[1] 02:02:31 INFO  StatisticsPage:262 - Trying to click on "Dienstleistungsstatistik" button in the sidebar...
[1] 02:02:58 INFO  Hook:209 - Wait for 250 ms...
[1] 02:02:58 INFO  StatisticsPage:252 - Checking if the Dienstleistungsstatistik statistics page is visible.
[1] 02:02:58 INFO  Hook:209 - Wait for 250 ms...
[1] 02:02:59 INFO  StatisticsPage:267 - Attempting to apply Standort filter on statistics sub-page with value: "Gewerbeamt (KVR-III/23) Verkehr"
[1] 02:02:59 INFO  StatisticsPage:277 - Location/date filter panel detected. Applying location for statistics sub-page...
[1] 02:02:59 INFO  StatisticsPage:285 - Selecting statistics location in multi-select scope picker: Gewerbeamt (KVR-III/23) Verkehr
[1] 02:03:00 INFO  StatisticsPage:309 - Location "Gewerbeamt (KVR-III/23) Verkehr" selected in scope-select.
[1] 02:03:00 INFO  Hook:209 - Wait for 250 ms...
[1] 02:03:00 INFO  StatisticsPage:321 - Attempting to apply date range filter on statistics sub-page...
[1] 02:03:00 INFO  StatisticsPage:333 - Setting statistics date range from 2026-02-21 to 2026-03-07
[1] 02:03:00 INFO  StatisticsPage:361 - Date input 'from' set to 2026-02-21
[1] 02:03:00 INFO  StatisticsPage:361 - Date input 'to' set to 2026-03-07
[1] 02:03:00 INFO  StatisticsPage:337 - Submitting statistics filter with ?bernehmen button via JavaScript click...
[1] 02:03:00 INFO  Hook:209 - Wait for 250 ms...
[1] 02:03:01 INFO  ServiceStatisticsPage:64 - Checking that service statistics table is visible.
[1] 02:03:01 INFO  ServiceStatisticsPage:70 - Checking that service row is listed: G?terkraftverkehr ? Erlaubnis und Lizenz
[1] 02:03:03 INFO  ServiceStatisticsPage:70 - Checking that service row is listed: Taxi oder Mietwagen ? Unterlagen nachreichen
[1] 02:03:04 INFO  ServiceStatisticsPage:70 - Checking that service row is listed: Zulassung Taxi oder Mietwagen
[1] 02:03:04 INFO  Hook:209 - Wait for 250 ms...
[1] 02:03:04 INFO  StatisticsPage:427 - Trying to click the download button in the customer statistics...
[1] 02:03:04 INFO  Hook:209 - Wait for 250 ms...
[1] 02:03:05 INFO  StatisticsPage:427 - Trying to click the download button in the customer statistics...
[1] 02:03:05 INFO  Hook:179 - Test passed: Dienstleistungsstatistik
[1] 02:03:05 INFO  WindowControls:554 - Window list cleared!
[1] 02:03:05 INFO  Hook:189 - <<<<<<<End of test scenario: Dienstleistungsstatistik<<<<<<<
[1] 02:03:05 INFO  Hook:68 - Hook: AfterAll (Framework-API)
[1] 02:03:05 INFO  AuthenticationHelper:125 - User name disposed!
[1] 02:03:05 INFO  AuthenticationHelper:132 - Password disposed!
[1] 02:03:05 WARN  AuthenticationHelper:143 - Authorization token cannot be disposed! It has not been set yet.
[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 300.6 s <<< FAILURE! -- in zms.ataf.runner.UiTestRunner
[ERROR] zms.ataf.runner.UiTestRunner.runScenario["Kundenstatistik", "Aufbau ZMS-Testautomatisierung"](1) -- Time elapsed: 132.3 s <<< FAILURE!
org.openqa.selenium.StaleElementReferenceException:
The element with the reference 2c5cc3bb-3c69-4225-aa45-8313187c8aa4 is stale; either its node document is not the active document, or it is no longer connected to the DOM
For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#staleelementreferenceexception
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [421130ed-e57f-44d1-b531-aa9174b5e9d3, executeScript {args=[{element-6066-11e4-a52e-4f735466cecf=2c5cc3bb-3c69-4225-aa45-8313187c8aa4}], script=arguments[0].click();}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 1891, moz:profile: /tmp/rust_mozprofilePvgOn7, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Session ID: 421130ed-e57f-44d1-b531-aa9174b5e9d3
        at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:169)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:142)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49)
        at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:223)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:216)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:174)
        at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601)
        at org.openqa.selenium.remote.RemoteWebDriver.executeScript(RemoteWebDriver.java:532)
        at zms.ataf.ui.pages.statistics.StatisticsPage.clickOnApplySelectionButton(StatisticsPage.java:224)
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_in_der_statistik_auf_die_schaltflaeche_string_klicken(StatisticsSteps.java:62)
        at ?.Sie in der Statistik auf die Schaltfl?che "Auswahl best?tigen" klicken.(file:///var/www/html/zmsautomation/src/test/resources/features/ui/zmsstatistic/ZMS-1558.feature:10)

[INFO]
[INFO] Results:
[INFO]
[ERROR] Failures:
[ERROR]   UiTestRunner.runScenario ? StaleElementReference The element with the reference 2c5cc3bb-3c69-4225-aa45-8313187c8aa4 is stale; either its node document is not the active document, or it is no longer connected to the DOM
For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#staleelementreferenceexception
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [421130ed-e57f-44d1-b531-aa9174b5e9d3, executeScript {args=[{element-6066-11e4-a52e-4f735466cecf=2c5cc3bb-3c69-4225-aa45-8313187c8aa4}], script=arguments[0].click();}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 1891, moz:profile: /tmp/rust_mozprofilePvgOn7, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Session ID: 421130ed-e57f-44d1-b531-aa9174b5e9d3
[INFO]
[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  05:05 min
[INFO] Finished at: 2026-03-07T02:03:05+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.5.4:test (default-test) on project zmsautomation: There are test failures.
[ERROR]
[ERROR] See /var/www/html/zmsautomation/target/surefire-reports for the individual test results.
[ERROR] See dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[SECTION] 12. Test Results
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[INFO] Collecting surefire reports...
total 268
drwxr-xr-x 5 root root  4096 Mar  7 02:03  .
drwxr-xr-x 9 root root 12288 Mar  7 02:03  ..
drwxr-xr-x 2 root root  4096 Mar  7 02:03 'Surefire suite'
-rw-r--r-- 1 root root 49402 Mar  7 02:03  TEST-zms.ataf.runner.UiTestRunner.xml
-rw-r--r-- 1 root root   356 Mar  7 02:03  bullet_point.png
-rw-r--r-- 1 root root   157 Mar  7 02:03  collapseall.gif
-rw-r--r-- 1 root root  5551 Mar  7 02:03  emailable-report.html
-rw-r--r-- 1 root root   977 Mar  7 02:03  failed.png
-rw-r--r-- 1 root root 18928 Mar  7 02:03  index.html
-rw-r--r-- 1 root root 89501 Mar  7 02:03  jquery-3.6.0.min.js
drwxr-xr-x 2 root root  4096 Mar  7 02:03  junitreports
-rw-r--r-- 1 root root   352 Mar  7 02:03  navigator-bullet.png
drwxr-xr-x 3 root root  4096 Mar  7 02:03  old
-rw-r--r-- 1 root root  1019 Mar  7 02:03  passed.png
-rw-r--r-- 1 root root   967 Mar  7 02:03  skipped.png
-rw-r--r-- 1 root root   712 Mar  7 02:03  testng-failed.xml
-rw-r--r-- 1 root root  5355 Mar  7 02:03  testng-reports.css
-rw-r--r-- 1 root root  3612 Mar  7 02:03  testng-reports.js
-rw-r--r-- 1 root root  7050 Mar  7 02:03  testng-reports1.css
-rw-r--r-- 1 root root  3618 Mar  7 02:03  testng-reports2.js
-rw-r--r-- 1 root root  8645 Mar  7 02:03  testng-results.xml
-rw-r--r-- 1 root root   303 Mar  7 02:03  testng.css
-rw-r--r-- 1 root root  3135 Mar  7 02:03  zms.ataf.runner.UiTestRunner.txt
==== /var/www/html/zmsautomation/target/surefire-reports/zms.ataf.runner.UiTestRunner.txt ====
-------------------------------------------------------------------------------
Test set: zms.ataf.runner.UiTestRunner
-------------------------------------------------------------------------------
Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 300.6 s <<< FAILURE! -- in zms.ataf.runner.UiTestRunner
zms.ataf.runner.UiTestRunner.runScenario["Kundenstatistik", "Aufbau ZMS-Testautomatisierung"](1) -- Time elapsed: 132.3 s <<< FAILURE!
org.openqa.selenium.StaleElementReferenceException:
The element with the reference 2c5cc3bb-3c69-4225-aa45-8313187c8aa4 is stale; either its node document is not the active document, or it is no longer connected to the DOM
For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#staleelementreferenceexception
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [421130ed-e57f-44d1-b531-aa9174b5e9d3, executeScript {args=[{element-6066-11e4-a52e-4f735466cecf=2c5cc3bb-3c69-4225-aa45-8313187c8aa4}], script=arguments[0].click();}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 1891, moz:profile: /tmp/rust_mozprofilePvgOn7, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Session ID: 421130ed-e57f-44d1-b531-aa9174b5e9d3
        at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:169)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:142)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49)
        at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:223)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:216)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:174)
        at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601)
        at org.openqa.selenium.remote.RemoteWebDriver.executeScript(RemoteWebDriver.java:532)
        at zms.ataf.ui.pages.statistics.StatisticsPage.clickOnApplySelectionButton(StatisticsPage.java:224)
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_in_der_statistik_auf_die_schaltflaeche_string_klicken(StatisticsSteps.java:62)
        at ✽.Sie in der Statistik auf die Schaltfläche "Auswahl bestätigen" klicken.(file:///var/www/html/zmsautomation/src/test/resources/features/ui/zmsstatistic/ZMS-1558.feature:10)


@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 2026

Tip

For best results, initiate chat on the files or code changes.

@ThomasAFink Thanks for the detailed logs — they make the pattern clear.

Why @zmsstatistic is flakier than @zmsadmin

  • Extra full-page transition: After you open “Kundenstatistik” or “Dienstleistungsstatistik”, the app shows a Standort + Datum filter with an “Übernehmen/Weiter” submit. Submitting it triggers a full page reload. That extra reload doesn’t exist in most @zmsadmin flows and is exactly where you see 30s timeouts and stale elements.
  • Aggressive re-rendering: The statistics landing and subpages load content via includes after the main document is “complete.” Buttons you just located can be replaced by the time you click them → StaleElementReferenceException.
  • External network in test steps: Only the statistics tests call get.api-feiertage.de (HolidayUtil) to compute “previous business day.” When the PAC/proxy can’t resolve (flaucherint.muenchen.de), the step stalls. @zmsadmin doesn’t make that call.
  • Miscellaneous external calls: plausible.io and SeleniumManager attempting to resolve geckodriver add noise/latency. @zmsadmin usually completes critical interactions before those show up, so you don’t notice them.

Two quick hardenings that remove the remaining flakes

  1. Make “Auswahl bestätigen/Weiter” click anti-stale with retries and a strong post-condition
  • Re‑locate the submit control each attempt.
  • Click (scroll + normal click; JS fallback).
  • After click: wait for either stalenessOf(oldElement) or “good” next page signals (overview heading, sidebar links, or Übernehmen in the subpage).
  • Give it up to 3 attempts before failing.

Java patch (StatisticsPage.java)

// imports (if missing)
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;

// Replaces existing clickOnApplySelectionButton()
public void clickOnApplySelectionButton() {
    ScenarioLogManager.getLogger().info("Trying to click on \"Auswahl bestätigen\" button...");
    CONTEXT.set();
    if (CONTEXT instanceof StatisticsPageContext s) {
        s.waitForSpinners();
    }

    By[] submitCandidates = new By[] {
        By.xpath("//button[`@type`='submit' and (normalize-space()='Weiter' or normalize-space()='Auswahl bestätigen')]"),
        By.xpath("//input[`@type`='submit' and (translate(`@value`,'WEITER','weiter')='weiter' "
               + "or contains(translate(`@value`,'ÄAUSW','äausw'),'auswahl'))]"),
        By.cssSelector("form button[type='submit'], form input[type='submit']")
    };

    WebDriverWait wait = new WebDriverWait(DRIVER, Duration.ofSeconds(DEFAULT_EXPLICIT_WAIT_TIME));
    int attempts = 0;
    while (attempts++ < 3) {
        try {
            WebElement submit = waitFirstClickable(submitCandidates, DEFAULT_EXPLICIT_WAIT_TIME);
            Assert.assertNotNull(submit, "Could not find the statistics location submit button.");

            scrollToCenterByVisibleElement(submit);

            // pre-click: ensure overlays gone
            try { wait.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector(".spinner, div.spinner, .loading"))); }
            catch (Exception ignored) {}

            try {
                submit.click();
            } catch (Exception e) {
                ScenarioLogManager.getLogger().warn("Normal click failed (" + e.getClass().getSimpleName() + "). Falling back to JS click.");
                ((JavascriptExecutor) DRIVER).executeScript("arguments[0].click();", submit);
            }

            // post-click: either element becomes stale OR we see a next-state signal
            boolean navigated = false;
            try {
                new WebDriverWait(DRIVER, Duration.ofSeconds(10))
                    .until(ExpectedConditions.stalenessOf(submit));
                navigated = true;
            } catch (Exception ignored) {
                navigated = waitForNextStatisticsState(60);
            }

            if (navigated) return;
        } catch (StaleElementReferenceException sere) {
            ScenarioLogManager.getLogger().warn("Submit element went stale; retrying (" + attempts + "/3)...");
        } catch (TimeoutException te) {
            ScenarioLogManager.getLogger().warn("Timeout waiting around submit; retrying (" + attempts + "/3)...");
        }
    }
    Assert.fail("Could not proceed after clicking \"Auswahl bestätigen\" (retries exhausted).");
}

private WebElement waitFirstClickable(By[] locators, int seconds) {
    WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
    for (By by : locators) {
        try { return w.until(ExpectedConditions.elementToBeClickable(by)); }
        catch (Exception ignored) {}
    }
    return null;
}

private boolean waitForNextStatisticsState(int seconds) {
    WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
    By[] good = new By[] {
        // overview or sidebar ready
        By.xpath("//h1[contains(normalize-space(),'Übersicht')]"),
        By.xpath("//aside//a[contains(normalize-space(),'Kundenstatistik') or contains(normalize-space(),'Dienstleistungsstatistik')]"),
        // sub-page filter present (Übernehmen visible)
        By.xpath("//*[self::button or self::input][normalize-space()='Übernehmen' "
               + "or translate(`@value`,'ÜBERNEHMEN','übernehmen')='übernehmen']")
    };
    for (By by : good) {
        try { w.until(ExpectedConditions.presenceOfElementLocated(by)); return true; }
        catch (Exception ignored) {}
    }
    return false;
}
  1. Remove the network dependency for “previous business day”
  • The PAC/holiday API lookup is a common stall. Provide a local fallback that kicks in automatically when the call fails or when a flag is set.
  • Simple rule that aligns with our seeded data: use “yesterday”; if today is Monday, use “previous Friday”. You can keep the online path for CI if you want it.

Java patch (StatisticsSteps.java)

// Add helper
private String computePreviousBusinessDayLocally() {
    ZoneId tz = ZoneId.of("Europe/Berlin");
    LocalDate d = LocalDate.now(tz).minusDays(1);
    if (d.getDayOfWeek() == DayOfWeek.SUNDAY) d = d.minusDays(2);
    if (d.getDayOfWeek() == DayOfWeek.SATURDAY) d = d.minusDays(1);
    return d.format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
}

// In zeige_kunden_statistik_fuer_vorherigen_tag(...)
String useOffline = System.getProperty("zms.stat.offlineHolidays",
                    System.getenv().getOrDefault("ZMS_STAT_OFFLINE_HOLIDAYS", "true"));
String date;
if ("true".equalsIgnoreCase(useOffline)) {
    date = computePreviousBusinessDayLocally();
} else {
    try {
        date = HolidayUtil.getPreviousBusinessDay("by") // existing
               .format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
    } catch (Exception ex) {
        ScenarioLogManager.getLogger().warn("Holiday API failed; falling back to local previous business day. Cause: " + ex.getMessage());
        date = computePreviousBusinessDayLocally();
    }
}
// then use 'date' to drive the assertions

Low‑effort environment tweaks that also help

  • Pin geckodriver to avoid SeleniumManager’s network calls:
    • In zmsautomation-test add -Dwebdriver.gecko.driver=/usr/local/bin/geckodriver to the Maven run (or export WEBDRIVER_GECKO_DRIVER and pass it through MAVEN_OPTS). This stops Selenium from trying to look up versions online.
  • Keep the new SSO post‑click wait in StatisticsPage (aligned to zmsadmin). That turned the kc-login 30s timeout into a warning and continued — exactly what we want for CI stability.

Would you like me to stage the two code changes above? They’re small and should eliminate the remaining flakes:

  • retrying anti‑stale submit click, and

  • offline business‑day fallback.

  • Harden zmsautomation/src/test/java/zms/ataf/ui/pages/statistics/StatisticsPage.java:

    • Replace clickOnApplySelectionButton() with a 3‑attempt anti‑stale click (re‑locate each time), pre/post spinner waits, JS‑click fallback, and post‑conditions (overview/sidebar/Übernehmen) or stalenessOf.
    • Add helpers waitFirstClickable(...) and waitForNextStatisticsState(...).
  • Make zmsautomation/src/test/java/zms/ataf/ui/steps/StatisticsSteps.java resilient to holiday API failures:

    • Add computePreviousBusinessDayLocally() and prefer it when ZMS_STAT_OFFLINE_HOLIDAYS=true (default), or on any exception from HolidayUtil.
  • Optional (script, not code): In zmsautomation-test, pass -Dwebdriver.gecko.driver=/usr/local/bin/geckodriver to mvn test to avoid SeleniumManager network lookups.


💡 Use @coderabbitai plan to create a plan for code changes

@ThomasAFink
Copy link
Member Author

Tip

For best results, initiate chat on the files or code changes.

@ThomasAFink Thanks for the detailed logs — they make the pattern clear.

Why @zmsstatistic is flakier than @zmsadmin

  • Extra full-page transition: After you open “Kundenstatistik” or “Dienstleistungsstatistik”, the app shows a Standort + Datum filter with an “Übernehmen/Weiter” submit. Submitting it triggers a full page reload. That extra reload doesn’t exist in most @zmsadmin flows and is exactly where you see 30s timeouts and stale elements.
  • Aggressive re-rendering: The statistics landing and subpages load content via includes after the main document is “complete.” Buttons you just located can be replaced by the time you click them → StaleElementReferenceException.
  • External network in test steps: Only the statistics tests call get.api-feiertage.de (HolidayUtil) to compute “previous business day.” When the PAC/proxy can’t resolve (flaucherint.muenchen.de), the step stalls. @zmsadmin doesn’t make that call.
  • Miscellaneous external calls: plausible.io and SeleniumManager attempting to resolve geckodriver add noise/latency. @zmsadmin usually completes critical interactions before those show up, so you don’t notice them.

Two quick hardenings that remove the remaining flakes

  1. Make “Auswahl bestätigen/Weiter” click anti-stale with retries and a strong post-condition
  • Re‑locate the submit control each attempt.
  • Click (scroll + normal click; JS fallback).
  • After click: wait for either stalenessOf(oldElement) or “good” next page signals (overview heading, sidebar links, or Übernehmen in the subpage).
  • Give it up to 3 attempts before failing.

Java patch (StatisticsPage.java)

// imports (if missing)
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;

// Replaces existing clickOnApplySelectionButton()
public void clickOnApplySelectionButton() {
    ScenarioLogManager.getLogger().info("Trying to click on \"Auswahl bestätigen\" button...");
    CONTEXT.set();
    if (CONTEXT instanceof StatisticsPageContext s) {
        s.waitForSpinners();
    }

    By[] submitCandidates = new By[] {
        By.xpath("//button[`@type`='submit' and (normalize-space()='Weiter' or normalize-space()='Auswahl bestätigen')]"),
        By.xpath("//input[`@type`='submit' and (translate(`@value`,'WEITER','weiter')='weiter' "
               + "or contains(translate(`@value`,'ÄAUSW','äausw'),'auswahl'))]"),
        By.cssSelector("form button[type='submit'], form input[type='submit']")
    };

    WebDriverWait wait = new WebDriverWait(DRIVER, Duration.ofSeconds(DEFAULT_EXPLICIT_WAIT_TIME));
    int attempts = 0;
    while (attempts++ < 3) {
        try {
            WebElement submit = waitFirstClickable(submitCandidates, DEFAULT_EXPLICIT_WAIT_TIME);
            Assert.assertNotNull(submit, "Could not find the statistics location submit button.");

            scrollToCenterByVisibleElement(submit);

            // pre-click: ensure overlays gone
            try { wait.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector(".spinner, div.spinner, .loading"))); }
            catch (Exception ignored) {}

            try {
                submit.click();
            } catch (Exception e) {
                ScenarioLogManager.getLogger().warn("Normal click failed (" + e.getClass().getSimpleName() + "). Falling back to JS click.");
                ((JavascriptExecutor) DRIVER).executeScript("arguments[0].click();", submit);
            }

            // post-click: either element becomes stale OR we see a next-state signal
            boolean navigated = false;
            try {
                new WebDriverWait(DRIVER, Duration.ofSeconds(10))
                    .until(ExpectedConditions.stalenessOf(submit));
                navigated = true;
            } catch (Exception ignored) {
                navigated = waitForNextStatisticsState(60);
            }

            if (navigated) return;
        } catch (StaleElementReferenceException sere) {
            ScenarioLogManager.getLogger().warn("Submit element went stale; retrying (" + attempts + "/3)...");
        } catch (TimeoutException te) {
            ScenarioLogManager.getLogger().warn("Timeout waiting around submit; retrying (" + attempts + "/3)...");
        }
    }
    Assert.fail("Could not proceed after clicking \"Auswahl bestätigen\" (retries exhausted).");
}

private WebElement waitFirstClickable(By[] locators, int seconds) {
    WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
    for (By by : locators) {
        try { return w.until(ExpectedConditions.elementToBeClickable(by)); }
        catch (Exception ignored) {}
    }
    return null;
}

private boolean waitForNextStatisticsState(int seconds) {
    WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
    By[] good = new By[] {
        // overview or sidebar ready
        By.xpath("//h1[contains(normalize-space(),'Übersicht')]"),
        By.xpath("//aside//a[contains(normalize-space(),'Kundenstatistik') or contains(normalize-space(),'Dienstleistungsstatistik')]"),
        // sub-page filter present (Übernehmen visible)
        By.xpath("//*[self::button or self::input][normalize-space()='Übernehmen' "
               + "or translate(`@value`,'ÜBERNEHMEN','übernehmen')='übernehmen']")
    };
    for (By by : good) {
        try { w.until(ExpectedConditions.presenceOfElementLocated(by)); return true; }
        catch (Exception ignored) {}
    }
    return false;
}
  1. Remove the network dependency for “previous business day”
  • The PAC/holiday API lookup is a common stall. Provide a local fallback that kicks in automatically when the call fails or when a flag is set.
  • Simple rule that aligns with our seeded data: use “yesterday”; if today is Monday, use “previous Friday”. You can keep the online path for CI if you want it.

Java patch (StatisticsSteps.java)

// Add helper
private String computePreviousBusinessDayLocally() {
    ZoneId tz = ZoneId.of("Europe/Berlin");
    LocalDate d = LocalDate.now(tz).minusDays(1);
    if (d.getDayOfWeek() == DayOfWeek.SUNDAY) d = d.minusDays(2);
    if (d.getDayOfWeek() == DayOfWeek.SATURDAY) d = d.minusDays(1);
    return d.format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
}

// In zeige_kunden_statistik_fuer_vorherigen_tag(...)
String useOffline = System.getProperty("zms.stat.offlineHolidays",
                    System.getenv().getOrDefault("ZMS_STAT_OFFLINE_HOLIDAYS", "true"));
String date;
if ("true".equalsIgnoreCase(useOffline)) {
    date = computePreviousBusinessDayLocally();
} else {
    try {
        date = HolidayUtil.getPreviousBusinessDay("by") // existing
               .format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
    } catch (Exception ex) {
        ScenarioLogManager.getLogger().warn("Holiday API failed; falling back to local previous business day. Cause: " + ex.getMessage());
        date = computePreviousBusinessDayLocally();
    }
}
// then use 'date' to drive the assertions

Low‑effort environment tweaks that also help

  • Pin geckodriver to avoid SeleniumManager’s network calls:

    • In zmsautomation-test add -Dwebdriver.gecko.driver=/usr/local/bin/geckodriver to the Maven run (or export WEBDRIVER_GECKO_DRIVER and pass it through MAVEN_OPTS). This stops Selenium from trying to look up versions online.
  • Keep the new SSO post‑click wait in StatisticsPage (aligned to zmsadmin). That turned the kc-login 30s timeout into a warning and continued — exactly what we want for CI stability.

Would you like me to stage the two code changes above? They’re small and should eliminate the remaining flakes:

  • retrying anti‑stale submit click, and

  • offline business‑day fallback.

  • Harden zmsautomation/src/test/java/zms/ataf/ui/pages/statistics/StatisticsPage.java:

    • Replace clickOnApplySelectionButton() with a 3‑attempt anti‑stale click (re‑locate each time), pre/post spinner waits, JS‑click fallback, and post‑conditions (overview/sidebar/Übernehmen) or stalenessOf.
    • Add helpers waitFirstClickable(...) and waitForNextStatisticsState(...).
  • Make zmsautomation/src/test/java/zms/ataf/ui/steps/StatisticsSteps.java resilient to holiday API failures:

    • Add computePreviousBusinessDayLocally() and prefer it when ZMS_STAT_OFFLINE_HOLIDAYS=true (default), or on any exception from HolidayUtil.
  • Optional (script, not code): In zmsautomation-test, pass -Dwebdriver.gecko.driver=/usr/local/bin/geckodriver to mvn test to avoid SeleniumManager network lookups.

💡 Use @coderabbitai plan to create a plan for code changes

@coderabbitai total nonsense. It logs in and selects worstation just like zmsadmin (This is where we have timeout issues unlike zmsadmin). The part where it selects a location the second time from the selection scroll window and date range is after navigating to the Kundenstatistik or Dienstleistungstatistik. Looks like you're not up to date.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 2026

Tip

For best results, initiate chat on the files or code changes.

@ThomasAFink Thanks for the correction — you’re right. I misattributed the flakiness. The timeouts happen on the very first screen (Keycloak submit + initial Standort submit “Auswahl bestätigen”), not on the second selection after navigating to Kunden-/Dienstleistungsstatistik. Sorry for the noise.

Here’s a focused fix to make zmsstatistic behave like zmsadmin at that exact point.

  1. Align StatisticsPage.clickOnLoginButton() with zmsadmin’s pattern
  • Normal click then wait for “any-of” post-login signals.
  • If the 30s navigation timeout fires but post-login UI is present, continue (warn only).
  • JS-click fallback when the normal click is intercepted.

Java (replace method body and add the two helpers)

// imports to have:
// import org.openqa.selenium.*;
// import org.openqa.selenium.support.ui.*;
// import java.time.Duration;

public void clickOnLoginButton() throws Exception {
    ScenarioLogManager.getLogger().info("Trying to click on \"Login\" button (Keycloak)...");
    CONTEXT.set();

    // If we are on Keycloak already, fields are present; otherwise click the entry link/button first
    if (!isWebElementVisible(5, "//*[`@id`='username' or `@id`='kc-login']", LocatorType.XPATH, false)) {
        By[] entryCandidates = new By[] {
            By.xpath("//a[contains(normalize-space(),'Anmelden') or contains(.,'Login')]"),
            By.cssSelector("a.button--login, a[href*='keycloak'], a[href*='oidc']"),
            By.xpath("//button[contains(normalize-space(),'Anmelden') or contains(normalize-space(),'Login')]")
        };
        WebElement entry = waitFirstClickable(entryCandidates, DEFAULT_EXPLICIT_WAIT_TIME);
        Assert.assertNotNull(entry, "Could not find the statistics SSO entry button/link.");
        scrollToCenterByVisibleElement(entry);
        try { entry.click(); } catch (Exception e) { ((JavascriptExecutor) DRIVER).executeScript("arguments[0].click();", entry); }
    }

    // Fill credentials and click kc-login
    enterTextInWebElement(DEFAULT_EXPLICIT_WAIT_TIME, "username", LocatorType.ID, TestPropertiesHelper.getString("userName"));
    enterTextInWebElement(DEFAULT_EXPLICIT_WAIT_TIME, "password", LocatorType.ID, TestPropertiesHelper.getString("userPassword"));

    WebElement submit = waitFirstClickable(new By[]{ By.id("kc-login") }, DEFAULT_EXPLICIT_WAIT_TIME);
    Assert.assertNotNull(submit, "Could not find Keycloak submit button (kc-login).");

    try {
        scrollToCenterByVisibleElement(submit);
        submit.click();
        Assert.assertTrue(waitForStatisticsHomeOrFilter(60), "Post-login statistics page did not appear in time.");
    } catch (TimeoutException te) {
        ScenarioLogManager.getLogger().warn("SSO login navigation took longer than expected; probing post-login state...");
        Assert.assertTrue(waitForStatisticsHomeOrFilter(30), "Post-login UI not detected after timeout.");
    } catch (Exception e) {
        ScenarioLogManager.getLogger().warn("Normal kc-login click failed (" + e.getClass().getSimpleName() + "). Falling back to JS click.");
        ((JavascriptExecutor) DRIVER).executeScript("arguments[0].click();", submit);
        Assert.assertTrue(waitForStatisticsHomeOrFilter(60), "Post-login UI not detected after JS click.");
    }
}

private WebElement waitFirstClickable(By[] locators, int seconds) {
    WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
    for (By by : locators) {
        try { return w.until(ExpectedConditions.elementToBeClickable(by)); } catch (Exception ignored) {}
    }
    return null;
}

private boolean waitForStatisticsHomeOrFilter(int seconds) {
    WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
    try { w.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector(".spinner, div.spinner, .loading"))); } catch (Exception ignored) {}
    By[] goodSignals = new By[] {
        By.name("scope"), By.id("scope"), // Standort select present
        By.xpath("//button[normalize-space()='Auswahl bestätigen' or normalize-space()='Weiter']"),
        By.cssSelector("form button[type='submit'], form input[type='submit']"),
        By.xpath("//aside//a[contains(normalize-space(),'Kundenstatistik') or contains(normalize-space(),'Dienstleistungsstatistik')]")
    };
    for (By by : goodSignals) {
        try { w.until(ExpectedConditions.presenceOfElementLocated(by)); return true; } catch (Exception ignored) {}
    }
    return false;
}
  1. Make the initial “Auswahl bestätigen/Weiter” submit anti-stale with retries
  • Re-locate before each attempt.
  • Scroll + normal click; JS fallback when intercepted.
  • After click, either the element goes stale or we detect the next stable state (overview, sidebar, or subpage filter).

Java (replace clickOnApplySelectionButton)

public void clickOnApplySelectionButton() {
    ScenarioLogManager.getLogger().info("Trying to click on \"Auswahl bestätigen\" button...");
    CONTEXT.set();
    if (CONTEXT instanceof StatisticsPageContext s) { s.waitForSpinners(); }

    By[] submitCandidates = new By[] {
        By.xpath("//button[`@type`='submit' and (normalize-space()='Weiter' or normalize-space()='Auswahl bestätigen')]"),
        By.xpath("//input[`@type`='submit' and (translate(`@value`,'WEITER','weiter')='weiter' or contains(translate(`@value`,'ÄAUSW','äausw'),'auswahl'))]"),
        By.cssSelector("form button[type='submit'], form input[type='submit']")
    };

    WebDriverWait wait = new WebDriverWait(DRIVER, Duration.ofSeconds(DEFAULT_EXPLICIT_WAIT_TIME));
    for (int attempt = 1; attempt <= 3; attempt++) {
        try {
            WebElement submit = waitFirstClickable(submitCandidates, DEFAULT_EXPLICIT_WAIT_TIME);
            Assert.assertNotNull(submit, "Could not find the statistics location submit button.");

            scrollToCenterByVisibleElement(submit);
            try { wait.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector(".spinner, div.spinner, .loading"))); } catch (Exception ignored) {}

            try { submit.click(); }
            catch (Exception e) {
                ScenarioLogManager.getLogger().warn("Normal click failed (" + e.getClass().getSimpleName() + "). Falling back to JS click.");
                ((JavascriptExecutor) DRIVER).executeScript("arguments[0].click();", submit);
            }

            boolean navigated = false;
            try { new WebDriverWait(DRIVER, Duration.ofSeconds(10)).until(ExpectedConditions.stalenessOf(submit)); navigated = true; }
            catch (Exception ignored) { navigated = waitForNextStatisticsState(60); }

            if (navigated) return;
            ScenarioLogManager.getLogger().warn("Post-submit state not detected; retrying (" + attempt + "/3)...");
        } catch (StaleElementReferenceException | TimeoutException e) {
            ScenarioLogManager.getLogger().warn("Submit interaction issue (" + e.getClass().getSimpleName() + "); retrying (" + attempt + "/3)...");
        }
    }
    Assert.fail("Could not proceed after clicking \"Auswahl bestätigen\" (retries exhausted).");
}

private boolean waitForNextStatisticsState(int seconds) {
    WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
    By[] next = new By[] {
        By.xpath("//h1[contains(normalize-space(),'Übersicht')]"),
        By.xpath("//aside//a[contains(normalize-space(),'Kundenstatistik') or contains(normalize-space(),'Dienstleistungsstatistik')]"),
        By.xpath("//*[self::button or self::input][normalize-space()='Übernehmen' or translate(`@value`,'ÜBERNEHMEN','übernehmen')='übernehmen']")
    };
    for (By by : next) {
        try { w.until(ExpectedConditions.presenceOfElementLocated(by)); return true; } catch (Exception ignored) {}
    }
    return false;
}

This directly addresses the two flaky points you called out:

  • Keycloak submit (“kc-login”) → tolerate the driver’s navigation timeout when the UI actually advanced.
  • Initial Standort submit (“Auswahl bestätigen”/Weiter) → anti‑stale + next‑state verification.

If you want, I can also lift the exact SSO/login helper out of AdminPage to a shared utility so Admin/Statistic stay in lock‑step.

Quick answer to your earlier question (data freshness)

  • Simplest to implement and maintain: fix “today” in zmsautomation-test so the apps believe, for example, 2026‑03‑06 at runtime. That freezes all time‑dependent UI and SQL fixtures and eliminates drift.
  • Optional belt‑and‑suspenders: in V10 opening-hours test data, set Datum_von = CURDATE() and Datum_bis = DATE_ADD(CURDATE(), INTERVAL 30 DAY) so fresh slots appear after every clean+migrate even without time mocking.

Want me to stage the two StatisticsPage changes above? I’ll prep a minimal plan and wire it exactly like in zmsadmin.

Task: Stabilize zmsstatistic first-screen flows (SSO + initial Standort submit)

  • Align StatisticsPage.clickOnLoginButton with zmsadmin’s post-click wait and JS fallback
  • Make clickOnApplySelectionButton anti-stale with retries and strong post-conditions

✏️ Learnings added
Learnt from: ThomasAFink
URL: https://github.com/it-at-m/eappointment/pull/1892

Timestamp: 2026-03-07T01:13:01.933Z
Learning: zmsstatistic flakes occur on the first screen (Keycloak submit and the initial Standort “Auswahl bestätigen” submit), not on the second selection after navigating to Kunden-/Dienstleistungsstatistik. For stability, StatisticsPage must mirror zmsadmin: post-click “any-of” waits on SSO, and anti-stale retries for the initial submit with spinner/overlay handling.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


💡 Use @coderabbitai plan to create a plan for code changes

@ThomasAFink
Copy link
Member Author

@coderabbitai I still don't think you understand we have two different scope selections. The first one is like zmsadmin to select the workstation. The second is to apply to the statistics table and may be rendered differently from the first selection box.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 2026

Tip

For best results, initiate chat on the files or code changes.

@ThomasAFink You’re right — there are two different scope selections in zmsstatistic, and they must be handled separately:

  • Initial scope/workstation selection (landing page, like zmsadmin): Standort + optional Tresen/Platz-Nr. → submit.
  • Statistics-table filter (after opening Kunden-/Dienstleistungsstatistik): scope picker (often multi-select) + date range → Übernehmen.

Here’s a concise refactor plan to make that separation explicit and robust.

  • StatisticsPage.java

    • Add two clear detectors:
      • boolean isInitialScopeFormPresent(): detects landing-page Standort + optional Tresen/Platz-Nr. form.
      • boolean isStatisticsFilterPresent(): detects subpage filter (scope picker + Von/Bis + Übernehmen).
    • Implement two dedicated helpers:
      • void selectInitialScopeAndApply(String location, @Nullable String workstation):
        • Locators (candidates, tried in order):
          • Standort: select[name='scope'], select#scope, //label[.='Standort']/following::select[1]
          • Workstation (optional): input[name='workstation'], input#tresen, //label[contains(.,'Platz') or contains(.,'Tresen')]/following::input[1]
          • Submit: //button[@type='submit' and (normalize-space()='Weiter' or normalize-space()='Auswahl bestätigen')], //input[@type='submit'].
        • Behavior: spinner wait → scroll → normal click; JS click fallback; then wait for staleness or “overview/sidebar” next-state (60s). Retry up to 3 times on stale/timeout.
      • void applyStatisticsTableFilter(String location, LocalDate from, LocalDate to):
        • Scope candidates:
          • select[name='scope[]'], select[name='scope'], #scope-select, .select2 / .ts-control (tom-select).
          • If plugin: click container, type partial text, pick option by visible text equals location.
        • Dates: set via JS (names/ids: von/bis or from/to); dispatch change event.
        • Submit: Übernehmen button/input (normal + JS fallback), then wait for table heading/rows (or breadcrumb) as post-condition.
    • Keep hardened SSO click (kc-login) with any-of post-conditions (already aligned with zmsadmin).
  • StatisticsSteps.java

    • After login: call selectInitialScopeAndApply(TestData.location, Optional<TestData.workstation>).
    • After clicking Kundenstatistik/Dienstleistungsstatistik: call applyStatisticsTableFilter(TestData.location, prevBusinessDay..today) instead of a generic “apply if present”.

Example core of the two helpers (shortened to show intent)

// Detect landing form vs subpage filter
private boolean isInitialScopeFormPresent() {
  return isWebElementVisible(3, "//form//select[`@name`='scope' or `@id`='scope']",
         LocatorType.XPATH, false)
      || isWebElementVisible(3, "//label[contains(.,'Standort')]", LocatorType.XPATH, false);
}
private boolean isStatisticsFilterPresent() {
  return isWebElementVisible(3, "//*[self::button or self::input][normalize-space()='Übernehmen' or translate(`@value`,'ÜBERNEHMEN','übernehmen')='übernehmen']",
         LocatorType.XPATH, false)
      || isWebElementVisible(3, "#scope-select, .select2, .ts-control", LocatorType.CSS, false);
}

public void selectInitialScopeAndApply(String location, String workstationOrNull) {
  CONTEXT.set();
  if (CONTEXT instanceof StatisticsPageContext s) s.waitForSpinners();

  // Standort
  By[] scopeSel = {
    By.cssSelector("select[name='scope']"),
    By.id("scope"),
    By.xpath("//label[contains(normalize-space(.),'Standort')]/following::select[1]")
  };
  WebElement scope = waitFirstClickable(scopeSel, DEFAULT_EXPLICIT_WAIT_TIME);
  Assert.assertNotNull(scope, "Initial Standort select not found.");
  selectDropDownListValueByVisibleText(DEFAULT_EXPLICIT_WAIT_TIME, scope, location);

  // Optional Tresen/Platz-Nr.
  By[] wsInp = {
    By.cssSelector("input[name='workstation']"),
    By.id("tresen"),
    By.xpath("//label[matches(normalize-space(.),'(?i)(Platz|Tresen)')]/following::input[1]")
  };
  WebElement ws = waitFirstClickable(wsInp, 3);
  if (ws != null && workstationOrNull != null && !workstationOrNull.isBlank()) {
    enterTextInWebElement(DEFAULT_EXPLICIT_WAIT_TIME, ws, workstationOrNull);
  }

  // Submit with anti-stale
  clickSubmitWithRetries(new By[]{
    By.xpath("//button[`@type`='submit' and (normalize-space()='Weiter' or normalize-space()='Auswahl bestätigen')]"),
    By.cssSelector("form button[type='submit'], form input[type='submit']")
  });
}

public void applyStatisticsTableFilter(String location, LocalDate from, LocalDate to) {
  CONTEXT.set();
  if (!isStatisticsFilterPresent()) return;

  // Scope on subpage: native select[] or plugin container
  if (isWebElementVisible(2, "select[name='scope[]'], select[name='scope']", LocatorType.CSS, false)) {
    selectDropDownListValueByVisibleText(DEFAULT_EXPLICIT_WAIT_TIME, 
      "select[name='scope[]'], select[name='scope']", LocatorType.CSS, location);
  } else {
    // plugin: select2 / tom-select
    clickOnWebElement(DEFAULT_EXPLICIT_WAIT_TIME, "#scope-select, .select2, .ts-control", LocatorType.CSS, false);
    enterTextInWebElement(DEFAULT_EXPLICIT_WAIT_TIME, 
      ".ts-control input, .select2-search__field", LocatorType.CSS, location);
    clickOnWebElement(DEFAULT_EXPLICIT_WAIT_TIME, 
      String.format("//div[contains(`@class`,'option') or contains(`@class`,'select2-results__option')][normalize-space()='%s']", location),
      LocatorType.XPATH, false);
  }

  // Dates via JS (names/ids may differ)
  setDateByJs(new String[]{"von","from"}, from);
  setDateByJs(new String[]{"bis","to"}, to);

  // Übernehmen with anti-stale and post-condition wait
  clickSubmitWithRetries(new By[]{
    By.xpath("//*[self::button or self::input][normalize-space()='Übernehmen' or translate(`@value`,'ÜBERNEHMEN','übernehmen')='übernehmen']")
  });
}

This keeps the flows independent and variant-aware (native select vs plugin; date ids vs names), and uses the same anti-stale + spinner + JS‑fallback strategy as zmsadmin.

If you confirm, I’ll wire this in exactly as above and adjust the steps to call:

  • after login: selectInitialScopeAndApply(...)
  • after opening Kundenstatistik/Dienstleistungsstatistik: applyStatisticsTableFilter(...)

Also, I’ll keep the existing “any-of” post-condition waits to eliminate the remaining timeouts.

Please confirm:

  • The preferred keys in TestData for the first screen (location, workstation).
  • Whether the subpage scope is a multi-select (select[name='scope[]']) or a JS plugin (id='scope-select'/select2/tom-select) in your current build. I’ll include both paths either way.

✏️ Learnings added
Learnt from: ThomasAFink
URL: https://github.com/it-at-m/eappointment/pull/1892

Timestamp: 2026-03-07T01:17:30.552Z
Learning: zmsstatistic has two distinct scope selection UIs that must be handled separately:
1) Initial landing page (like zmsadmin): select Standort and optionally enter Tresen/Platz‑Nr., then submit (“Auswahl bestätigen”/“Weiter”).
2) Subpage filter (after opening Kundenstatistik/Dienstleistungsstatistik): statistics scope picker (often multi‑select or JS plugin) plus date range (Von/Bis) and an “Übernehmen” submit.
Tests and page objects should implement two separate flows with variant-aware locators and anti-stale/overlay waits.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


💡 Use @coderabbitai plan to create a plan for code changes

…selections

- Login and first scope (selectLocation, clickOnApplySelectionButton) match
  zmsadmin; JS click on confirm to avoid navigation timeout and stale element
- Second scope: rename applyLocationAndDateFilter to
  selectScopeInStatisticsTableFilter (statistics table filter only, after
  opening Kundenstatistik/Dienstleistungsstatistik)
- Expose STATISTICS_TIMEOUT_SECONDS for overview wait after apply selection
@ThomasAFink
Copy link
Member Author

@coderabbitai check last commit to see speration of concerns now

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== git HEAD =="
git rev-parse --abbrev-ref HEAD || true
git log -1 --pretty=fuller --no-color

echo -e "\n== files changed in last commit =="
git show --name-status --pretty=format: HEAD

echo -e "\n== scan for key files touched =="
fd -t f "StatisticsPage.java|StatisticsSteps.java|AdminPage.java|StatisticsPageContext.java" -E "target" || true

echo -e "\n== show HEAD hunks for StatisticsPage.java (if modified) =="
if git show --name-only --pretty=format: HEAD | grep -q "zmsautomation/src/test/java/zms/ataf/ui/pages/statistics/StatisticsPage.java"; then
  git show -U4 --no-color HEAD -- "zmsautomation/src/test/java/zms/ataf/ui/pages/statistics/StatisticsPage.java" | sed -n '1,300p'
fi

echo -e "\n== show HEAD hunks for StatisticsSteps.java (if modified) =="
if git show --name-only --pretty=format: HEAD | grep -q "zmsautomation/src/test/java/zms/ataf/ui/steps/StatisticsSteps.java"; then
  git show -U4 --no-color HEAD -- "zmsautomation/src/test/java/zms/ataf/ui/steps/StatisticsSteps.java" | sed -n '1,240p'
fi

echo -e "\n== grep for separation helpers in working tree =="
rg -nP --type=java -C2 "(isInitialScopeFormPresent|isStatisticsFilterPresent|selectInitialScopeAndApply|applyStatisticsTableFilter)" || true

echo -e "\n== grep for hardened interactions (kc-login, Auswahl bestätigen, Übernehmen) =="
rg -nP --type=java -C2 "(kc-login|Auswahl bestätigen|Übernehmen|stalenessOf|ExpectedConditions\\.(presenceOfElementLocated|invisibilityOfElementLocated|elementToBeClickable))" "zmsautomation/src/test/java/zms/ataf/ui/pages/statistics/StatisticsPage.java" || true

Length of output: 22194


@ThomasAFink Thanks — I checked the last commit (53b17f6, Sat Mar 7, 2026 02:18:45) and the separation of concerns is now in place and aligned with zmsadmin:

What changed and looks correct

  • First scope (post‑login, like zmsadmin)

    • selectLocation(String) still targets the initial Standort dropdown (name=scope).
    • clickOnApplySelectionButton() now JS‑clicks //button[@type='submit' and @value='weiter'] and then waits for the Übersicht heading using StatisticsPageContext.STATISTICS_TIMEOUT_SECONDS. This avoids the earlier navigation timeout/stale issues on the first screen and mirrors the admin flow.
  • Second scope (statistics table/report filter after opening Kunden‑/Dienstleistungsstatistik)

    • applyLocationAndDateFilter(...) was split: scope selection is now selectScopeInStatisticsTableFilter(String), and dates remain in applyDateRangeFilter(LocalDate, LocalDate).
    • The filter presence is gated by Übernehmen; scope selection prefers #scope-select with a visible-text option click and falls back to native option when needed. Übernehmen is submitted after setting dates via JS, consistent with the earlier fixes. Steps wiring StatisticsSteps now routes “Standort” on the filter screen to STATISTICS_PAGE.selectScopeInStatisticsTableFilter(value), so it only affects the second scope and not the initial workstation selection. Minor note to consider (optional) clickOnLoginButton() was simplified to a direct kc-login click. If Keycloak navigation timeouts reappear under load, we can reintroduce the “any‑of” post‑login wait used earlier (scope select/submit/sidebar) without changing the public steps. Otherwise this is fine as-is given your alignment goal. If you want me to scan for any remaining calls that still use the old applyLocationAndDateFilter name or to add a tiny wait helper for the stats sidebar before clicking its entries, I can do that next. Otherwise, from my side the two-scope split and zmsadmin parity look good. ✏️ Learnings added Learnt from: ThomasAFink URL: https://github.com/refactor(ZMSKVR-686): zmsapiautomation to zmsautomation module with ATAF integration #1892 Timestamp: 2026-03-07T01:24:42.921Z Learning: zmsautomation statistics separation: use selectLocation() + clickOnApplySelectionButton() exclusively for the first (post‑login) scope/workstation selection; use selectScopeInStatisticsTableFilter() + applyDateRangeFilter() only on the Kunden-/Dienstleistungsstatistik subpages for the second scope/date filter. Learnt from: ThomasAFink URL: https://github.com/refactor(ZMSKVR-686): zmsapiautomation to zmsautomation module with ATAF integration #1892 Timestamp: 2026-03-07T01:24:42.921Z Learning: StatisticsSteps mapping: for the statistics filter UI, the “Standort” type now calls StatisticsPage.selectScopeInStatisticsTableFilter(value); the initial Standort selection remains handled by selectLocation() + clickOnApplySelectionButton() immediately after login. Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@ThomasAFink
Copy link
Member Author

ThomasAFink commented Mar 7, 2026

Still randomly fails at work station select @coderabbitai

20260307-022559 490
[INFO] Running Cucumber/ATAF tests with -Pataf profile...
[INFO] Note: You can filter tests by tags using: -Dcucumber.filter.tags=@tagName
[INFO] Example: bash zmsautomation-test -Dcucumber.filter.tags=@smoke
[INFO] Keycloak config rewritten (8091→443): /var/www/html/zmsstatistic/keycloak.json
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------< de.muenchen.eappointment:zmsautomation >---------------
[INFO] Building zmsautomation 0.1.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ zmsautomation ---
[INFO] Copying 18 resources from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.14.1:compile (default-compile) @ zmsautomation ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ zmsautomation ---
[INFO] Copying 42 resources from src/test/resources to target/test-classes
[INFO]
[INFO] --- compiler:3.14.1:testCompile (default-testCompile) @ zmsautomation ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 58 source files with javac [debug target 21] to target/test-classes
[INFO] Annotation processing is enabled because one or more processors were found
  on the class path. A future release of javac may disable annotation processing
  unless at least one processor is specified by name (-processor), or a search
  path is specified (--processor-path, --processor-module-path), or annotation
  processing is enabled explicitly (-proc:only, -proc:full).
  Use -Xlint:-options to suppress this message.
  Use -proc:none to disable annotation processing.
[INFO] /var/www/html/zmsautomation/src/test/java/zms/ataf/data/TestData.java: Some input files use or override a deprecated API.
[INFO] /var/www/html/zmsautomation/src/test/java/zms/ataf/data/TestData.java: Recompile with -Xlint:deprecation for details.
[INFO]
[INFO] --- surefire:3.5.4:test (default-test) @ zmsautomation ---
[INFO] Using configured provider org.apache.maven.surefire.testng.TestNGProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running zms.ataf.runner.UiTestRunner
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[1] 02:21:56 INFO  TestData:17 - Initializing ZMS API test environments
[1] 02:21:56 INFO  RunnerUtils:87 - Start of Test Suite!
[1] 02:21:56 INFO  TestProperties:36 - >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Start of loading properties>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[1] 02:21:56 INFO  TestProperties:150 - (string) Property [features] with value [src/test/resources/features] has been successfully loaded!
[1] 02:21:56 INFO  TestProperties:150 - (string) Property [userName] with value [ataf] has been successfully loaded!
[1] 02:21:56 INFO  TestProperties:150 - (string) Property [noProxy] with value [keycloak] has been successfully loaded!
[1] 02:21:56 INFO  TestProperties:150 - (string) Property [logLevel] with value [INFO] has been successfully loaded!
[1] 02:21:56 INFO  TestProperties:150 - (string) Property [publish.quiet] with value [true] has been successfully loaded!
[1] 02:21:56 INFO  TestProperties:150 - (string) Property [filter.tags] with value [@web and @zmsstatistic] has been successfully loaded!
[1] 02:21:56 INFO  TestProperties:142 - (string) Property [testDataEncryptionPassword] has been successfully loaded!
[1] 02:21:56 INFO  TestProperties:150 - (string) Property [plugin] with value [json:target/cucumber.json,html:target/site/cucumber-pretty] has been successfully loaded!
[1] 02:21:56 INFO  TestProperties:150 - (string) Property [glue] with value [zms.ataf.steps,zms.ataf.api.steps,zms.ataf.ui.steps,zms.ataf.hooks,ataf.rest.steps,ataf.web.steps] has been successfully loaded!
[1] 02:21:56 INFO  TestProperties:142 - (string) Property [userPassword] has been successfully loaded!
[1] 02:21:56 INFO  TestProperties:150 - (string) Property [publish.enabled] with value [false] has been successfully loaded!
[1] 02:21:56 INFO  TestProperties:150 - (string) Property [test.execution.test.environment] with value [LOCAL] has been successfully loaded!
[1] 02:21:56 INFO  TestProperties:63 - <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Finished loading of properties<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
[1] 02:21:56 INFO  RunnerUtils:116 - Log Level set to "INFO"
[1] 02:21:56 WARN  RunnerUtils:228 - Cannot create JQL: No property value for "issueKeys" or "filterId" has been provided. Also see: https://docs.getxray.app/display/XRAY/Exporting+Cucumber+Tests+-+REST
[1] 02:21:57 INFO  Hook:25 - Hook: BeforeAll (Framework-API)
[1] 02:21:57 INFO  Hook:142 - >>>>>>>Start of test scenario: Kundenstatistik>>>>>>>
[1] 02:21:57 INFO  DriverUtil:132 - Test will be executed locally!
[1] 02:21:57 INFO  DriverUtil:217 - Proxy set
Mar 07, 2026 2:22:39 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Problem reading geckodriver versions: error sending request for url (https://raw.githubusercontent.com/SeleniumHQ/selenium/trunk/common/geckodriver/geckodriver-support.json). Using latest geckodriver version
Mar 07, 2026 2:22:39 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Exception managing firefox: error sending request for url (https://github.com/mozilla/geckodriver/releases/latest)
[1] 02:22:48 INFO  Hook:209 - Wait for 250 ms...
[1] 02:22:48 INFO  BasePage:1977 - Trying to navigate to "https://localhost:443/terminvereinbarung/statistic/"
[1] 02:23:10 INFO  BasePage:1984 - Waiting until page title is "Anmeldung - ZMS Statistik"
[1] 02:23:10 INFO  WindowControls:78 - Window added to the list: [0] Window{windowHandle='9004e8b0-0ae9-4c59-980c-a668cc29f66d', windowTitle='Anmeldung - ZMS Statistik', windowType=ataf.web.model.WindowType@43984213}
[1] 02:23:10 INFO  Hook:209 - Wait for 250 ms...
[1] 02:23:10 INFO  StatisticsPage:71 - Trying to click on "Login" button...
[1] 02:23:11 INFO  StatisticsPage:74 - SSO-Login page detected!
[1] 02:23:11 INFO  StatisticsPage:92 - Trying to enter user name...
[1] 02:23:11 INFO  StatisticsPage:95 - Trying to enter password...
[1] 02:23:11 INFO  StatisticsPage:98 - Trying to click on "Login" button (Keycloak)...
[1] 02:23:36 INFO  StatisticsPage:100 - SSO login submitted successfully.
[1] 02:23:36 INFO  Hook:209 - Wait for 250 ms...
[1] 02:23:37 INFO  StatisticsPage:119 - Trying to select location "Gewerbeamt (KVR-III/23) Verkehr" (first scope, like zmsadmin)
[1] 02:23:38 INFO  BasePage:1613 - Text "Gewerbeamt (KVR-III/23) Verkehr" found in Dropdown
[1] 02:23:38 INFO  Hook:209 - Wait for 250 ms...
[1] 02:23:38 INFO  StatisticsPage:126 - Trying to click on "Auswahl best?tigen" button...
[1] 02:23:42 INFO  Hook:209 - Wait for 250 ms...
[1] 02:23:42 INFO  StatisticsPage:139 - Checking if the overview page is visible.
[1] 02:23:42 INFO  Hook:209 - Wait for 250 ms...
[1] 02:23:43 INFO  StatisticsPage:148 - Trying to click on "Kundenstatistik" button in the sidebar...
[1] 02:24:05 INFO  Hook:209 - Wait for 250 ms...
[1] 02:24:05 INFO  StatisticsPage:153 - Checking if the Kundenstatistik statistics page is visible.
[1] 02:24:05 INFO  Hook:209 - Wait for 250 ms...
[1] 02:24:05 INFO  StatisticsPage:174 - Selecting scope in statistics table filter (second scope): "Gewerbeamt (KVR-III/23) Verkehr"
[1] 02:24:06 INFO  StatisticsPage:211 - Scope "Gewerbeamt (KVR-III/23) Verkehr" selected in statistics table filter.
[1] 02:24:07 INFO  Hook:209 - Wait for 250 ms...
[1] 02:24:07 INFO  StatisticsPage:223 - Applying date range in statistics table filter...
[1] 02:24:07 INFO  StatisticsPage:235 - Setting statistics date range from 2026-02-21 to 2026-03-07
[1] 02:24:07 INFO  StatisticsPage:263 - Date input 'from' set to 2026-02-21
[1] 02:24:07 INFO  StatisticsPage:263 - Date input 'to' set to 2026-03-07
[1] 02:24:07 INFO  StatisticsPage:239 - Submitting statistics filter with ?bernehmen button via JavaScript click...
[1] 02:24:07 INFO  Hook:209 - Wait for 250 ms...
[1] 02:24:07 INFO  CustomerStatisticsPage:45 - Verifying the number of customers who appeared on 06.03.2026. Expected number: 2
[1] 02:24:11 INFO  CustomerStatisticsPage:56 - Verifying the number of customers who didn't appear on 06.03.2026. Expected number: 2
[1] 02:24:11 INFO  CustomerStatisticsPage:67 - Verifying the number of appointment customers who appeard on 06.03.2026. Expected number: 1
[1] 02:24:11 INFO  CustomerStatisticsPage:79 - Verifying the number of appointment customers who didn't appear on 06.03.2026. Expected number: 1
[1] 02:24:12 INFO  CustomerStatisticsPage:90 - Verifying the number of spontaneous customers who appeard on 06.03.2026. Expected number: 1
[1] 02:24:12 INFO  CustomerStatisticsPage:102 - Verifying the number of spontaneous customers who didn't appear on 06.03.2026. Expected number: 1
[1] 02:24:12 INFO  Hook:209 - Wait for 250 ms...
[1] 02:24:13 INFO  StatisticsPage:329 - Trying to click the download button in the customer statistics...
[1] 02:24:13 INFO  Hook:209 - Wait for 250 ms...
[1] 02:24:13 INFO  StatisticsPage:329 - Trying to click the download button in the customer statistics...
[1] 02:24:13 INFO  Hook:179 - Test passed: Kundenstatistik
[1] 02:24:13 INFO  WindowControls:554 - Window list cleared!
[1] 02:24:13 INFO  Hook:189 - <<<<<<<End of test scenario: Kundenstatistik<<<<<<<
[1] 02:24:13 INFO  Hook:142 - >>>>>>>Start of test scenario: Dienstleistungsstatistik>>>>>>>
[1] 02:24:13 INFO  DriverUtil:132 - Test will be executed locally!
[1] 02:24:13 INFO  DriverUtil:217 - Proxy set
Mar 07, 2026 2:24:55 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Problem reading geckodriver versions: error sending request for url (https://raw.githubusercontent.com/SeleniumHQ/selenium/trunk/common/geckodriver/geckodriver-support.json). Using latest geckodriver version
Mar 07, 2026 2:24:55 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Exception managing firefox: error sending request for url (https://github.com/mozilla/geckodriver/releases/latest)
[1] 02:25:03 INFO  Hook:209 - Wait for 250 ms...
[1] 02:25:03 INFO  BasePage:1977 - Trying to navigate to "https://localhost:443/terminvereinbarung/statistic/"
[1] 02:25:28 INFO  BasePage:1984 - Waiting until page title is "Anmeldung - ZMS Statistik"
[1] 02:25:28 INFO  WindowControls:78 - Window added to the list: [0] Window{windowHandle='6ec68bbd-6c08-4d08-b669-e03d246b3687', windowTitle='Anmeldung - ZMS Statistik', windowType=ataf.web.model.WindowType@4aebee4b}
[1] 02:25:28 INFO  Hook:209 - Wait for 250 ms...
[1] 02:25:28 INFO  StatisticsPage:71 - Trying to click on "Login" button...
[1] 02:25:29 INFO  StatisticsPage:74 - SSO-Login page detected!
[1] 02:25:29 INFO  StatisticsPage:92 - Trying to enter user name...
[1] 02:25:29 INFO  StatisticsPage:95 - Trying to enter password...
[1] 02:25:29 INFO  StatisticsPage:98 - Trying to click on "Login" button (Keycloak)...
[1] 02:25:59 ERROR StatisticsPage:102 - Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [81aa5665-cc56-4f77-9753-9c65271d336e, clickElement {id=c3f05e70-68ed-491f-af2e-bf1b1bb689ea}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 3882, moz:profile: /tmp/rust_mozprofileZiFbr1, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (81aa5665-cc56-4f77-9753-9c65271d336e)] -> id: kc-login]
Session ID: 81aa5665-cc56-4f77-9753-9c65271d336e
org.openqa.selenium.TimeoutException: Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [81aa5665-cc56-4f77-9753-9c65271d336e, clickElement {id=c3f05e70-68ed-491f-af2e-bf1b1bb689ea}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 3882, moz:profile: /tmp/rust_mozprofileZiFbr1, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (81aa5665-cc56-4f77-9753-9c65271d336e)] -> id: kc-login]
Session ID: 81aa5665-cc56-4f77-9753-9c65271d336e
        at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:169) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:142) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:223) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:216) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:174) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:223) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebElement.click(RemoteWebElement.java:76) ~[selenium-remote-driver-4.40.0.jar:?]
        at ataf.web.pages.BasePage.lambda$clickOnWebElement$4(BasePage.java:867) ~[web-0.2.jar:?]
        at org.openqa.selenium.support.ui.FluentWait.until(FluentWait.java:207) ~[selenium-support-4.40.0.jar:?]
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:852) ~[web-0.2.jar:?]
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:912) ~[web-0.2.jar:?]
        at zms.ataf.ui.pages.statistics.StatisticsPage.clickOnLoginButton(StatisticsPage.java:99) ~[test-classes/:?]
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_in_der_statistik_auf_die_schaltflaeche_string_klicken(StatisticsSteps.java:59) ~[test-classes/:?]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
        at io.cucumber.java.Invoker.doInvoke(Invoker.java:66) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.Invoker.invoke(Invoker.java:24) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.AbstractGlueDefinition.invokeMethod(AbstractGlueDefinition.java:47) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.JavaStepDefinition.execute(JavaStepDefinition.java:29) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.core.runner.CoreStepDefinition.execute(CoreStepDefinition.java:66) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.PickleStepDefinitionMatch.runStep(PickleStepDefinitionMatch.java:62) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.ExecutionMode$1.execute(ExecutionMode.java:10) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestStep.executeStep(TestStep.java:81) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestStep.run(TestStep.java:53) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.PickleStepTestStep.run(PickleStepTestStep.java:55) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestCase.run(TestCase.java:87) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.Runner.runPickle(Runner.java:75) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.testng.TestNGCucumberRunner.lambda$runScenario$1(TestNGCucumberRunner.java:132) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at io.cucumber.core.runtime.CucumberExecutionContext.lambda$runTestCase$5(CucumberExecutionContext.java:136) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runtime.RethrowingThrowableCollector.executeAndThrow(RethrowingThrowableCollector.java:23) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runtime.CucumberExecutionContext.runTestCase(CucumberExecutionContext.java:136) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.testng.TestNGCucumberRunner.runScenario(TestNGCucumberRunner.java:129) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at io.cucumber.testng.AbstractTestNGCucumberTests.runScenario(AbstractTestNGCucumberTests.java:35) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
        at org.testng.internal.invokers.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:139) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeMethod(TestInvoker.java:664) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeTestMethod(TestInvoker.java:227) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.MethodRunner.runInSequence(MethodRunner.java:50) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:957) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeTestMethods(TestInvoker.java:200) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:148) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestMethodWorker.run(TestMethodWorker.java:128) ~[testng-7.8.0.jar:7.8.0]
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) [?:?]
        at org.testng.TestRunner.privateRun(TestRunner.java:848) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestRunner.run(TestRunner.java:621) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.runTest(SuiteRunner.java:443) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:437) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.privateRun(SuiteRunner.java:397) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.run(SuiteRunner.java:336) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:95) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuitesSequentially(TestNG.java:1280) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuitesLocally(TestNG.java:1200) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuites(TestNG.java:1114) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.run(TestNG.java:1082) [testng-7.8.0.jar:7.8.0]
        at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:155) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeSingleClass(TestNGDirectoryTestSuite.java:102) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:91) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:137) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495) [surefire-booter-3.5.4.jar:3.5.4]
[1] 02:25:59 ERROR Hook:174 - Test failed: Dienstleistungsstatistik
[1] 02:25:59 INFO  WindowControls:554 - Window list cleared!
[1] 02:25:59 INFO  Hook:189 - <<<<<<<End of test scenario: Dienstleistungsstatistik<<<<<<<
[1] 02:25:59 INFO  Hook:68 - Hook: AfterAll (Framework-API)
[1] 02:25:59 INFO  AuthenticationHelper:125 - User name disposed!
[1] 02:25:59 INFO  AuthenticationHelper:132 - Password disposed!
[1] 02:25:59 WARN  AuthenticationHelper:143 - Authorization token cannot be disposed! It has not been set yet.
[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 244.8 s <<< FAILURE! -- in zms.ataf.runner.UiTestRunner
[ERROR] zms.ataf.runner.UiTestRunner.runScenario["Dienstleistungsstatistik", "Default"](2) -- Time elapsed: 105.9 s <<< FAILURE!
org.openqa.selenium.TimeoutException:
Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [81aa5665-cc56-4f77-9753-9c65271d336e, clickElement {id=c3f05e70-68ed-491f-af2e-bf1b1bb689ea}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 3882, moz:profile: /tmp/rust_mozprofileZiFbr1, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (81aa5665-cc56-4f77-9753-9c65271d336e)] -> id: kc-login]
Session ID: 81aa5665-cc56-4f77-9753-9c65271d336e
        at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:169)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:142)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49)
        at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:223)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:216)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:174)
        at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601)
        at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:223)
        at org.openqa.selenium.remote.RemoteWebElement.click(RemoteWebElement.java:76)
        at ataf.web.pages.BasePage.lambda$clickOnWebElement$4(BasePage.java:867)
        at org.openqa.selenium.support.ui.FluentWait.until(FluentWait.java:207)
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:852)
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:912)
        at zms.ataf.ui.pages.statistics.StatisticsPage.clickOnLoginButton(StatisticsPage.java:99)
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_in_der_statistik_auf_die_schaltflaeche_string_klicken(StatisticsSteps.java:59)
        at ?.Sie in der Statistik auf die Schaltfl?che "Anmelden" klicken.(file:///var/www/html/zmsautomation/src/test/resources/features/ui/zmsstatistic/ZMS-1559.feature:8)

[INFO]
[INFO] Results:
[INFO]
[ERROR] Failures:
[ERROR]   UiTestRunner.runScenario ? Timeout Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [81aa5665-cc56-4f77-9753-9c65271d336e, clickElement {id=c3f05e70-68ed-491f-af2e-bf1b1bb689ea}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 3882, moz:profile: /tmp/rust_mozprofileZiFbr1, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (81aa5665-cc56-4f77-9753-9c65271d336e)] -> id: kc-login]
Session ID: 81aa5665-cc56-4f77-9753-9c65271d336e
[INFO]
[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  04:12 min
[INFO] Finished at: 2026-03-07T02:26:00+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.5.4:test (default-test) on project zmsautomation: There are test failures.
[ERROR]
[ERROR] See /var/www/html/zmsautomation/target/surefire-reports for the individual test results.
[ERROR] See dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[SECTION] 12. Test Results
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[INFO] Collecting surefire reports...
total 268
drwxr-xr-x 5 root root  4096 Mar  7 02:25  .
drwxr-xr-x 9 root root 12288 Mar  7 02:26  ..
drwxr-xr-x 2 root root  4096 Mar  7 02:25 'Surefire suite'
-rw-r--r-- 1 root root 50220 Mar  7 02:25  TEST-zms.ataf.runner.UiTestRunner.xml
-rw-r--r-- 1 root root   356 Mar  7 02:25  bullet_point.png
-rw-r--r-- 1 root root   157 Mar  7 02:25  collapseall.gif
-rw-r--r-- 1 root root  5613 Mar  7 02:25  emailable-report.html
-rw-r--r-- 1 root root   977 Mar  7 02:25  failed.png
-rw-r--r-- 1 root root 18992 Mar  7 02:25  index.html
-rw-r--r-- 1 root root 89501 Mar  7 02:25  jquery-3.6.0.min.js
drwxr-xr-x 2 root root  4096 Mar  7 02:25  junitreports
-rw-r--r-- 1 root root   352 Mar  7 02:25  navigator-bullet.png
drwxr-xr-x 3 root root  4096 Mar  7 02:25  old
-rw-r--r-- 1 root root  1019 Mar  7 02:25  passed.png
-rw-r--r-- 1 root root   967 Mar  7 02:25  skipped.png
-rw-r--r-- 1 root root   712 Mar  7 02:25  testng-failed.xml
-rw-r--r-- 1 root root  5355 Mar  7 02:25  testng-reports.css
-rw-r--r-- 1 root root  3612 Mar  7 02:25  testng-reports.js
-rw-r--r-- 1 root root  7050 Mar  7 02:25  testng-reports1.css
-rw-r--r-- 1 root root  3618 Mar  7 02:25  testng-reports2.js
-rw-r--r-- 1 root root  8414 Mar  7 02:25  testng-results.xml
-rw-r--r-- 1 root root   303 Mar  7 02:25  testng.css
-rw-r--r-- 1 root root  3180 Mar  7 02:25  zms.ataf.runner.UiTestRunner.txt
==== /var/www/html/zmsautomation/target/surefire-reports/zms.ataf.runner.UiTestRunner.txt ====
-------------------------------------------------------------------------------
Test set: zms.ataf.runner.UiTestRunner
-------------------------------------------------------------------------------
Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 244.8 s <<< FAILURE! -- in zms.ataf.runner.UiTestRunner
zms.ataf.runner.UiTestRunner.runScenario["Dienstleistungsstatistik", "Default"](2) -- Time elapsed: 105.9 s <<< FAILURE!
org.openqa.selenium.TimeoutException:
Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [81aa5665-cc56-4f77-9753-9c65271d336e, clickElement {id=c3f05e70-68ed-491f-af2e-bf1b1bb689ea}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 3882, moz:profile: /tmp/rust_mozprofileZiFbr1, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (81aa5665-cc56-4f77-9753-9c65271d336e)] -> id: kc-login]
Session ID: 81aa5665-cc56-4f77-9753-9c65271d336e
        at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:169)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:142)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49)
        at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:223)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:216)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:174)
        at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601)
        at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:223)
        at org.openqa.selenium.remote.RemoteWebElement.click(RemoteWebElement.java:76)
        at ataf.web.pages.BasePage.lambda$clickOnWebElement$4(BasePage.java:867)
        at org.openqa.selenium.support.ui.FluentWait.until(FluentWait.java:207)
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:852)
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:912)
        at zms.ataf.ui.pages.statistics.StatisticsPage.clickOnLoginButton(StatisticsPage.java:99)
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_in_der_statistik_auf_die_schaltflaeche_string_klicken(StatisticsSteps.java:59)
        at ✽.Sie in der Statistik auf die Schaltfläche "Anmelden" klicken.(file:///var/www/html/zmsautomation/src/test/resources/features/ui/zmsstatistic/ZMS-1559.feature:8)
        ```

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 2026

Tip

For best results, initiate chat on the files or code changes.

The Comment image shows the zmsstatistic initial selection screen (first scope), with the Standort dropdown and the workstation (“Platz-Nr. oder Tresen”) input above an “Auswahl bestätigen”/“Weiter” submit. This is the same form as zmsadmin’s landing workflow before navigating to any statistics pages.

@ThomasAFink thanks for the logs — I can reproduce the two flaky points on the first screen:

  • kc-login click intermittently hits a 30s TimeoutException even though the post-login UI is already present
  • immediately after that, the first submit can be stale/overlayed during re-render

Below is a concise, drop-in hardening for StatisticsPage that mirrors zmsadmin’s robustness.

  1. Keycloak login: tolerate navigation timeouts and proceed if post-login UI is detected; add JS-click fallback
    java
    public void clickOnLoginButton() throws Exception {
    ScenarioLogManager.getLogger().info("Trying to click on "Login" button (Keycloak)...");
    CONTEXT.set();

    // If we’re not yet on the SSO page, click the entry link/button
    if (!isWebElementVisible(5, "//[@id='username' or @id='kc-login']", LocatorType.XPATH, false)) {
    By[] entryCandidates = new By[] {
    By.xpath("//a[contains(normalize-space(),'Anmelden') or contains(.,'Login')]"),
    By.cssSelector("a.button--login, a[href
    ='keycloak'], a[href*='oidc']"),
    By.xpath("//button[contains(normalize-space(),'Anmelden') or contains(normalize-space(),'Login')]")
    };
    WebElement entry = waitFirstClickable(entryCandidates, DEFAULT_EXPLICIT_WAIT_TIME);
    Assert.assertNotNull(entry, "Could not find the statistics SSO entry button/link.");
    scrollToCenterByVisibleElement(entry);
    try { entry.click(); } catch (Exception e) { ((JavascriptExecutor) DRIVER).executeScript("arguments[0].click();", entry); }
    }

    // Fill credentials
    enterTextInWebElement(DEFAULT_EXPLICIT_WAIT_TIME, "username", LocatorType.ID, TestPropertiesHelper.getString("userName"));
    enterTextInWebElement(DEFAULT_EXPLICIT_WAIT_TIME, "password", LocatorType.ID, TestPropertiesHelper.getString("userPassword"));

    WebElement submit = waitFirstClickable(new By[]{ By.id("kc-login") }, DEFAULT_EXPLICIT_WAIT_TIME);
    Assert.assertNotNull(submit, "Could not find Keycloak submit button (kc-login).");

    try {
    scrollToCenterByVisibleElement(submit);
    submit.click();
    Assert.assertTrue(waitForStatisticsHomeOrFilter(60), "Post-login statistics page did not appear in time.");
    } catch (TimeoutException te) {
    ScenarioLogManager.getLogger().warn("SSO login navigation took longer than expected; probing post-login state...");
    Assert.assertTrue(waitForStatisticsHomeOrFilter(30), "Post-login UI not detected after timeout.");
    } catch (Exception e) {
    ScenarioLogManager.getLogger().warn("Normal kc-login click failed (" + e.getClass().getSimpleName() + "). Falling back to JS click.");
    ((JavascriptExecutor) DRIVER).executeScript("arguments[0].click();", submit);
    Assert.assertTrue(waitForStatisticsHomeOrFilter(60), "Post-login UI not detected after JS click.");
    }
    }

private WebElement waitFirstClickable(By[] locators, int seconds) {
WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
for (By by : locators) {
try { return w.until(ExpectedConditions.elementToBeClickable(by)); }
catch (Exception ignored) {}
}
return null;
}

private boolean waitForStatisticsHomeOrFilter(int seconds) {
WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
try { w.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector(".spinner, div.spinner, .loading"))); } catch (Exception ignored) {}

By[] goodSignals = new By[] {
    By.name("scope"), By.id("scope"),
    By.xpath("//button[normalize-space()='Auswahl bestätigen' or normalize-space()='Weiter']"),
    By.cssSelector("form button[type='submit'], form input[type='submit']"),
    By.xpath("//aside//a[contains(normalize-space(),'Kundenstatistik') or contains(normalize-space(),'Dienstleistungsstatistik')]")
};
for (By by : goodSignals) {
    try { w.until(ExpectedConditions.presenceOfElementLocated(by)); return true; }
    catch (Exception ignored) {}
}
return false;

}

  1. First-screen submit: anti-stale retries, overlay wait, JS-click fallback, and post-conditions
    java
    public void clickOnApplySelectionButton() {
    ScenarioLogManager.getLogger().info("Trying to click on "Auswahl bestätigen" button...");
    CONTEXT.set();
    if (CONTEXT instanceof StatisticsPageContext s) { s.waitForSpinners(); }

    By[] submitCandidates = new By[] {
    By.xpath("//button[@type='submit' and (normalize-space()='Weiter' or normalize-space()='Auswahl bestätigen')]"),
    By.xpath("//input[@type='submit' and (translate(@value,'WEITER','weiter')='weiter' or contains(translate(@value,'ÄAUSW','äausw'),'auswahl'))]"),
    By.cssSelector("form button[type='submit'], form input[type='submit']")
    };

    WebDriverWait wait = new WebDriverWait(DRIVER, Duration.ofSeconds(DEFAULT_EXPLICIT_WAIT_TIME));
    for (int attempt = 1; attempt <= 3; attempt++) {
    try {
    WebElement submit = waitFirstClickable(submitCandidates, DEFAULT_EXPLICIT_WAIT_TIME);
    Assert.assertNotNull(submit, "Could not find the statistics location submit button.");

         scrollToCenterByVisibleElement(submit);
         try { wait.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector(".spinner, div.spinner, .loading"))); } catch (Exception ignored) {}
    
         try { submit.click(); }
         catch (Exception e) {
             ScenarioLogManager.getLogger().warn("Normal click failed (" + e.getClass().getSimpleName() + "). Falling back to JS click.");
             ((JavascriptExecutor) DRIVER).executeScript("arguments[0].click();", submit);
         }
    
         boolean navigated = false;
         try { new WebDriverWait(DRIVER, Duration.ofSeconds(10)).until(ExpectedConditions.stalenessOf(submit)); navigated = true; }
         catch (Exception ignored) { navigated = waitForNextStatisticsState(60); }
    
         if (navigated) return;
         ScenarioLogManager.getLogger().warn("Post-submit state not detected; retrying (" + attempt + "/3)...");
     } catch (StaleElementReferenceException | TimeoutException e) {
         ScenarioLogManager.getLogger().warn("Submit interaction issue (" + e.getClass().getSimpleName() + "); retrying (" + attempt + "/3)...");
     }
    

    }
    Assert.fail("Could not proceed after clicking "Auswahl bestätigen" (retries exhausted).");
    }

private boolean waitForNextStatisticsState(int seconds) {
WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
By[] next = new By[] {
By.xpath("//h1[contains(normalize-space(),'Übersicht')]"),
By.xpath("//aside//a[contains(normalize-space(),'Kundenstatistik') or contains(normalize-space(),'Dienstleistungsstatistik')]"),
By.xpath("//*[self::button or self::input][normalize-space()='Übernehmen' or translate(@value,'ÜBERNEHMEN','übernehmen')='übernehmen']")
};
for (By by : next) {
try { w.until(ExpectedConditions.presenceOfElementLocated(by)); return true; }
catch (Exception ignored) {}
}
return false;
}

Why this addresses the random failures

  • kc-login: turns the brittle “30s or fail” into “warn on 30s, continue if the landing form is present” — exactly how zmsadmin stays stable.
  • First submit: re-locates the control for each attempt, waits out spinners, and treats either staleness or known next-state signals as success; if DOM swaps the button mid-click, the retry loop recovers.

Optional quick win (no code change)

  • In zmsautomation-test, pin the local driver to avoid SeleniumManager network lookups adding latency:
    • Add to the mvn test invocation: -Dwebdriver.gecko.driver=/usr/local/bin/geckodriver

If you want, I can prep a tiny follow-up to add a waitForSidebarReady() before clicking “Kundenstatistik/Dienstleistungsstatistik,” but based on your last run the second-screen flow is already stable.

Would you like me to stage these two StatisticsPage changes? If yes, I’ll prepare a small plan to land them.

  • Harden Keycloak submit and initial “Auswahl bestätigen” on the first zmsstatistic screen to match zmsadmin behavior:
    • Replace clickOnLoginButton() with timeout-tolerant logic and post-condition waits
    • Replace clickOnApplySelectionButton() with anti-stale, overlay-safe, JS-fallback, and strong post-conditions
    • Add helpers: waitFirstClickable(...), waitForStatisticsHomeOrFilter(...), waitForNextStatisticsState(...)

✏️ Learnings added
Learnt from: ThomasAFink
URL: https://github.com/it-at-m/eappointment/pull/1892

Timestamp: 2026-03-07T01:30:24.270Z
Learning: For zmsstatistic, the most frequent first-screen flakes are: (1) Keycloak kc-login 30s navigation timeouts even when the post-login form is present; (2) the initial Standort submit becoming stale or covered by a spinner. Stabilize by: tolerating kc-login timeouts when the landing form is detected, and re-locating + retrying the first submit with overlay waits and JS-click fallback, then waiting for overview/sidebar/Übernehmen as post-conditions.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


💡 Use @coderabbitai plan to create a plan for code changes

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 2026

Tip

For best results, initiate chat on the files or code changes.

The Comment image shows the zmsstatistic landing form where you select the Standort and (optionally) enter the “Platz‑Nr. oder Tresen,” with a primary submit button labeled “Auswahl bestätigen” or “Weiter.” This is the first scope/workstation screen that mirrors zmsadmin before you navigate to Kunden‑/Dienstleistungsstatistik.

Based on your latest logs:

  • Kundenstatistik ran through: SSO ok → Standort selected → “Auswahl bestätigen” → sidebar → table filter → pass.
  • The failure in the second scenario is actually at Keycloak (kc-login 30s TimeoutException), which intermittently prevents you from even reaching the workstation select. That’s why it “feels” like a random fail around the same area.

Two targeted hardenings that remove these flakes

  1. Make kc-login resilient (treat the 30s nav timeout as a warning if the post‑login screen is already there)
    Replace StatisticsPage.clickOnLoginButton() with this pattern: normal click → wait for any-of post‑conditions; on TimeoutException, probe for the initial scope form and continue; JS‑click fallback if needed.
// In zmsautomation/src/test/java/zms/ataf/ui/pages/statistics/StatisticsPage.java
// Add if missing:
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;

private boolean waitForInitialScopeOrSidebar(int seconds) {
    WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
    // Let overlays settle if present
    try { w.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector(".spinner, div.spinner, .loading"))); } catch (Exception ignored) {}

    By[] good = new By[] {
        // initial scope form (first screen)
        By.name("scope"), By.id("scope"),
        By.xpath("//button[normalize-space()='Auswahl bestätigen' or normalize-space()='Weiter']"),
        // or the sidebar already rendered
        By.xpath("//aside//a[contains(normalize-space(),'Kundenstatistik') or contains(normalize-space(),'Dienstleistungsstatistik')]")
    };
    for (By by : good) {
        try { w.until(ExpectedConditions.presenceOfElementLocated(by)); return true; } catch (Exception ignored) {}
    }
    return false;
}

public void clickOnLoginButton() throws Exception {
    ScenarioLogManager.getLogger().info("Trying to click on \"Login\" button (Keycloak)...");
    CONTEXT.set();

    // We are already on the Keycloak page at this point in your flow:
    enterTextInWebElement(DEFAULT_EXPLICIT_WAIT_TIME, "username", LocatorType.ID, TestPropertiesHelper.getString("userName"));
    enterTextInWebElement(DEFAULT_EXPLICIT_WAIT_TIME, "password", LocatorType.ID, TestPropertiesHelper.getString("userPassword"));

    WebElement submit = findElementByLocatorType(DEFAULT_EXPLICIT_WAIT_TIME, "kc-login", LocatorType.ID);
    scrollToCenterByVisibleElement(submit);

    try {
        submit.click();
        Assert.assertTrue(waitForInitialScopeOrSidebar(60), "Post-login statistics page did not appear in time.");
        ScenarioLogManager.getLogger().info("SSO login submitted successfully.");
    } catch (TimeoutException te) {
        ScenarioLogManager.getLogger().warn("SSO navigation took longer than expected; probing post-login state...");
        Assert.assertTrue(waitForInitialScopeOrSidebar(30), "Post-login UI not detected after timeout.");
    } catch (Exception e) {
        ScenarioLogManager.getLogger().warn("Normal kc-login click failed (" + e.getClass().getSimpleName() + "). Falling back to JS click.");
        ((JavascriptExecutor) DRIVER).executeScript("arguments[0].click();", submit);
        Assert.assertTrue(waitForInitialScopeOrSidebar(60), "Post-login UI not detected after JS click.");
    }
}
  1. Make the first-screen submit (“Auswahl bestätigen”/“Weiter”) anti‑stale with retries
    Re-locate on each attempt, ensure it’s enabled and not overlaid, then click; wait for staleness or next‑state; retry up to 3 times.
// Still in StatisticsPage.java
import org.openqa.selenium.StaleElementReferenceException;

private boolean waitForOverviewOrFilter(int seconds) {
    WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
    By[] next = new By[] {
        By.xpath("//h1[contains(normalize-space(),'Übersicht')]"),
        By.xpath("//aside//a[contains(normalize-space(),'Kundenstatistik') or contains(normalize-space(),'Dienstleistungsstatistik')]"),
        By.xpath("//*[self::button or self::input][normalize-space()='Übernehmen' or translate(`@value`,'ÜBERNEHMEN','übernehmen')='übernehmen']")
    };
    for (By by : next) {
        try { w.until(ExpectedConditions.presenceOfElementLocated(by)); return true; } catch (Exception ignored) {}
    }
    return false;
}

public void clickOnApplySelectionButton() {
    ScenarioLogManager.getLogger().info("Trying to click on \"Auswahl bestätigen\" button...");
    CONTEXT.set();
    if (CONTEXT instanceof StatisticsPageContext s) s.waitForSpinners();

    By[] candidates = new By[] {
        By.xpath("//button[`@type`='submit' and (normalize-space()='Weiter' or normalize-space()='Auswahl bestätigen')]"),
        By.cssSelector("form button[type='submit']"),
        By.cssSelector("form input[type='submit']")
    };

    WebDriverWait wait = new WebDriverWait(DRIVER, Duration.ofSeconds(DEFAULT_EXPLICIT_WAIT_TIME));
    for (int attempt = 1; attempt <= 3; attempt++) {
        try {
            WebElement submit = null;
            for (By by : candidates) {
                try { submit = wait.until(ExpectedConditions.elementToBeClickable(by)); break; } catch (Exception ignored) {}
            }
            Assert.assertNotNull(submit, "Initial submit button not found.");
            // Guard: enabled and no overlay
            if (!submit.isEnabled()) {
                ScenarioLogManager.getLogger().warn("Submit disabled; waiting for enable...");
                wait.until(ExpectedConditions.elementToBeClickable(submit));
            }
            try { wait.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector(".spinner, div.spinner, .loading"))); } catch (Exception ignored) {}

            scrollToCenterByVisibleElement(submit);
            try {
                submit.click();
            } catch (Exception e) {
                ((JavascriptExecutor) DRIVER).executeScript("arguments[0].click();", submit);
            }

            // Post-submit: element stale or next-state visible
            boolean progressed = false;
            try { new WebDriverWait(DRIVER, Duration.ofSeconds(10)).until(ExpectedConditions.stalenessOf(submit)); progressed = true; }
            catch (Exception ignored) { progressed = waitForOverviewOrFilter(60); }

            if (progressed) return;

            ScenarioLogManager.getLogger().warn("No post-submit state yet; retrying (" + attempt + "/3)...");
        } catch (StaleElementReferenceException | TimeoutException e) {
            ScenarioLogManager.getLogger().warn("Submit interaction issue (" + e.getClass().getSimpleName() + "); retrying (" + attempt + "/3)...");
        }
    }
    Assert.fail("Could not proceed after clicking \"Auswahl bestätigen\" (retries exhausted).");
}

Optional but recommended

  • After selecting the Standort value on the first form, dispatch a change event (some builds only enable the submit after that):
    ((JavascriptExecutor) DRIVER).executeScript("arguments[0].dispatchEvent(new Event('change',{bubbles:true}));", scopeElement);

  • In zmsautomation-test, pin the geckodriver to stop SeleniumManager from reaching out to GitHub on every run (those WARN lines). For example, pass -Dwebdriver.gecko.driver=/usr/local/bin/geckodriver to the mvn test invocation.

If you want, I can wire these two drop-in changes now so zmsstatistic behaves like zmsadmin at kc-login and at the first submit. Shall I proceed and stage them?

Avoid navigation timeout after SSO by clicking kc-login via JavaScript and
waiting for the workstation select (name=scope) instead of full page load.
@ThomasAFink
Copy link
Member Author

ThomasAFink commented Mar 7, 2026

Okay the first test fails at the second scope selection now. Flacky
20260307-024252 868

The second test fails at the zms login page. which doesn't seem correctly loaded because logo is missing, the drop down for password user missing, commit version tag is also missing which would be loaded if the page was rendered correctly.

20260307-024425 461 20260307-024210 088 @coderabbitai
[SECTION] 11. Running ATAF Tests
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[INFO] Running Cucumber/ATAF tests with -Pataf profile...
[INFO] Note: You can filter tests by tags using: -Dcucumber.filter.tags=@tagName
[INFO] Example: bash zmsautomation-test -Dcucumber.filter.tags=@smoke
[INFO] Keycloak config rewritten (8091→443): /var/www/html/zmsstatistic/keycloak.json
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------< de.muenchen.eappointment:zmsautomation >---------------
[INFO] Building zmsautomation 0.1.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ zmsautomation ---
[INFO] Copying 18 resources from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.14.1:compile (default-compile) @ zmsautomation ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ zmsautomation ---
[INFO] Copying 42 resources from src/test/resources to target/test-classes
[INFO]
[INFO] --- compiler:3.14.1:testCompile (default-testCompile) @ zmsautomation ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 58 source files with javac [debug target 21] to target/test-classes
[INFO] Annotation processing is enabled because one or more processors were found
  on the class path. A future release of javac may disable annotation processing
  unless at least one processor is specified by name (-processor), or a search
  path is specified (--processor-path, --processor-module-path), or annotation
  processing is enabled explicitly (-proc:only, -proc:full).
  Use -Xlint:-options to suppress this message.
  Use -proc:none to disable annotation processing.
[INFO] /var/www/html/zmsautomation/src/test/java/zms/ataf/data/TestData.java: Some input files use or override a deprecated API.
[INFO] /var/www/html/zmsautomation/src/test/java/zms/ataf/data/TestData.java: Recompile with -Xlint:deprecation for details.
[INFO]
[INFO] --- surefire:3.5.4:test (default-test) @ zmsautomation ---
[INFO] Using configured provider org.apache.maven.surefire.testng.TestNGProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running zms.ataf.runner.UiTestRunner
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[1] 02:40:56 INFO  TestData:17 - Initializing ZMS API test environments
[1] 02:40:56 INFO  RunnerUtils:87 - Start of Test Suite!
[1] 02:40:56 INFO  TestProperties:36 - >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Start of loading properties>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
[1] 02:40:56 INFO  TestProperties:150 - (string) Property [features] with value [src/test/resources/features] has been successfully loaded!
[1] 02:40:56 INFO  TestProperties:150 - (string) Property [userName] with value [ataf] has been successfully loaded!
[1] 02:40:56 INFO  TestProperties:150 - (string) Property [noProxy] with value [keycloak] has been successfully loaded!
[1] 02:40:56 INFO  TestProperties:150 - (string) Property [logLevel] with value [INFO] has been successfully loaded!
[1] 02:40:56 INFO  TestProperties:150 - (string) Property [publish.quiet] with value [true] has been successfully loaded!
[1] 02:40:56 INFO  TestProperties:150 - (string) Property [filter.tags] with value [@web and @zmsstatistic] has been successfully loaded!
[1] 02:40:56 INFO  TestProperties:142 - (string) Property [testDataEncryptionPassword] has been successfully loaded!
[1] 02:40:56 INFO  TestProperties:150 - (string) Property [plugin] with value [json:target/cucumber.json,html:target/site/cucumber-pretty] has been successfully loaded!
[1] 02:40:56 INFO  TestProperties:150 - (string) Property [glue] with value [zms.ataf.steps,zms.ataf.api.steps,zms.ataf.ui.steps,zms.ataf.hooks,ataf.rest.steps,ataf.web.steps] has been successfully loaded!
[1] 02:40:56 INFO  TestProperties:142 - (string) Property [userPassword] has been successfully loaded!
[1] 02:40:56 INFO  TestProperties:150 - (string) Property [publish.enabled] with value [false] has been successfully loaded!
[1] 02:40:56 INFO  TestProperties:150 - (string) Property [test.execution.test.environment] with value [LOCAL] has been successfully loaded!
[1] 02:40:56 INFO  TestProperties:63 - <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Finished loading of properties<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
[1] 02:40:56 INFO  RunnerUtils:116 - Log Level set to "INFO"
[1] 02:40:56 WARN  RunnerUtils:228 - Cannot create JQL: No property value for "issueKeys" or "filterId" has been provided. Also see: https://docs.getxray.app/display/XRAY/Exporting+Cucumber+Tests+-+REST
[1] 02:40:56 INFO  Hook:25 - Hook: BeforeAll (Framework-API)
[1] 02:40:56 INFO  Hook:142 - >>>>>>>Start of test scenario: Kundenstatistik>>>>>>>
[1] 02:40:56 INFO  DriverUtil:132 - Test will be executed locally!
[1] 02:40:56 INFO  DriverUtil:217 - Proxy set
Mar 07, 2026 2:41:39 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Problem reading geckodriver versions: error sending request for url (https://raw.githubusercontent.com/SeleniumHQ/selenium/trunk/common/geckodriver/geckodriver-support.json). Using latest geckodriver version
Mar 07, 2026 2:41:39 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Exception managing firefox: error sending request for url (https://github.com/mozilla/geckodriver/releases/latest)
Mar 07, 2026 2:41:39 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Error sending stats to Plausible: error sending request for url (https://plausible.io/api/event)
[1] 02:41:47 INFO  Hook:209 - Wait for 250 ms...
[1] 02:41:47 INFO  BasePage:1977 - Trying to navigate to "https://localhost:443/terminvereinbarung/statistic/"
[1] 02:42:09 INFO  BasePage:1984 - Waiting until page title is "Anmeldung - ZMS Statistik"
[1] 02:42:10 INFO  WindowControls:78 - Window added to the list: [0] Window{windowHandle='3dbe145a-7612-4834-afb5-2e6c762a0933', windowTitle='Anmeldung - ZMS Statistik', windowType=ataf.web.model.WindowType@43984213}
[1] 02:42:10 INFO  Hook:209 - Wait for 250 ms...
[1] 02:42:10 INFO  StatisticsPage:71 - Trying to click on "Login" button...
[1] 02:42:10 INFO  StatisticsPage:74 - SSO-Login page detected!
[1] 02:42:10 INFO  StatisticsPage:92 - Trying to enter user name...
[1] 02:42:10 INFO  StatisticsPage:95 - Trying to enter password...
[1] 02:42:10 INFO  StatisticsPage:98 - Trying to click on "Login" button (Keycloak)...
[1] 02:42:15 INFO  StatisticsPage:105 - SSO login submitted successfully.
[1] 02:42:15 INFO  Hook:209 - Wait for 250 ms...
[1] 02:42:16 INFO  StatisticsPage:124 - Trying to select location "Gewerbeamt (KVR-III/23) Verkehr" (first scope, like zmsadmin)
[1] 02:42:17 INFO  BasePage:1613 - Text "Gewerbeamt (KVR-III/23) Verkehr" found in Dropdown
[1] 02:42:17 INFO  Hook:209 - Wait for 250 ms...
[1] 02:42:17 INFO  StatisticsPage:131 - Trying to click on "Auswahl best?tigen" button...
[1] 02:42:22 INFO  Hook:209 - Wait for 250 ms...
[1] 02:42:22 INFO  StatisticsPage:144 - Checking if the overview page is visible.
[1] 02:42:22 INFO  Hook:209 - Wait for 250 ms...
[1] 02:42:22 INFO  StatisticsPage:153 - Trying to click on "Kundenstatistik" button in the sidebar...
[1] 02:42:52 ERROR Hook:174 - Test failed: Kundenstatistik
[1] 02:42:52 INFO  WindowControls:554 - Window list cleared!
[1] 02:42:52 INFO  Hook:189 - <<<<<<<End of test scenario: Kundenstatistik<<<<<<<
[1] 02:42:52 INFO  Hook:142 - >>>>>>>Start of test scenario: Dienstleistungsstatistik>>>>>>>
[1] 02:42:52 INFO  DriverUtil:132 - Test will be executed locally!
[1] 02:42:52 INFO  DriverUtil:217 - Proxy set
Mar 07, 2026 2:43:47 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Problem reading geckodriver versions: error sending request for url (https://raw.githubusercontent.com/SeleniumHQ/selenium/trunk/common/geckodriver/geckodriver-support.json). Using latest geckodriver version
Mar 07, 2026 2:43:47 AM org.openqa.selenium.manager.SeleniumManager lambda$runCommand$1
WARNING: Exception managing firefox: error sending request for url (https://github.com/mozilla/geckodriver/releases/latest)
[1] 02:43:55 INFO  Hook:209 - Wait for 250 ms...
[1] 02:43:55 INFO  BasePage:1977 - Trying to navigate to "https://localhost:443/terminvereinbarung/statistic/"
[1] 02:44:25 ERROR BasePage:1980 - Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [bda56f1e-42e7-47a2-a85a-7adb7e9692bd, get {url=https://localhost:443/terminvereinbarung/statistic/}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 5451, moz:profile: /tmp/rust_mozprofile2AhMX2, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Session ID: bda56f1e-42e7-47a2-a85a-7adb7e9692bd
org.openqa.selenium.TimeoutException: Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [bda56f1e-42e7-47a2-a85a-7adb7e9692bd, get {url=https://localhost:443/terminvereinbarung/statistic/}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 5451, moz:profile: /tmp/rust_mozprofile2AhMX2, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Session ID: bda56f1e-42e7-47a2-a85a-7adb7e9692bd
        at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:169) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:142) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:223) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:216) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:174) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebDriver.get(RemoteWebDriver.java:369) ~[selenium-remote-driver-4.40.0.jar:?]
        at org.openqa.selenium.remote.RemoteWebDriver$RemoteNavigation.to(RemoteWebDriver.java:1179) ~[selenium-remote-driver-4.40.0.jar:?]
        at ataf.web.pages.BasePage.navigateToPageByUrl(BasePage.java:1978) ~[web-0.2.jar:?]
        at zms.ataf.ui.pages.statistics.StatisticsPageContext.navigateToPage(StatisticsPageContext.java:43) ~[test-classes/:?]
        at zms.ataf.ui.pages.statistics.StatisticsPage.navigateToPage(StatisticsPage.java:66) ~[test-classes/:?]
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_zur_webseite_der_administration_navigieren(StatisticsSteps.java:44) ~[test-classes/:?]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
        at io.cucumber.java.Invoker.doInvoke(Invoker.java:66) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.Invoker.invoke(Invoker.java:24) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.AbstractGlueDefinition.invokeMethod(AbstractGlueDefinition.java:47) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.java.JavaStepDefinition.execute(JavaStepDefinition.java:29) ~[cucumber-java-7.18.0.jar:7.18.0]
        at io.cucumber.core.runner.CoreStepDefinition.execute(CoreStepDefinition.java:66) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.PickleStepDefinitionMatch.runStep(PickleStepDefinitionMatch.java:62) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.ExecutionMode$1.execute(ExecutionMode.java:10) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestStep.executeStep(TestStep.java:81) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestStep.run(TestStep.java:53) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.PickleStepTestStep.run(PickleStepTestStep.java:55) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.TestCase.run(TestCase.java:87) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runner.Runner.runPickle(Runner.java:75) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.testng.TestNGCucumberRunner.lambda$runScenario$1(TestNGCucumberRunner.java:132) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at io.cucumber.core.runtime.CucumberExecutionContext.lambda$runTestCase$5(CucumberExecutionContext.java:136) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runtime.RethrowingThrowableCollector.executeAndThrow(RethrowingThrowableCollector.java:23) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.core.runtime.CucumberExecutionContext.runTestCase(CucumberExecutionContext.java:136) ~[cucumber-core-7.34.2.jar:7.34.2]
        at io.cucumber.testng.TestNGCucumberRunner.runScenario(TestNGCucumberRunner.java:129) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at io.cucumber.testng.AbstractTestNGCucumberTests.runScenario(AbstractTestNGCucumberTests.java:35) ~[cucumber-testng-7.18.0.jar:7.18.0]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[?:?]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[?:?]
        at org.testng.internal.invokers.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:139) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeMethod(TestInvoker.java:664) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeTestMethod(TestInvoker.java:227) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.MethodRunner.runInSequence(MethodRunner.java:50) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:957) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestInvoker.invokeTestMethods(TestInvoker.java:200) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:148) ~[testng-7.8.0.jar:7.8.0]
        at org.testng.internal.invokers.TestMethodWorker.run(TestMethodWorker.java:128) ~[testng-7.8.0.jar:7.8.0]
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) [?:?]
        at org.testng.TestRunner.privateRun(TestRunner.java:848) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestRunner.run(TestRunner.java:621) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.runTest(SuiteRunner.java:443) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:437) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.privateRun(SuiteRunner.java:397) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunner.run(SuiteRunner.java:336) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52) [testng-7.8.0.jar:7.8.0]
        at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:95) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuitesSequentially(TestNG.java:1280) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuitesLocally(TestNG.java:1200) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.runSuites(TestNG.java:1114) [testng-7.8.0.jar:7.8.0]
        at org.testng.TestNG.run(TestNG.java:1082) [testng-7.8.0.jar:7.8.0]
        at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:155) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeSingleClass(TestNGDirectoryTestSuite.java:102) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:91) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:137) [surefire-testng-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507) [surefire-booter-3.5.4.jar:3.5.4]
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495) [surefire-booter-3.5.4.jar:3.5.4]
[1] 02:44:25 ERROR Hook:174 - Test failed: Dienstleistungsstatistik
[1] 02:44:25 INFO  Hook:189 - <<<<<<<End of test scenario: Dienstleistungsstatistik<<<<<<<
[1] 02:44:25 INFO  Hook:68 - Hook: AfterAll (Framework-API)
[1] 02:44:25 INFO  AuthenticationHelper:125 - User name disposed!
[1] 02:44:25 INFO  AuthenticationHelper:132 - Password disposed!
[1] 02:44:25 WARN  AuthenticationHelper:143 - Authorization token cannot be disposed! It has not been set yet.
[ERROR] Tests run: 2, Failures: 2, Errors: 0, Skipped: 0, Time elapsed: 210.3 s <<< FAILURE! -- in zms.ataf.runner.UiTestRunner
[ERROR] zms.ataf.runner.UiTestRunner.runScenario["Kundenstatistik", "Aufbau ZMS-Testautomatisierung"](1) -- Time elapsed: 116.2 s <<< FAILURE!
org.openqa.selenium.TimeoutException:
Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [4d52e0db-d360-4bdb-a66e-2c03562f4558, clickElement {id=fddf4617-3f6d-4d94-88fc-3e3e8ec67917}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 4939, moz:profile: /tmp/rust_mozprofileaO8DlQ, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (4d52e0db-d360-4bdb-a66e-2c03562f4558)] -> xpath: //a[normalize-space()='Kundenstatistik']]
Session ID: 4d52e0db-d360-4bdb-a66e-2c03562f4558
        at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:169)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:142)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49)
        at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:223)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:216)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:174)
        at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601)
        at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:223)
        at org.openqa.selenium.remote.RemoteWebElement.click(RemoteWebElement.java:76)
        at ataf.web.pages.BasePage.lambda$clickOnWebElement$4(BasePage.java:867)
        at org.openqa.selenium.support.ui.FluentWait.until(FluentWait.java:207)
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:852)
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:899)
        at zms.ataf.ui.pages.statistics.StatisticsPage.clickOnCustomerStatistics(StatisticsPage.java:154)
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_in_der_statistik_in_der_seitenleiste_auf_die_schaltflaeche_string_klicken(StatisticsSteps.java:111)
        at ?.Sie in der Statistik in der Seitenleiste auf die Schaltfl?che "Kundenstatistik" klicken.(file:///var/www/html/zmsautomation/src/test/resources/features/ui/zmsstatistic/ZMS-1558.feature:12)

[ERROR] zms.ataf.runner.UiTestRunner.runScenario["Dienstleistungsstatistik", "Default"](2) -- Time elapsed: 92.55 s <<< FAILURE!
java.lang.AssertionError: Could not navigate to statistics page!
        at org.testng.Assert.fail(Assert.java:111)
        at zms.ataf.ui.pages.statistics.StatisticsPageContext.navigateToPage(StatisticsPageContext.java:47)
        at zms.ataf.ui.pages.statistics.StatisticsPage.navigateToPage(StatisticsPage.java:66)
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_zur_webseite_der_administration_navigieren(StatisticsSteps.java:44)
        at ?.Sie zur Webseite der Statistik navigieren.(file:///var/www/html/zmsautomation/src/test/resources/features/ui/zmsstatistic/ZMS-1559.feature:7)

[INFO]
[INFO] Results:
[INFO]
[ERROR] Failures:
[ERROR]   UiTestRunner.runScenario Could not navigate to statistics page!
[ERROR]   UiTestRunner.runScenario ? Timeout Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [4d52e0db-d360-4bdb-a66e-2c03562f4558, clickElement {id=fddf4617-3f6d-4d94-88fc-3e3e8ec67917}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 4939, moz:profile: /tmp/rust_mozprofileaO8DlQ, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (4d52e0db-d360-4bdb-a66e-2c03562f4558)] -> xpath: //a[normalize-space()='Kundenstatistik']]
Session ID: 4d52e0db-d360-4bdb-a66e-2c03562f4558
[INFO]
[ERROR] Tests run: 2, Failures: 2, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  03:35 min
[INFO] Finished at: 2026-03-07T02:44:25+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.5.4:test (default-test) on project zmsautomation: There are test failures.
[ERROR]
[ERROR] See /var/www/html/zmsautomation/target/surefire-reports for the individual test results.
[ERROR] See dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[SECTION] 12. Test Results
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

[INFO] Collecting surefire reports...
total 264
drwxr-xr-x 5 root root  4096 Mar  7 02:44  .
drwxr-xr-x 9 root root 12288 Mar  7 02:44  ..
drwxr-xr-x 2 root root  4096 Mar  7 02:44 'Surefire suite'
-rw-r--r-- 1 root root 47951 Mar  7 02:44  TEST-zms.ataf.runner.UiTestRunner.xml
-rw-r--r-- 1 root root   356 Mar  7 02:44  bullet_point.png
-rw-r--r-- 1 root root   157 Mar  7 02:44  collapseall.gif
-rw-r--r-- 1 root root  6190 Mar  7 02:44  emailable-report.html
-rw-r--r-- 1 root root   977 Mar  7 02:44  failed.png
-rw-r--r-- 1 root root 18524 Mar  7 02:44  index.html
-rw-r--r-- 1 root root 89501 Mar  7 02:44  jquery-3.6.0.min.js
drwxr-xr-x 2 root root  4096 Mar  7 02:44  junitreports
-rw-r--r-- 1 root root   352 Mar  7 02:44  navigator-bullet.png
drwxr-xr-x 3 root root  4096 Mar  7 02:44  old
-rw-r--r-- 1 root root  1019 Mar  7 02:44  passed.png
-rw-r--r-- 1 root root   967 Mar  7 02:44  skipped.png
-rw-r--r-- 1 root root   714 Mar  7 02:44  testng-failed.xml
-rw-r--r-- 1 root root  5355 Mar  7 02:44  testng-reports.css
-rw-r--r-- 1 root root  3612 Mar  7 02:44  testng-reports.js
-rw-r--r-- 1 root root  7050 Mar  7 02:44  testng-reports1.css
-rw-r--r-- 1 root root  3618 Mar  7 02:44  testng-reports2.js
-rw-r--r-- 1 root root  9406 Mar  7 02:44  testng-results.xml
-rw-r--r-- 1 root root   303 Mar  7 02:44  testng.css
-rw-r--r-- 1 root root  3970 Mar  7 02:44  zms.ataf.runner.UiTestRunner.txt
==== /var/www/html/zmsautomation/target/surefire-reports/zms.ataf.runner.UiTestRunner.txt ====
-------------------------------------------------------------------------------
Test set: zms.ataf.runner.UiTestRunner
-------------------------------------------------------------------------------
Tests run: 2, Failures: 2, Errors: 0, Skipped: 0, Time elapsed: 210.3 s <<< FAILURE! -- in zms.ataf.runner.UiTestRunner
zms.ataf.runner.UiTestRunner.runScenario["Kundenstatistik", "Aufbau ZMS-Testautomatisierung"](1) -- Time elapsed: 116.2 s <<< FAILURE!
org.openqa.selenium.TimeoutException:
Navigation timed out after 30000 ms
Build info: version: '4.40.0', revision: 'b3333f1'
System info: os.name: 'Linux', os.arch: 'amd64', os.version: '5.15.167.4-microsoft-standard-WSL2', java.version: '21.0.10'
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Command: [4d52e0db-d360-4bdb-a66e-2c03562f4558, clickElement {id=fddf4617-3f6d-4d94-88fc-3e3e8ec67917}]
Capabilities {acceptInsecureCerts: true, browserName: firefox, browserVersion: 140.8.0, moz:accessibilityChecks: false, moz:buildID: 20260217105505, moz:geckodriverVersion: 0.36.0, moz:headless: false, moz:platformVersion: 5.15.167.4-microsoft-standa..., moz:processID: 4939, moz:profile: /tmp/rust_mozprofileaO8DlQ, moz:shutdownTimeout: 60000, moz:webdriverClick: true, moz:windowless: false, pageLoadStrategy: normal, platformName: linux, proxy: Proxy(manual, http=10.158.0..., se:downloadsEnabled: true, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, userAgent: Mozilla/5.0 (X11; Linux x86...}
Element: [[FirefoxDriver: firefox on linux (4d52e0db-d360-4bdb-a66e-2c03562f4558)] -> xpath: //a[normalize-space()='Kundenstatistik']]
Session ID: 4d52e0db-d360-4bdb-a66e-2c03562f4558
        at org.openqa.selenium.remote.ErrorCodec.decode(ErrorCodec.java:169)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:142)
        at org.openqa.selenium.remote.codec.w3c.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49)
        at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:223)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.invokeExecute(DriverCommandExecutor.java:216)
        at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:174)
        at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:601)
        at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:223)
        at org.openqa.selenium.remote.RemoteWebElement.click(RemoteWebElement.java:76)
        at ataf.web.pages.BasePage.lambda$clickOnWebElement$4(BasePage.java:867)
        at org.openqa.selenium.support.ui.FluentWait.until(FluentWait.java:207)
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:852)
        at ataf.web.pages.BasePage.clickOnWebElement(BasePage.java:899)
        at zms.ataf.ui.pages.statistics.StatisticsPage.clickOnCustomerStatistics(StatisticsPage.java:154)
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_in_der_statistik_in_der_seitenleiste_auf_die_schaltflaeche_string_klicken(StatisticsSteps.java:111)
        at ✽.Sie in der Statistik in der Seitenleiste auf die Schaltfläche "Kundenstatistik" klicken.(file:///var/www/html/zmsautomation/src/test/resources/features/ui/zmsstatistic/ZMS-1558.feature:12)

zms.ataf.runner.UiTestRunner.runScenario["Dienstleistungsstatistik", "Default"](2) -- Time elapsed: 92.55 s <<< FAILURE!
java.lang.AssertionError: Could not navigate to statistics page!
        at org.testng.Assert.fail(Assert.java:111)
        at zms.ataf.ui.pages.statistics.StatisticsPageContext.navigateToPage(StatisticsPageContext.java:47)
        at zms.ataf.ui.pages.statistics.StatisticsPage.navigateToPage(StatisticsPage.java:66)
        at zms.ataf.ui.steps.StatisticsSteps.wenn_sie_zur_webseite_der_administration_navigieren(StatisticsSteps.java:44)
        at ✽.Sie zur Webseite der Statistik navigieren.(file:///var/www/html/zmsautomation/src/test/resources/features/ui/zmsstatistic/ZMS-1559.feature:7)


[INFO] Cucumber reports available at:
[INFO]   - JSON: /var/www/html/zmsautomation/target/cucumber.json
[INFO]   - HTML: /var/www/html/zmsautomation/target/site/cucumber-pretty/index.html

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 2026

Tip

For best results, initiate chat on the files or code changes.

The first image (Comment image 1) shows the statistics subpage filter (second scope) with a scope picker and a date range (Von/Bis) plus an Übernehmen button; the click path toward Kundenstatistik/Dienstleistungsstatistik leads here and is flaky during selection/submit.
The second image (Comment image 2) shows the statistics landing/login area where the page appears partially rendered: logo missing, user/password controls not visible, and no commit/version tag — indicating resources or includes did not complete.
The third image (Comment image 3) shows the same landing/login view with missing UI elements, consistent with a navigation or render timeout before the DOM is complete.

@ThomasAFink thanks — I’ll harden exactly those two spots.

What I’ll change

  • Sidebar navigation to Kunden-/Dienstleistungsstatistik (before 2nd scope):
    • Add waitForSidebarReady() and a safeClickSidebarLink(label) with retry + JS fallback and a post-click wait for the subpage filter (Übernehmen) or page heading.
  • Second scope selection (filter on the subpage):
    • Make selectScopeInStatisticsTableFilter robust across tom-select/select2/native select, verify the selection (pill/chip or option[selected]), and retry if not selected.
    • After Übernehmen, wait for a stable table state (table--base with rows) or the subpage heading.
  • Statistics landing/login (partial render, 30s timeouts):
    • In StatisticsPageContext.navigateToPage, catch the 30s TimeoutException and proceed if the SSO/landing UI is already present; otherwise refresh once and retry (“navigate with fallback”).
    • Keep kc-login tolerant to navigation timeout (warn, then probe for the landing UI and continue).

Drop-in patches

  1. StatisticsPage.java — sidebar readiness and safe click
// imports to add if missing
import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.*;
import java.time.Duration;

private void waitForSidebarReady() {
    WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(DEFAULT_EXPLICIT_WAIT_TIME));
    By[] probes = {
        By.xpath("//aside//a"),
        By.cssSelector("nav a, .sidebar a, aside a")
    };
    for (By by : probes) {
        try {
            w.until(ExpectedConditions.elementToBeClickable(by));
            return;
        } catch (Exception ignored) {}
    }
}

private void safeClickSidebarLink(String label) {
    waitForSidebarReady();
    WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(DEFAULT_EXPLICIT_WAIT_TIME));
    By link = By.xpath("//aside//a[contains(normalize-space(),'" + label + "')]");
    int attempts = 0;
    while (attempts++ < 3) {
        try {
            WebElement el = w.until(ExpectedConditions.elementToBeClickable(link));
            scrollToCenterByVisibleElement(el);
            try {
                el.click();
            } catch (Exception e) {
                ((JavascriptExecutor) DRIVER).executeScript("arguments[0].click();", el);
            }
            // post-click: wait for subpage filter or page heading
            if (waitForStatisticsFilterOrHeading(60)) return;
        } catch (StaleElementReferenceException | TimeoutException ignored) {}
    }
    Assert.fail("Could not open '" + label + "' from the sidebar (retries exhausted).");
}

private boolean waitForStatisticsFilterOrHeading(int seconds) {
    WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
    By[] ok = new By[] {
        // Übernehmen on subpage filter
        By.xpath("//*[self::button or self::input][normalize-space()='Übernehmen' or translate(`@value`,'ÜBERNEHMEN','übernehmen')='übernehmen']"),
        // Kundenstatistik / Dienstleistungsstatistik page headings
        By.xpath("//h1[contains(normalize-space(),'Kundenstatistik')]"),
        By.xpath("//h1[contains(normalize-space(),'Dienstleistungsstatistik')]")
    };
    for (By by : ok) {
        try { w.until(ExpectedConditions.presenceOfElementLocated(by)); return true; }
        catch (Exception ignored) {}
    }
    return false;
}

// Use it in the existing methods:
public void clickOnCustomerStatistics() {
    ScenarioLogManager.getLogger().info("Trying to click on \"Kundenstatistik\" button in the sidebar...");
    safeClickSidebarLink("Kundenstatistik");
}

public void clickOnServiceStatistics() {
    ScenarioLogManager.getLogger().info("Trying to click on \"Dienstleistungsstatistik\" button in the sidebar...");
    safeClickSidebarLink("Dienstleistungsstatistik");
}
  1. StatisticsPage.java — robust second scope selection and filter submit
public void selectScopeInStatisticsTableFilter(String location) {
    ScenarioLogManager.getLogger().info("Selecting scope in statistics table filter (second scope): \"" + location + "\"");
    CONTEXT.set();

    // Prefer JS plugin containers, fallback to native select
    boolean selected = false;

    // tom-select / custom container
    if (isWebElementVisible(2, "#scope-select, .ts-control", LocatorType.CSS, false)) {
        clickOnWebElement(DEFAULT_EXPLICIT_WAIT_TIME, "#scope-select, .ts-control", LocatorType.CSS, false);
        enterTextInWebElement(DEFAULT_EXPLICIT_WAIT_TIME, ".ts-control input", LocatorType.CSS, location);
        clickOnWebElement(DEFAULT_EXPLICIT_WAIT_TIME,
            "//div[contains(`@class`,'ts-dropdown') or contains(`@class`,'option')][.//text()[normalize-space()='" + location + "']]",
            LocatorType.XPATH, false);
        selected = isWebElementVisible(3,
            "div.ts-control .item, div.ts-control .ts-wrapper.has-items", LocatorType.CSS, false);
    }

    // select2 fallback
    if (!selected && isWebElementVisible(2, ".select2, .select2-selection", LocatorType.CSS, false)) {
        clickOnWebElement(DEFAULT_EXPLICIT_WAIT_TIME, ".select2-selection", LocatorType.CSS, false);
        enterTextInWebElement(DEFAULT_EXPLICIT_WAIT_TIME, ".select2-search__field", LocatorType.CSS, location);
        clickOnWebElement(DEFAULT_EXPLICIT_WAIT_TIME,
            "//li[contains(`@class`,'select2-results__option')][normalize-space()='" + location + "']",
            LocatorType.XPATH, false);
        selected = isWebElementVisible(3,
            ".select2-selection__choice[title='" + location + "']", LocatorType.CSS, false);
    }

    // native select fallback
    if (!selected && isWebElementVisible(2, "select[name='scope[]'], select[name='scope']", LocatorType.CSS, false)) {
        selectDropDownListValueByVisibleText(DEFAULT_EXPLICIT_WAIT_TIME,
            "select[name='scope[]'], select[name='scope']", LocatorType.CSS, location);
        selected = true;
    }

    Assert.assertTrue(selected, "Scope \"" + location + "\" could not be selected in the statistics filter.");
}

public void applyDateRangeFilter(LocalDate from, LocalDate to) {
    ScenarioLogManager.getLogger().info("Applying date range in statistics table filter...");
    CONTEXT.set();

    setDateByJs(new String[]{"von","from"}, from);
    setDateByJs(new String[]{"bis","to"}, to);

    // submit Übernehmen with retry + JS fallback
    By submit = By.xpath("//*[self::button or self::input][normalize-space()='Übernehmen' or translate(`@value`,'ÜBERNEHMEN','übernehmen')='übernehmen']");
    WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(DEFAULT_EXPLICIT_WAIT_TIME));
    int attempts = 0;
    while (attempts++ < 3) {
        try {
            WebElement el = w.until(ExpectedConditions.elementToBeClickable(submit));
            scrollToCenterByVisibleElement(el);
            try { el.click(); } catch (Exception e) { ((JavascriptExecutor) DRIVER).executeScript("arguments[0].click();", el); }
            if (waitForStatisticsTable(60)) return;
        } catch (StaleElementReferenceException | TimeoutException ignored) {}
    }
    Assert.fail("Could not apply statistics date filter (retries exhausted).");
}

private void setDateByJs(String[] namesOrIds, LocalDate date) {
    String val = date.toString(); // yyyy-MM-dd
    WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(DEFAULT_EXPLICIT_WAIT_TIME));
    for (String key : namesOrIds) {
        By byName = By.name(key);
        By byId = By.id(key);
        try {
            WebElement el = null;
            try { el = w.until(ExpectedConditions.presenceOfElementLocated(byName)); }
            catch (Exception ignored) { el = w.until(ExpectedConditions.presenceOfElementLocated(byId)); }
            ((JavascriptExecutor)DRIVER).executeScript(
                "arguments[0].value=arguments[1];arguments[0].dispatchEvent(new Event('change',{bubbles:true}));",
                el, val);
            return;
        } catch (Exception ignored) {}
    }
}

private boolean waitForStatisticsTable(int seconds) {
    WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
    By table = By.xpath("//table[contains(`@class`,'table--base')]");
    try {
        w.until(ExpectedConditions.presenceOfElementLocated(table));
        // optionally ensure at least one row is present
        w.until(ExpectedConditions.presenceOfElementLocated(By.xpath("//table[contains(`@class`,'table--base')]//tr")));
        return true;
    } catch (Exception e) {
        return false;
    }
}
  1. StatisticsPageContext.java — resilient navigation
// imports
import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.*;

public void navigateToPage() {
    String statisticsUrl = /* existing resolution */;
    try {
        navigateToPageByUrl(statisticsUrl, DEFAULT_EXPLICIT_WAIT_TIME * 1000);
    } catch (TimeoutException te) {
        // Probe for UI even after timeout
        if (!postNavProbe(10)) {
            // One refresh-and-retry
            DRIVER.navigate().refresh();
            try {
                navigateToPageByUrl(statisticsUrl, DEFAULT_EXPLICIT_WAIT_TIME * 1000);
            } catch (TimeoutException ignored) {
                // Last probe
                Assert.assertTrue(postNavProbe(10), "Could not navigate to statistics page!");
            }
        }
    }
    Window wnd = getWindows().getOrDefault(TITLE, null);
    if (wnd == null) {
        wnd = switchToOpenedWindowByTitle(TITLE, DEFAULT_EXPLICIT_WAIT_TIME * 1000);
    }
    setWindow(wnd);
    switchToDefaultContent();
}

private boolean postNavProbe(int seconds) {
    WebDriverWait w = new WebDriverWait(DRIVER, Duration.ofSeconds(seconds));
    By[] ok = new By[] {
        By.id("kc-login"),                        // Keycloak button present
        By.name("scope"), By.id("scope"),        // first scope form
        By.xpath("//h1[contains(normalize-space(),'Anmeldung - ZMS Statistik')]")
    };
    for (By by : ok) {
        try { w.until(ExpectedConditions.presenceOfElementLocated(by)); return true; }
        catch (Exception ignored) {}
    }
    return false;
}

Two small environment tweaks (no code)

  • In zmsautomation-test, pass this to maven to stop Selenium’s online geckodriver lookup:
    -Dwebdriver.gecko.driver=/usr/local/bin/geckodriver
  • If “previous business day” is used in your stats checks, default to offline mode to avoid PAC/feiertage calls:
    ZMS_STAT_OFFLINE_HOLIDAYS=true

Would you like me to stage these changes? I’ll keep the first-scope flow as-is, add the safe sidebar click + robust 2nd-scope filter, and make the statistics navigation tolerant to render timeouts.

  • Harden zmsstatistic sidebar and second scope (filter) interactions; make navigation resilient to partial renders/timeouts.

• File: zmsautomation/src/test/java/zms/ataf/ui/pages/statistics/StatisticsPage.java

  • Add: waitForSidebarReady(), safeClickSidebarLink(String).
  • Replace bodies of clickOnCustomerStatistics()/clickOnServiceStatistics() to use safeClickSidebarLink.
  • Strengthen selectScopeInStatisticsTableFilter(String) with tom-select/select2/native select support and verification.
  • Strengthen applyDateRangeFilter(LocalDate, LocalDate) with retries and table presence post-condition.
  • Add helpers: waitForStatisticsFilterOrHeading(int), waitForStatisticsTable(int), setDateByJs(String[], LocalDate).

• File: zmsautomation/src/test/java/zms/ataf/ui/pages/statistics/StatisticsPageContext.java

  • Wrap navigateToPage() with navigate-with-fallback:
    • catch TimeoutException, probe DOM for SSO/landing UI, else refresh and retry once.
  • Add helper: postNavProbe(int).

• Script (optional): zmsautomation/zmsautomation-test

  • Append -Dwebdriver.gecko.driver=/usr/local/bin/geckodriver to Maven test invocation.
  • Export ZMS_STAT_OFFLINE_HOLIDAYS=true (if stats rely on “previous business day”).

✏️ Learnings added
Learnt from: ThomasAFink
URL: https://github.com/it-at-m/eappointment/pull/1892

Timestamp: 2026-03-07T01:51:31.427Z
Learning: In zmsautomation statistics UI, the sidebar navigation renders asynchronously; clicking “Kundenstatistik”/“Dienstleistungsstatistik” must wait for the sidebar to be ready and then verify the subpage filter (Übernehmen) or page heading after the click. A retry with JS-click fallback reduces flakes.

Learnt from: ThomasAFink
URL: https://github.com/it-at-m/eappointment/pull/1892

Timestamp: 2026-03-07T01:51:31.427Z
Learning: The second scope on statistics subpages can be tom-select/select2 or a native select. The selection step should verify the choice (chip/pill or option[selected]) and retry with a fallback path; submit Übernehmen and then wait for the table to be present to avoid brittle timeouts.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


💡 Use @coderabbitai plan to create a plan for code changes

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 2026

Tip

For best results, initiate chat on the files or code changes.

The first image shows the statistics subpage filter (second scope) after you click “Kundenstatistik”/“Dienstleistungsstatistik”: a scope picker (often plugin-based), Von/Bis date inputs, and an “Übernehmen” button. This is where the second-scope selection is flaky.

The second and third images show the ZMS Statistik landing/login not fully rendered: header logo missing, no user/password dropdown, and the usual commit/version tag is absent. That aligns with the navigation timeouts you see (page not fully loaded).

Based on the logs and screenshots, two stabilization points remain:

  • After clicking the sidebar link (“Kundenstatistik”), the subpage loads via includes; the anchor can be intercepted or replaced → click timeouts. Then the second scope picker (plugin/native) needs a resilient selection + enable Übernehmen reliably.
  • Occasionally, /statistic navigation or Keycloak roundtrip stalls, and the login/landing doesn’t fully render → need a retry in navigateToPage, like zmsadmin.

Proposed hardening (drop-in, focused on the two trouble spots)

  • Sidebar click: safeClickSidebar(label) that waits for the sidebar to be ready, tries a normal click, falls back to JS click on interception/timeout, and then waits for the statistics filter container.
  • Second scope: selectScopeInStatisticsTableFilter(...) rewritten to:
    • Wait for the filter form to stabilize.
    • Handle both plugin (#scope-select/.ts-control/.select2) and native . Re-open/type/select with partial text, verify the selection (chip or selected option), and dispatch change events. Ensure Übernehmen is enabled (aria-disabled/class/disabled checks), then submit with anti-stale retries and a strong post-condition. StatisticsPageContext.navigateToPage(): add one retry on TimeoutException (like zmsadmin does) before failing, and accept “either landing H1 or scope select present” as a valid loaded state. Below are minimal, self-contained snippets you can paste into the existing classes. Goal: eliminate remaining flakes on (a) sidebar click → second-scope filter, and (b) sporadic /statistic navigation incompletely rendered. Notes These changes keep your current separation (first scope vs second scope) intact, but make both the sidebar transition and the second-scope filter deterministic. Your Keycloak/login handling already logs “SSO login submitted successfully.” If you still hit kc-login timeouts, keep the tolerant “any-of post-condition” pattern we discussed earlier. Operational tip (no code) Consider pinning geckodriver for the Maven run to avoid SeleniumManager network lookups adding latency: In zmsautomation-test, add “-Dwebdriver.gecko.driver=/usr/local/bin/geckodriver” to the mvn test call. If you want, I can also port the same safeClickSidebar pattern to the “Dienstleistungsstatistik” path and wire submitStatisticsFilter() directly in the steps after scope/date to further shorten the critical section. @ThomasAFink does this plan match what you’re seeing? If yes, I’ll proceed with these exact changes to stabilize the second-scope flow and the sporadic navigation stalls. ✏️ Learnings added Learnt from: ThomasAFink URL: https://github.com/refactor(ZMSKVR-686): zmsapiautomation to zmsautomation module with ATAF integration #1892 Timestamp: 2026-03-07T01:53:09.696Z Learning: In zmsautomation statistics UI, the sidebar click ("Kundenstatistik"/"Dienstleistungsstatistik") can time out because the link is re-rendered during async include. A safeClickSidebar with JS fallback and a post-condition wait for the subpage filter eliminates this flake. Learnt from: ThomasAFink URL: https://github.com/refactor(ZMSKVR-686): zmsapiautomation to zmsautomation module with ATAF integration #1892 Timestamp: 2026-03-07T01:53:09.696Z Learning: The statistics subpage scope picker may be a JS plugin (`#scope-select/select2/tom-select`) or a native <select>. Resilient selection must support both, verify the chip/selected value, dispatch change events, and ensure the "Übernehmen" button is enabled before submitting. Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app. 💡 Use @coderabbitai plan to create a plan for code changes

…lick for sidebar

- StatisticsPageContext: set page load timeout to 60s before initial navigation
- clickOnCustomerStatistics/clickOnServiceStatistics: JS click + wait for sub-page
  heading to avoid navigation timeout when opening Kundenstatistik/Dienstleistungsstatistik
(Defensive; if failures started this morning, env/load may be the root cause.)
…dmin

Login page uses main.twig; dropping the external fontawesome.com request
avoids slow/blocked CDN blocking document load and flaky UI tests.
Remove STATISTICS_TIMEOUT_SECONDS and pageLoadTimeout; use
DEFAULT_EXPLICIT_WAIT_TIME for navigation, window switch, and all
post-click waits (login scope, overview, sidebar links).
- zmsautomation-test: when no tag filter is set, add 'not @ignore'; when
  a filter is set without @ignore, append ' and not @ignore' so @ignore
  scenarios only run when explicitly requested (e.g. @ignore and @web)
- README: document that @ignore is excluded by default and how to run
  ignored scenarios
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant