Skip to content

feat(review): exclude configured file patterns from PR review diff#254

Merged
tmseidel merged 3 commits into
developfrom
feature/review-with-exclude-pattern
Jul 10, 2026
Merged

feat(review): exclude configured file patterns from PR review diff#254
tmseidel merged 3 commits into
developfrom
feature/review-with-exclude-pattern

Conversation

@tmseidel

@tmseidel tmseidel commented Jul 9, 2026

Copy link
Copy Markdown
Owner

No description provided.

Add an "excludedFilePatterns" parameter to the PR Review workflow so
operators can strip non-meaningful files (lockfiles, minified JS,
generated code) from the diff before it reaches the AI. Reduces token
usage and cost, and keeps the model focused on meaningful changes.

The pattern list is a comma-separated set of globs / filenames
(e.g. *.lock, *.min.js, package-lock.json, **/generated/**), configured
per-workflow only — no global application.properties hook. Blank list is
a no-op, preserving current behaviour.

- DiffFileFilter: stateless util that splits the unified diff on
  `diff --git` boundaries and drops sections whose path matches an
  exclude pattern (PathMatcher glob, matched on full path and bare
  filename; deletions matched on old path).
- ReviewParam: new EXCLUDED_FILE_PATTERNS key.
- ReviewWorkflow: new TEXT param field + strParam helper, threaded
  through all five action handlers.
- CodeReviewServiceFactory / CodeReviewService: parse patterns once,
  route all diff fetches through fetchFilteredDiff() before
  chunking/enrichment.

Scope: review workflow only; AgentReviewService still consumes the raw
diff (util is reusable there as a follow-up).

Tests: DiffFileFilterTest (globs, extensions, exact names, deletions,
empty/non-diff); CodeReviewServiceTest wiring test asserting excluded
files are stripped from the AI message; updated factory/mocks in
BotWebhookServiceTest and ReviewWorkflowTest.
@tmseidel tmseidel requested a review from max-california July 10, 2026 00:15

@max-california max-california left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Agentic Code Review

Summary: the core diff-filtering utility looks reasonable, but the PR does not fully wire the new configuration through the existing config model/UI default path, so the advertised application-level default currently cannot work. I’d fix that before merge.

Blocking

1. review.context.excluded-file-patterns is not actually implemented in the config bean or workflow constructor

The PR description says this should be configurable via application.properties, but the code shown does not add the property to ReviewConfigProperties, nor inject that bean into ReviewWorkflow.

  • src/main/java/org/remus/giteabot/config/ReviewConfigProperties.java:15-38 still only contains the existing numeric context limits.
  • src/main/java/org/remus/giteabot/prworkflow/review/ReviewWorkflow.java:64-72 still has a constructor that only accepts ReviewChunkingProperties, and
  • paramsSchema() hard-codes the default for EXCLUDED_FILE_PATTERNS to "" at ReviewWorkflow.java:114-118.

So even if application.properties is updated later, the workflow schema/UI default will ignore it, and operators won’t get the configured default value. That’s a correctness gap relative to the feature being added, not just missing documentation.

Suggested fix:

  • Add private String excludedFilePatterns = ""; to ReviewConfigProperties.
  • Inject ReviewConfigProperties into ReviewWorkflow.
  • Use reviewConfigProperties.getExcludedFilePatterns() as both:
    • the schema default in paramsSchema(), and
    • the fallback in run() instead of "".

Non-blocking

1. Consider aligning fallback behavior with the schema default

Related to the above: strParam(..., "") in ReviewWorkflow.run() means blank/missing values always fall back to empty string, regardless of future config defaults.

  • src/main/java/org/remus/giteabot/prworkflow/review/ReviewWorkflow.java:146
  • src/main/java/org/remus/giteabot/prworkflow/review/ReviewWorkflow.java:263-268

Once the config bean exists, the fallback should come from it too; otherwise runtime behavior can diverge from what the UI showed as the default.

2. Nice-to-have: add a workflow test for the new parameter passthrough

ReviewWorkflowTest updates the mock signature, but it doesn’t verify that excludedFilePatterns is actually read from resolved params and passed into the factory. A small assertion here would protect the new wiring from regressions.

What looks good

  • Filtering is applied consistently in all CodeReviewService diff fetch paths, not just the initial review.
  • Handling deleted files via the a/ path is a good touch.
  • The utility is side-effect free and has focused unit tests for common matching cases.

Read-only agentic review by AI Git Bot

@tmseidel tmseidel linked an issue Jul 10, 2026 that may be closed by this pull request
Repository owner deleted a comment from clicki-mc-testface Jul 10, 2026
Repository owner deleted a comment from clicki-mc-testface Jul 10, 2026
@tmseidel tmseidel requested review from clicki-mc-testface and removed request for clicki-mc-testface July 10, 2026 05:47
Repository owner deleted a comment from clicki-mc-testface Jul 10, 2026
Repository owner deleted a comment from clicki-mc-testface Jul 10, 2026
@tmseidel tmseidel requested review from clicki-mc-testface and removed request for clicki-mc-testface July 10, 2026 06:51
Repository owner deleted a comment from clicki-mc-testface Jul 10, 2026
Repository owner deleted a comment from clicki-mc-testface Jul 10, 2026
@tmseidel tmseidel requested review from clicki-mc-testface and removed request for clicki-mc-testface July 10, 2026 07:07
Repository owner deleted a comment from clicki-mc-testface Jul 10, 2026
Repository owner deleted a comment from clicki-mc-testface Jul 10, 2026
@tmseidel tmseidel requested review from clicki-mc-testface and removed request for clicki-mc-testface July 10, 2026 07:25
@clicki-mc-testface

Copy link
Copy Markdown
Collaborator

E2E Test Run for PR #254

🤖 Starting end-to-end test run — this typically takes several minutes.

  • Framework: playwright
  • Suite lifecycle: offer-as-pr

I'll deploy a preview environment, generate the test suite and post the results here when the run finishes. Use @bot rerun-tests to re-execute or @bot regenerate-tests <feedback> to re-plan the suite.

@clicki-mc-testface clicki-mc-testface left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

E2E Test Run for PR #254

Framework: playwright
Preview environment: http://46.225.226.81:20038
Source SHA: 4d9bf73b
Outcome: ✅ PASSED (1/1 passed)

Test Status Duration
tests/preview-app-loads.spec.ts
Preview deployment serves the app root
✅ PASSED 1.36s

1/1 passed

@clicki-mc-testface

Copy link
Copy Markdown
Collaborator

Suite promotion (offer-as-pr)

✅ Opened follow-up PR #257 on branch ai-tests/pr-254-r167 with 1 generated test file(s).

@tmseidel

Copy link
Copy Markdown
Owner Author

@clicki-mc-testface regenerate-tests Can you please add a test where the user navigates to the default worflow-configuration system-settings/workflow-configurations/1/workflows selectes the review workflow, expands the section, enters in the field excludedFilePatterns e.g. *.yaml and saves the page - the value should be persisted and if the page is opened again, the value should be present in the form-field.

@clicki-mc-testface

Copy link
Copy Markdown
Collaborator

E2E Test Run for PR #254

🤖 Starting end-to-end test run — this typically takes several minutes.

  • Framework: playwright
  • Suite lifecycle: offer-as-pr

I'll deploy a preview environment, generate the test suite and post the results here when the run finishes. Use @bot rerun-tests to re-execute or @bot regenerate-tests <feedback> to re-plan the suite.

Repository owner deleted a comment from max-california Jul 10, 2026
@clicki-mc-testface

Copy link
Copy Markdown
Collaborator

excluded-file-patterns-persists — error context (24.1 KiB)

# Instructions

- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.

# Test info

- Name: excluded-file-patterns-persists.spec.ts >> excludedFilePatterns value persists after saving review workflow config
- Location: tests/excluded-file-patterns-persists.spec.ts:84:1

# Error details

TimeoutError: locator.waitFor: Timeout 15000ms exceeded.
Call log:

  • waiting for getByLabel(/excludedFilePatterns/i).or(locator('input[name="excludedFilePatterns"]')).or(locator('textarea[name="excludedFilePatterns"]')).or(locator('[data-testid="excludedFilePatterns"]')).first() to be visible

# Page snapshot

```yaml
- generic [ref=e2]:
  - navigation [ref=e3]:
    - generic [ref=e4]:
      - link "AI Git Bot logo AI Git Bot" [ref=e5] [cursor=pointer]:
        - /url: /dashboard
        - img "AI Git Bot logo" [ref=e6]
        - text: AI Git Bot
      - generic [ref=e7]:
        - list [ref=e8]:
          - listitem [ref=e9]:
            - link " Dashboard" [ref=e10] [cursor=pointer]:
              - /url: /dashboard
              - generic [ref=e11]: 
              - text: Dashboard
          - listitem [ref=e12]:
            - link " Bots" [ref=e13] [cursor=pointer]:
              - /url: /bots
              - generic [ref=e14]: 
              - text: Bots
          - listitem [ref=e15]:
            - link " AI Integrations" [ref=e16] [cursor=pointer]:
              - /url: /ai-integrations
              - generic [ref=e17]: 
              - text: AI Integrations
          - listitem [ref=e18]:
            - link " Git Integrations" [ref=e19] [cursor=pointer]:
              - /url: /git-integrations
              - generic [ref=e20]: 
              - text: Git Integrations
          - listitem [ref=e21]:
            - link " Usage" [ref=e22] [cursor=pointer]:
              - /url: /usage
              - generic [ref=e23]: 
              - text: Usage
          - listitem [ref=e24]:
            - link " System settings" [ref=e25] [cursor=pointer]:
              - /url: /system-settings
              - generic [ref=e26]: 
              - text: System settings
        - list [ref=e27]:
          - listitem [ref=e28]:
            - button "Toggle color theme" [ref=e29] [cursor=pointer]:
              - generic [ref=e30]: 
          - listitem [ref=e31]:
            - button " Logout" [ref=e33] [cursor=pointer]:
              - generic [ref=e34]: 
              - text: Logout
  - main [ref=e35]:
    - generic [ref=e36]:
      - generic [ref=e37]:
        - heading " Workflows for «Default»" [level=2] [ref=e38]:
          - generic [ref=e39]: 
          - text: Workflows for «Default»
        - generic [ref=e40]:
          - button " Expand all" [ref=e41] [cursor=pointer]:
            - generic [ref=e42]: 
            - text: Expand all
          - button " Collapse all" [ref=e43] [cursor=pointer]:
            - generic [ref=e44]: 
            - text: Collapse all
          - link " Back to System settings" [ref=e45] [cursor=pointer]:
            - /url: /system-settings
            - generic [ref=e46]: 
            - text: Back to System settings
      - paragraph [ref=e47]: Tick the PR workflows that should run on every pull-request webhook for bots using this configuration. Workflows are executed sequentially in stable order (lexicographic by workflow key).
      - generic [ref=e48]:
        - generic [ref=e49]:
          - generic [ref=e50]:
            - button "" [expanded] [ref=e51] [cursor=pointer]:
              - generic [ref=e52]: 
            - generic [ref=e53]:
              - checkbox "Agentic PR Review agentic-review REVIEW" [checked] [active] [ref=e54]
              - generic [ref=e55]:
                - strong [ref=e56]: Agentic PR Review
                - code [ref=e57]: agentic-review
                - generic [ref=e58]: REVIEW
              - generic [ref=e59]: Reviews the pull request with an LLM that can iteratively call read-only repository and MCP tools to gather context before writing its findings as a Markdown comment. When enabled, may optionally post a formal review decision (approve / request changes) based on the operator-configured criteria.
          - generic [ref=e61]:
            - generic [ref=e62]:
              - generic [ref=e63]: Max tool-exploration rounds
              - spinbutton "Max tool-exploration rounds" [ref=e64]: "12"
              - generic [ref=e65]: Upper bound on how many explore/answer rounds the agent may take while reading the repository (1-30). Higher values allow deeper analysis at higher token cost.
            - generic [ref=e66]:
              - generic [ref=e67]:
                - checkbox "Enable formal review decision" [ref=e68]
                - generic [ref=e69]: Enable formal review decision
              - generic [ref=e70]: When enabled, the bot may post a formal PR review decision (approve or request changes) based on the criteria configured below.
            - generic [ref=e71]:
              - generic [ref=e72]: Approval decision prompt
              - textbox "Approval decision prompt" [ref=e73]: "# Formal Review Decision Based on your review findings, decide whether to approve, request changes, or leave the PR review state unchanged. Use the following guidelines: - **APPROVE** — The PR is correct, follows best practices, and has no significant issues that should block merging. Minor style nits or optional suggestions do not warrant blocking. - **REQUEST_CHANGES** — The PR has non-trivial bugs, security concerns, missing error handling, broken tests, or significant code quality issues that must be fixed before merging. Regression risk alone is not enough — there must be an identifiable problem. - **NONE** — There are minor issues or suggestions, but nothing blocking. Also use NONE when you lack sufficient information to make a confident decision, or when the change is too large to assess reliably from the diff alone."
              - generic [ref=e74]: Criteria for when the bot should approve, request changes, or leave the PR review state unchanged. Only applies when the "Enable formal review decision" checkbox is ticked.
        - generic [ref=e75]:
          - generic [ref=e76]:
            - button "" [expanded] [ref=e77] [cursor=pointer]:
              - generic [ref=e78]: 
            - generic [ref=e79]:
              - checkbox "E2E Tests e2e-test TESTING" [ref=e80]
              - generic [ref=e81]:
                - strong [ref=e82]: E2E Tests
                - code [ref=e83]: e2e-test
                - generic [ref=e84]: TESTING
              - generic [ref=e85]: Deploys a preview of the pull request to its configured deployment target, then generates and runs end-to-end tests against the live preview and posts a Markdown summary. Requires a deployment target; supports re-run / regenerate slash commands.
          - generic [ref=e87]:
            - generic [ref=e88]:
              - generic [ref=e89]: Test framework
              - generic [ref=e90]:
                - radio "Playwright — Node.js — default, well-tested." [checked] [ref=e91]
                - generic [ref=e92]:
                  - strong [ref=e93]: Playwright
                  - text: — Node.js — default, well-tested.
              - generic [ref=e94]:
                - radio "pytest — Python — experimental." [ref=e95]
                - generic [ref=e96]:
                  - strong [ref=e97]: pytest
                  - text: — Python — experimental.
              - generic [ref=e98]:
                - radio "k6 — Grafana k6 load testing — experimental." [ref=e99]
                - generic [ref=e100]:
                  - strong [ref=e101]: k6
                  - text: — Grafana k6 load testing — experimental.
              - generic [ref=e102]:
                - radio "Cypress — Node.js — experimental." [ref=e103]
                - generic [ref=e104]:
                  - strong [ref=e105]: Cypress
                  - text: — Node.js — experimental.
            - generic [ref=e106]:
              - generic [ref=e107]: Max retries per test
              - spinbutton "Max retries per test" [ref=e108]: "1"
              - generic [ref=e109]: Per-test retry budget. A test that passes after retries is tagged FLAKY.
            - generic [ref=e110]:
              - generic [ref=e111]: Max test cases per suite
              - spinbutton "Max test cases per suite" [ref=e112]: "20"
              - generic [ref=e113]: Cost guard. Capped at 100 regardless of the configured value.
            - generic [ref=e114]:
              - generic [ref=e115]: Suite lifecycle
              - generic [ref=e116]:
                - radio "Ephemeral — Delete suite and test cases when the PR closes (default)." [checked] [ref=e117]
                - generic [ref=e118]:
                  - strong [ref=e119]: Ephemeral
                  - text: — Delete suite and test cases when the PR closes (default).
              - generic [ref=e120]:
                - radio "Offer as PR — Open a follow-up PR with the generated tests against the feature branch." [ref=e121]
                - generic [ref=e122]:
                  - strong [ref=e123]: Offer as PR
                  - text: — Open a follow-up PR with the generated tests against the feature branch.
              - generic [ref=e124]:
                - radio "Promote on merge — Once the parent PR merges, open a follow-up PR against the default branch." [ref=e125]
                - generic [ref=e126]:
                  - strong [ref=e127]: Promote on merge
                  - text: — Once the parent PR merges, open a follow-up PR against the default branch.
              - generic [ref=e128]:
                - radio "Commit to PR — Commit the generated tests directly onto the feature branch." [ref=e129]
                - generic [ref=e130]:
                  - strong [ref=e131]: Commit to PR
                  - text: — Commit the generated tests directly onto the feature branch.
            - generic [ref=e132]:
              - generic [ref=e133]: Promotion pass-rate threshold (%)
              - spinbutton "Promotion pass-rate threshold (%)" [ref=e134]: "100"
              - generic [ref=e135]: "Minimum percentage of executed test cases that must pass for the suite to be promoted (applies to offer-as-pr / commit-to-pr / promote-on-merge). 100 (default) means only fully green suites are promoted; lower it to e.g. 80 to also promote when at least 80% of the tests pass. Suites that ERROR or are SKIPPED are never promoted regardless of this value. Tip: LLM-generated tests are rarely 100% runnable on the first try — treat them as a regression-test baseline, not as ground truth."
        - generic [ref=e136]:
          - generic [ref=e137]:
            - button "" [expanded] [ref=e138] [cursor=pointer]:
              - generic [ref=e139]: 
            - generic [ref=e140]:
              - checkbox "PR Review review REVIEW" [checked] [ref=e141]
              - generic [ref=e142]:
                - strong [ref=e143]: PR Review
                - code [ref=e144]: review
                - generic [ref=e145]: REVIEW
              - generic [ref=e146]: Posts an AI code review as a single PR comment whenever a pull request is opened or updated, responds to @-mentions and inline review comments with conversational follow-ups, and optionally approves or requests changes based on the bot's configured post-review action.
          - generic [ref=e148]:
            - generic [ref=e149]:
              - generic [ref=e150]: Max diff chars per chunk
              - spinbutton "Max diff chars per chunk" [ref=e151]: "120000"
              - generic [ref=e152]: Maximum characters per diff chunk before splitting. The full PR diff is split into chunks of this size for review.
            - generic [ref=e153]:
              - generic [ref=e154]: Max diff chunks
              - spinbutton "Max diff chunks" [ref=e155]: "8"
              - generic [ref=e156]: Maximum number of diff chunks to review. Further chunks beyond this limit are skipped.
            - generic [ref=e157]:
              - generic [ref=e158]: Retry truncated chunk chars
              - spinbutton "Retry truncated chunk chars" [ref=e159]: "60000"
              - generic [ref=e160]: When a chunk is too large for the model's context window (prompt-too-long error), the chunk is truncated to this many characters and retried once.
            - generic [ref=e161]:
              - generic [ref=e162]: Excluded file patterns
              - textbox "Excluded file patterns" [ref=e163]
              - generic [ref=e164]: Comma-separated glob or filename patterns for files to exclude from the review diff (e.g. *.lock, *.min.js, package-lock.json, **/generated/**). Matching file sections are stripped before the diff is sent to the AI, reducing token usage and noise. Leave blank to review all files.
        - generic [ref=e165]:
          - generic [ref=e166]:
            - button "" [expanded] [ref=e167] [cursor=pointer]:
              - generic [ref=e168]: 
            - generic [ref=e169]:
              - checkbox "AI Unit Tests unit-test-author TESTING" [ref=e170]
              - generic [ref=e171]:
                - strong [ref=e172]: AI Unit Tests
                - code [ref=e173]: unit-test-author
                - generic [ref=e174]: TESTING
              - generic [ref=e175]: Generates white-box unit tests for the pull-request diff, runs them with the project's own test runner, and (optionally) commits the passing tests onto the PR branch. Works on a checkout of the PR head — no preview deployment needed.
          - generic [ref=e177]:
            - generic [ref=e178]:
              - generic [ref=e179]: Build/test toolchain
              - generic [ref=e180]:
                - radio "Auto-detect — Detect from pom.xml / build.gradle / package.json / go.mod / Cargo.toml / *.csproj / Gemfile / pyproject.toml / Makefile (default)." [checked] [ref=e181]
                - generic [ref=e182]:
                  - strong [ref=e183]: Auto-detect
                  - text: — Detect from pom.xml / build.gradle / package.json / go.mod / Cargo.toml / *.csproj / Gemfile / pyproject.toml / Makefile (default).
              - generic [ref=e184]:
                - 'radio "Maven (JUnit) — JVM — runs `mvn test`." [ref=e185]'
                - generic [ref=e186]:
                  - strong [ref=e187]: Maven (JUnit)
                  - text: "— JVM — runs `mvn test`."
              - generic [ref=e188]:
                - 'radio "Gradle (JUnit) — JVM — runs `gradle test`." [ref=e189]'
                - generic [ref=e190]:
                  - strong [ref=e191]: Gradle (JUnit)
                  - text: "— JVM — runs `gradle test`."
              - generic [ref=e192]:
                - 'radio "npm (Jest/Vitest) — Node.js — runs `npm test`." [ref=e193]'
                - generic [ref=e194]:
                  - strong [ref=e195]: npm (Jest/Vitest)
                  - text: "— Node.js — runs `npm test`."
              - generic [ref=e196]:
                - 'radio "pytest — Python — runs `python3 -m pytest`." [ref=e197]'
                - generic [ref=e198]:
                  - strong [ref=e199]: pytest
                  - text: "— Python — runs `python3 -m pytest`."
              - generic [ref=e200]:
                - 'radio "Go — Go — runs `go test ./...`." [ref=e201]'
                - generic [ref=e202]:
                  - strong [ref=e203]: Go
                  - text: "— Go — runs `go test ./...`."
              - generic [ref=e204]:
                - 'radio "Cargo (Rust) — Rust — runs `cargo test`." [ref=e205]'
                - generic [ref=e206]:
                  - strong [ref=e207]: Cargo (Rust)
                  - text: "— Rust — runs `cargo test`."
              - generic [ref=e208]:
                - 'radio ".NET — .NET — runs `dotnet test`." [ref=e209]'
                - generic [ref=e210]:
                  - strong [ref=e211]: .NET
                  - text: "— .NET — runs `dotnet test`."
              - generic [ref=e212]:
                - 'radio "Bundler (Ruby) — Ruby — runs `bundle exec rake test`." [ref=e213]'
                - generic [ref=e214]:
                  - strong [ref=e215]: Bundler (Ruby)
                  - text: "— Ruby — runs `bundle exec rake test`."
              - generic [ref=e216]:
                - 'radio "Make — Generic — runs `make test`." [ref=e217]'
                - generic [ref=e218]:
                  - strong [ref=e219]: Make
                  - text: "— Generic — runs `make test`."
              - generic [ref=e220]:
                - 'radio "C (gcc) — C — runs `make test` against the project Makefile." [ref=e221]'
                - generic [ref=e222]:
                  - strong [ref=e223]: C (gcc)
                  - text: "— C — runs `make test` against the project Makefile."
              - generic [ref=e224]:
                - 'radio "C++ (g++) — C++ — runs `make test` against the project Makefile." [ref=e225]'
                - generic [ref=e226]:
                  - strong [ref=e227]: C++ (g++)
                  - text: "— C++ — runs `make test` against the project Makefile."
            - generic [ref=e228]:
              - generic [ref=e229]: Max suite retries
              - spinbutton "Max suite retries" [ref=e230]: "1"
              - generic [ref=e231]: How many times to re-run a failing suite before reporting it failed (0-5).
            - generic [ref=e232]:
              - generic [ref=e233]: Max test files per PR
              - spinbutton "Max test files per PR" [ref=e234]: "10"
              - generic [ref=e235]: Cost guard. Capped at 50 regardless of the value.
            - generic [ref=e236]:
              - generic [ref=e237]: Generated-test lifecycle
              - generic [ref=e238]:
                - radio "Commit to PR — Commit the generated tests directly onto the PR branch (default)." [checked] [ref=e239]
                - generic [ref=e240]:
                  - strong [ref=e241]: Commit to PR
                  - text: — Commit the generated tests directly onto the PR branch (default).
              - generic [ref=e242]:
                - radio "Report only — Run and report the tests but do not commit them." [ref=e243]
                - generic [ref=e244]:
                  - strong [ref=e245]: Report only
                  - text: — Run and report the tests but do not commit them.
        - generic [ref=e246]:
          - button " Save selection" [ref=e247] [cursor=pointer]:
            - generic [ref=e248]: 
            - text: Save selection
          - link "Cancel" [ref=e249] [cursor=pointer]:
            - /url: /system-settings

Test source

  1   | import { test, expect, Page } from '@playwright/test';
  2   | 
  3   | /**
  4   |  * Verify that the `excludedFilePatterns` value entered on the review workflow
  5   |  * configuration is persisted after saving and reloading the page.
  6   |  *
  7   |  * The test is idempotent: it captures the original value before mutating it
  8   |  * and restores it in a cleanup step so that subsequent test runs start from
  9   |  * the same state.
  10  |  */
  11  | 
  12  | const WORKFLOWS_URL = '/system-settings/workflow-configurations/1/workflows';
  13  | const NEW_VALUE = '*.yaml';
  14  | 
  15  | async function openReviewWorkflow(page: Page): Promise<void> {
  16  |   await page.goto(WORKFLOWS_URL);
  17  |   // Wait for the workflows list to render.
  18  |   await page.waitForLoadState('networkidle');
  19  | 
  20  |   // Click the row/entry labelled "review". Use a case-insensitive match to be
  21  |   // resilient to small label changes.
  22  |   const reviewRow = page
  23  |     .getByRole('row', { name: /review/i })
  24  |     .or(page.getByRole('button', { name: /review/i }))
  25  |     .or(page.getByText(/^review$/i))
  26  |     .first();
  27  | 
  28  |   await reviewRow.waitFor({ state: 'visible', timeout: 15_000 });
  29  |   await reviewRow.click();
  30  | 
  31  |   // Give the details/parameters panel time to render.
  32  |   await page.waitForTimeout(300);
  33  | 
  34  |   // Try to expand a parameters section if it is collapsed. Some UIs render
  35  |   // the parameters directly, others require an explicit expand click. We
  36  |   // handle both cases without failing when no toggle exists.
  37  |   const parametersToggle = page
  38  |     .getByRole('button', { name: /parameters?/i })
  39  |     .or(page.locator('[data-testid*="parameters" i]'))
  40  |     .first();
  41  | 
  42  |   if (await parametersToggle.isVisible().catch(() => false)) {
  43  |     const expanded = await parametersToggle
  44  |       .getAttribute('aria-expanded')
  45  |       .catch(() => null);
  46  |     if (expanded !== 'true') {
  47  |       await parametersToggle.click().catch(() => {});
  48  |       await page.waitForTimeout(200);
  49  |     }
  50  |   }
  51  | }
  52  | 
  53  | function excludedFilePatternsField(page: Page) {
  54  |   // Match the field via label, placeholder, name, or data-testid — whichever
  55  |   // the app happens to expose.
  56  |   return page
  57  |     .getByLabel(/excludedFilePatterns/i)
  58  |     .or(page.locator('input[name="excludedFilePatterns"]'))
  59  |     .or(page.locator('textarea[name="excludedFilePatterns"]'))
  60  |     .or(page.locator('[data-testid="excludedFilePatterns"]'))
  61  |     .first();
  62  | }
  63  | 
  64  | async function saveAndWait(page: Page): Promise<void> {
  65  |   const saveButton = page.getByRole('button', { name: /^save$/i }).first();
  66  |   await saveButton.waitFor({ state: 'visible' });
  67  |   await saveButton.click();
  68  | 
  69  |   // Wait either for a visible confirmation, or for the button to settle
  70  |   // back into an enabled/idle state. We accept whichever happens first.
  71  |   await Promise.race([
  72  |     page
  73  |       .getByText(/saved|success|updated/i)
  74  |       .first()
  75  |       .waitFor({ state: 'visible', timeout: 10_000 })
  76  |       .catch(() => {}),
  77  |     page.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => {}),
  78  |   ]);
  79  | 
  80  |   // Small settle time so async state updates finish before we navigate away.
  81  |   await page.waitForTimeout(500);
  82  | }
  83  | 
  84  | test('excludedFilePatterns value persists after saving review workflow config', async ({
  85  |   page,
  86  | }) => {
  87  |   // --- Setup: open the review workflow and remember the original value. ---
  88  |   await openReviewWorkflow(page);
  89  | 
  90  |   const field = excludedFilePatternsField(page);
> 91  |   await field.waitFor({ state: 'visible', timeout: 15_000 });
      |               ^ TimeoutError: locator.waitFor: Timeout 15000ms exceeded.
  92  | 
  93  |   const originalValue = (await field.inputValue().catch(() => '')) ?? '';
  94  | 
  95  |   try {
  96  |     // --- Act: clear the field, enter the new value, save. ---
  97  |     await field.click();
  98  |     await field.fill('');
  99  |     await field.fill(NEW_VALUE);
  100 |     await expect(field).toHaveValue(NEW_VALUE);
  101 | 
  102 |     await saveAndWait(page);
  103 | 
  104 |     // --- Reload / re-navigate and re-open the review workflow. ---
  105 |     await openReviewWorkflow(page);
  106 | 
  107 |     const fieldAfterReload = excludedFilePatternsField(page);
  108 |     await fieldAfterReload.waitFor({ state: 'visible', timeout: 15_000 });
  109 | 
  110 |     // --- Assert: value was persisted. ---
  111 |     await expect(fieldAfterReload).toHaveValue(NEW_VALUE);
  112 |   } finally {
  113 |     // --- Cleanup: restore the original value so the test is idempotent. ---
  114 |     try {
  115 |       await openReviewWorkflow(page);
  116 |       const restoreField = excludedFilePatternsField(page);
  117 |       await restoreField.waitFor({ state: 'visible', timeout: 10_000 });
  118 |       await restoreField.click();
  119 |       await restoreField.fill('');
  120 |       if (originalValue.length > 0) {
  121 |         await restoreField.fill(originalValue);
  122 |       }
  123 |       await saveAndWait(page);
  124 |     } catch {
  125 |       // Best-effort cleanup — don't mask the primary assertion failure.
  126 |     }
  127 |   }
  128 | });
  129 | 

@clicki-mc-testface clicki-mc-testface left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

E2E Test Run for PR #254

Framework: playwright
Preview environment: http://46.225.226.81:20039
Source SHA: 4d9bf73b
Outcome: ❌ FAILED (0/1 passed)

Test Status Duration
tests/excluded-file-patterns-persists.spec.ts
excludedFilePatterns value persists after saving review workflow config
❌ FAILED 16.38s

1/1 failed

@tmseidel

Copy link
Copy Markdown
Owner Author

@clicki-mc-testface regenerate-tests The field you're looking for has the id param-review-excludedFilePatterns, here is the html-snippet:

<textarea class="form-control" rows="3" id="param-review-excludedFilePatterns" name="params.review.excludedFilePatterns"></textarea>

clicki-mc-testface

This comment was marked as low quality.

Repository owner deleted a comment from max-california Jul 10, 2026
@clicki-mc-testface

Copy link
Copy Markdown
Collaborator

E2E Test Run for PR #254

🤖 Starting end-to-end test run — this typically takes several minutes.

  • Framework: playwright
  • Suite lifecycle: offer-as-pr

I'll deploy a preview environment, generate the test suite and post the results here when the run finishes. Use @bot rerun-tests to re-execute or @bot regenerate-tests <feedback> to re-plan the suite.

Repository owner deleted a comment from clicki-mc-testface Jul 10, 2026
Repository owner deleted a comment from clicki-mc-testface Jul 10, 2026
Repository owner deleted a comment from max-california Jul 10, 2026
@clicki-mc-testface

Copy link
Copy Markdown
Collaborator

excluded-file-patterns-persistence — error context (22.6 KiB)

# Instructions

- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.

# Test info

- Name: excluded-file-patterns-persistence.spec.ts >> Review workflow – excludedFilePatterns persistence >> excludedFilePatterns value persists after save + reload
- Location: tests/excluded-file-patterns-persistence.spec.ts:34:3

# Error details

TimeoutError: locator.waitFor: Timeout 10000ms exceeded.
Call log:

  • waiting for getByRole('button', { name: /^save$/i }).first() to be visible

# Page snapshot

```yaml
- generic [ref=e2]:
  - navigation [ref=e3]:
    - generic [ref=e4]:
      - link "AI Git Bot logo AI Git Bot" [ref=e5] [cursor=pointer]:
        - /url: /dashboard
        - img "AI Git Bot logo" [ref=e6]
        - text: AI Git Bot
      - generic [ref=e7]:
        - list [ref=e8]:
          - listitem [ref=e9]:
            - link " Dashboard" [ref=e10] [cursor=pointer]:
              - /url: /dashboard
              - generic [ref=e11]: 
              - text: Dashboard
          - listitem [ref=e12]:
            - link " Bots" [ref=e13] [cursor=pointer]:
              - /url: /bots
              - generic [ref=e14]: 
              - text: Bots
          - listitem [ref=e15]:
            - link " AI Integrations" [ref=e16] [cursor=pointer]:
              - /url: /ai-integrations
              - generic [ref=e17]: 
              - text: AI Integrations
          - listitem [ref=e18]:
            - link " Git Integrations" [ref=e19] [cursor=pointer]:
              - /url: /git-integrations
              - generic [ref=e20]: 
              - text: Git Integrations
          - listitem [ref=e21]:
            - link " Usage" [ref=e22] [cursor=pointer]:
              - /url: /usage
              - generic [ref=e23]: 
              - text: Usage
          - listitem [ref=e24]:
            - link " System settings" [ref=e25] [cursor=pointer]:
              - /url: /system-settings
              - generic [ref=e26]: 
              - text: System settings
        - list [ref=e27]:
          - listitem [ref=e28]:
            - button "Toggle color theme" [ref=e29] [cursor=pointer]:
              - generic [ref=e30]: 
          - listitem [ref=e31]:
            - button " Logout" [ref=e33] [cursor=pointer]:
              - generic [ref=e34]: 
              - text: Logout
  - main [ref=e35]:
    - generic [ref=e36]:
      - generic [ref=e37]:
        - heading " Workflows for «Default»" [level=2] [ref=e38]:
          - generic [ref=e39]: 
          - text: Workflows for «Default»
        - generic [ref=e40]:
          - button " Expand all" [ref=e41] [cursor=pointer]:
            - generic [ref=e42]: 
            - text: Expand all
          - button " Collapse all" [ref=e43] [cursor=pointer]:
            - generic [ref=e44]: 
            - text: Collapse all
          - link " Back to System settings" [ref=e45] [cursor=pointer]:
            - /url: /system-settings
            - generic [ref=e46]: 
            - text: Back to System settings
      - paragraph [ref=e47]: Tick the PR workflows that should run on every pull-request webhook for bots using this configuration. Workflows are executed sequentially in stable order (lexicographic by workflow key).
      - generic [ref=e48]:
        - generic [ref=e49]:
          - generic [ref=e50]:
            - button "" [expanded] [ref=e51] [cursor=pointer]:
              - generic [ref=e52]: 
            - generic [ref=e53]:
              - checkbox "Agentic PR Review agentic-review REVIEW" [checked] [ref=e54]
              - generic [ref=e55]:
                - strong [ref=e56]: Agentic PR Review
                - code [ref=e57]: agentic-review
                - generic [ref=e58]: REVIEW
              - generic [ref=e59]: Reviews the pull request with an LLM that can iteratively call read-only repository and MCP tools to gather context before writing its findings as a Markdown comment. When enabled, may optionally post a formal review decision (approve / request changes) based on the operator-configured criteria.
          - generic [ref=e61]:
            - generic [ref=e62]:
              - generic [ref=e63]: Max tool-exploration rounds
              - spinbutton "Max tool-exploration rounds" [ref=e64]: "12"
              - generic [ref=e65]: Upper bound on how many explore/answer rounds the agent may take while reading the repository (1-30). Higher values allow deeper analysis at higher token cost.
            - generic [ref=e66]:
              - generic [ref=e67]:
                - checkbox "Enable formal review decision" [ref=e68]
                - generic [ref=e69]: Enable formal review decision
              - generic [ref=e70]: When enabled, the bot may post a formal PR review decision (approve or request changes) based on the criteria configured below.
            - generic [ref=e71]:
              - generic [ref=e72]: Approval decision prompt
              - textbox "Approval decision prompt" [ref=e73]: "# Formal Review Decision Based on your review findings, decide whether to approve, request changes, or leave the PR review state unchanged. Use the following guidelines: - **APPROVE** — The PR is correct, follows best practices, and has no significant issues that should block merging. Minor style nits or optional suggestions do not warrant blocking. - **REQUEST_CHANGES** — The PR has non-trivial bugs, security concerns, missing error handling, broken tests, or significant code quality issues that must be fixed before merging. Regression risk alone is not enough — there must be an identifiable problem. - **NONE** — There are minor issues or suggestions, but nothing blocking. Also use NONE when you lack sufficient information to make a confident decision, or when the change is too large to assess reliably from the diff alone."
              - generic [ref=e74]: Criteria for when the bot should approve, request changes, or leave the PR review state unchanged. Only applies when the "Enable formal review decision" checkbox is ticked.
        - generic [ref=e75]:
          - generic [ref=e76]:
            - button "" [expanded] [ref=e77] [cursor=pointer]:
              - generic [ref=e78]: 
            - generic [ref=e79]:
              - checkbox "E2E Tests e2e-test TESTING" [ref=e80]
              - generic [ref=e81]:
                - strong [ref=e82]: E2E Tests
                - code [ref=e83]: e2e-test
                - generic [ref=e84]: TESTING
              - generic [ref=e85]: Deploys a preview of the pull request to its configured deployment target, then generates and runs end-to-end tests against the live preview and posts a Markdown summary. Requires a deployment target; supports re-run / regenerate slash commands.
          - generic [ref=e87]:
            - generic [ref=e88]:
              - generic [ref=e89]: Test framework
              - generic [ref=e90]:
                - radio "Playwright — Node.js — default, well-tested." [checked] [ref=e91]
                - generic [ref=e92]:
                  - strong [ref=e93]: Playwright
                  - text: — Node.js — default, well-tested.
              - generic [ref=e94]:
                - radio "pytest — Python — experimental." [ref=e95]
                - generic [ref=e96]:
                  - strong [ref=e97]: pytest
                  - text: — Python — experimental.
              - generic [ref=e98]:
                - radio "k6 — Grafana k6 load testing — experimental." [ref=e99]
                - generic [ref=e100]:
                  - strong [ref=e101]: k6
                  - text: — Grafana k6 load testing — experimental.
              - generic [ref=e102]:
                - radio "Cypress — Node.js — experimental." [ref=e103]
                - generic [ref=e104]:
                  - strong [ref=e105]: Cypress
                  - text: — Node.js — experimental.
            - generic [ref=e106]:
              - generic [ref=e107]: Max retries per test
              - spinbutton "Max retries per test" [ref=e108]: "1"
              - generic [ref=e109]: Per-test retry budget. A test that passes after retries is tagged FLAKY.
            - generic [ref=e110]:
              - generic [ref=e111]: Max test cases per suite
              - spinbutton "Max test cases per suite" [ref=e112]: "20"
              - generic [ref=e113]: Cost guard. Capped at 100 regardless of the configured value.
            - generic [ref=e114]:
              - generic [ref=e115]: Suite lifecycle
              - generic [ref=e116]:
                - radio "Ephemeral — Delete suite and test cases when the PR closes (default)." [checked] [ref=e117]
                - generic [ref=e118]:
                  - strong [ref=e119]: Ephemeral
                  - text: — Delete suite and test cases when the PR closes (default).
              - generic [ref=e120]:
                - radio "Offer as PR — Open a follow-up PR with the generated tests against the feature branch." [ref=e121]
                - generic [ref=e122]:
                  - strong [ref=e123]: Offer as PR
                  - text: — Open a follow-up PR with the generated tests against the feature branch.
              - generic [ref=e124]:
                - radio "Promote on merge — Once the parent PR merges, open a follow-up PR against the default branch." [ref=e125]
                - generic [ref=e126]:
                  - strong [ref=e127]: Promote on merge
                  - text: — Once the parent PR merges, open a follow-up PR against the default branch.
              - generic [ref=e128]:
                - radio "Commit to PR — Commit the generated tests directly onto the feature branch." [ref=e129]
                - generic [ref=e130]:
                  - strong [ref=e131]: Commit to PR
                  - text: — Commit the generated tests directly onto the feature branch.
            - generic [ref=e132]:
              - generic [ref=e133]: Promotion pass-rate threshold (%)
              - spinbutton "Promotion pass-rate threshold (%)" [ref=e134]: "100"
              - generic [ref=e135]: "Minimum percentage of executed test cases that must pass for the suite to be promoted (applies to offer-as-pr / commit-to-pr / promote-on-merge). 100 (default) means only fully green suites are promoted; lower it to e.g. 80 to also promote when at least 80% of the tests pass. Suites that ERROR or are SKIPPED are never promoted regardless of this value. Tip: LLM-generated tests are rarely 100% runnable on the first try — treat them as a regression-test baseline, not as ground truth."
        - generic [ref=e136]:
          - generic [ref=e137]:
            - button "" [expanded] [ref=e138] [cursor=pointer]:
              - generic [ref=e139]: 
            - generic [ref=e140]:
              - checkbox "PR Review review REVIEW" [checked] [ref=e141]
              - generic [ref=e142]:
                - strong [ref=e143]: PR Review
                - code [ref=e144]: review
                - generic [ref=e145]: REVIEW
              - generic [ref=e146]: Posts an AI code review as a single PR comment whenever a pull request is opened or updated, responds to @-mentions and inline review comments with conversational follow-ups, and optionally approves or requests changes based on the bot's configured post-review action.
          - generic [ref=e148]:
            - generic [ref=e149]:
              - generic [ref=e150]: Max diff chars per chunk
              - spinbutton "Max diff chars per chunk" [ref=e151]: "120000"
              - generic [ref=e152]: Maximum characters per diff chunk before splitting. The full PR diff is split into chunks of this size for review.
            - generic [ref=e153]:
              - generic [ref=e154]: Max diff chunks
              - spinbutton "Max diff chunks" [ref=e155]: "8"
              - generic [ref=e156]: Maximum number of diff chunks to review. Further chunks beyond this limit are skipped.
            - generic [ref=e157]:
              - generic [ref=e158]: Retry truncated chunk chars
              - spinbutton "Retry truncated chunk chars" [ref=e159]: "60000"
              - generic [ref=e160]: When a chunk is too large for the model's context window (prompt-too-long error), the chunk is truncated to this many characters and retried once.
            - generic [ref=e161]:
              - generic [ref=e162]: Excluded file patterns
              - textbox "Excluded file patterns" [active] [ref=e163]: "*.yaml"
              - generic [ref=e164]: Comma-separated glob or filename patterns for files to exclude from the review diff (e.g. *.lock, *.min.js, package-lock.json, **/generated/**). Matching file sections are stripped before the diff is sent to the AI, reducing token usage and noise. Leave blank to review all files.
        - generic [ref=e165]:
          - generic [ref=e166]:
            - button "" [expanded] [ref=e167] [cursor=pointer]:
              - generic [ref=e168]: 
            - generic [ref=e169]:
              - checkbox "AI Unit Tests unit-test-author TESTING" [ref=e170]
              - generic [ref=e171]:
                - strong [ref=e172]: AI Unit Tests
                - code [ref=e173]: unit-test-author
                - generic [ref=e174]: TESTING
              - generic [ref=e175]: Generates white-box unit tests for the pull-request diff, runs them with the project's own test runner, and (optionally) commits the passing tests onto the PR branch. Works on a checkout of the PR head — no preview deployment needed.
          - generic [ref=e177]:
            - generic [ref=e178]:
              - generic [ref=e179]: Build/test toolchain
              - generic [ref=e180]:
                - radio "Auto-detect — Detect from pom.xml / build.gradle / package.json / go.mod / Cargo.toml / *.csproj / Gemfile / pyproject.toml / Makefile (default)." [checked] [ref=e181]
                - generic [ref=e182]:
                  - strong [ref=e183]: Auto-detect
                  - text: — Detect from pom.xml / build.gradle / package.json / go.mod / Cargo.toml / *.csproj / Gemfile / pyproject.toml / Makefile (default).
              - generic [ref=e184]:
                - 'radio "Maven (JUnit) — JVM — runs `mvn test`." [ref=e185]'
                - generic [ref=e186]:
                  - strong [ref=e187]: Maven (JUnit)
                  - text: "— JVM — runs `mvn test`."
              - generic [ref=e188]:
                - 'radio "Gradle (JUnit) — JVM — runs `gradle test`." [ref=e189]'
                - generic [ref=e190]:
                  - strong [ref=e191]: Gradle (JUnit)
                  - text: "— JVM — runs `gradle test`."
              - generic [ref=e192]:
                - 'radio "npm (Jest/Vitest) — Node.js — runs `npm test`." [ref=e193]'
                - generic [ref=e194]:
                  - strong [ref=e195]: npm (Jest/Vitest)
                  - text: "— Node.js — runs `npm test`."
              - generic [ref=e196]:
                - 'radio "pytest — Python — runs `python3 -m pytest`." [ref=e197]'
                - generic [ref=e198]:
                  - strong [ref=e199]: pytest
                  - text: "— Python — runs `python3 -m pytest`."
              - generic [ref=e200]:
                - 'radio "Go — Go — runs `go test ./...`." [ref=e201]'
                - generic [ref=e202]:
                  - strong [ref=e203]: Go
                  - text: "— Go — runs `go test ./...`."
              - generic [ref=e204]:
                - 'radio "Cargo (Rust) — Rust — runs `cargo test`." [ref=e205]'
                - generic [ref=e206]:
                  - strong [ref=e207]: Cargo (Rust)
                  - text: "— Rust — runs `cargo test`."
              - generic [ref=e208]:
                - 'radio ".NET — .NET — runs `dotnet test`." [ref=e209]'
                - generic [ref=e210]:
                  - strong [ref=e211]: .NET
                  - text: "— .NET — runs `dotnet test`."
              - generic [ref=e212]:
                - 'radio "Bundler (Ruby) — Ruby — runs `bundle exec rake test`." [ref=e213]'
                - generic [ref=e214]:
                  - strong [ref=e215]: Bundler (Ruby)
                  - text: "— Ruby — runs `bundle exec rake test`."
              - generic [ref=e216]:
                - 'radio "Make — Generic — runs `make test`." [ref=e217]'
                - generic [ref=e218]:
                  - strong [ref=e219]: Make
                  - text: "— Generic — runs `make test`."
              - generic [ref=e220]:
                - 'radio "C (gcc) — C — runs `make test` against the project Makefile." [ref=e221]'
                - generic [ref=e222]:
                  - strong [ref=e223]: C (gcc)
                  - text: "— C — runs `make test` against the project Makefile."
              - generic [ref=e224]:
                - 'radio "C++ (g++) — C++ — runs `make test` against the project Makefile." [ref=e225]'
                - generic [ref=e226]:
                  - strong [ref=e227]: C++ (g++)
                  - text: "— C++ — runs `make test` against the project Makefile."
            - generic [ref=e228]:
              - generic [ref=e229]: Max suite retries
              - spinbutton "Max suite retries" [ref=e230]: "1"
              - generic [ref=e231]: How many times to re-run a failing suite before reporting it failed (0-5).
            - generic [ref=e232]:
              - generic [ref=e233]: Max test files per PR
              - spinbutton "Max test files per PR" [ref=e234]: "10"
              - generic [ref=e235]: Cost guard. Capped at 50 regardless of the value.
            - generic [ref=e236]:
              - generic [ref=e237]: Generated-test lifecycle
              - generic [ref=e238]:
                - radio "Commit to PR — Commit the generated tests directly onto the PR branch (default)." [checked] [ref=e239]
                - generic [ref=e240]:
                  - strong [ref=e241]: Commit to PR
                  - text: — Commit the generated tests directly onto the PR branch (default).
              - generic [ref=e242]:
                - radio "Report only — Run and report the tests but do not commit them." [ref=e243]
                - generic [ref=e244]:
                  - strong [ref=e245]: Report only
                  - text: — Run and report the tests but do not commit them.
        - generic [ref=e246]:
          - button " Save selection" [ref=e247] [cursor=pointer]:
            - generic [ref=e248]: 
            - text: Save selection
          - link "Cancel" [ref=e249] [cursor=pointer]:
            - /url: /system-settings

Test source

  1  | import { test, expect, Page } from '@playwright/test';
  2  | 
  3  | const WORKFLOWS_URL = '/system-settings/workflow-configurations/1/workflows';
  4  | 
  5  | /**
  6  |  * Open the "review" workflow section on the workflows configuration page.
  7  |  * The page lists workflows; we click the one whose visible label contains
  8  |  * "review" (case-insensitive) to expand its parameter fields.
  9  |  */
  10 | async function openReviewWorkflow(page: Page): Promise<void> {
  11 |   // Wait for the workflow list to be populated. We look for any element
  12 |   // containing the word "review" (case-insensitive) and click the first
  13 |   // interactive ancestor.
  14 |   const reviewEntry = page
  15 |     .locator('text=/review/i')
  16 |     .first();
  17 |   await reviewEntry.waitFor({ state: 'visible', timeout: 15_000 });
  18 |   await reviewEntry.click();
  19 | 
  20 |   // Give the expansion animation / async parameter fields a moment.
  21 |   await page
  22 |     .locator('[name="excludedFilePatterns"], [id*="excludedFilePatterns" i], [data-testid*="excludedFilePatterns" i]')
  23 |     .first()
  24 |     .waitFor({ state: 'visible', timeout: 15_000 });
  25 | }
  26 | 
  27 | function excludedFilePatternsInput(page: Page) {
  28 |   return page
  29 |     .locator('[name="excludedFilePatterns"], [id*="excludedFilePatterns" i], [data-testid*="excludedFilePatterns" i]')
  30 |     .first();
  31 | }
  32 | 
  33 | test.describe('Review workflow – excludedFilePatterns persistence', () => {
  34 |   test('excludedFilePatterns value persists after save + reload', async ({ page }) => {
  35 |     // --- Step 1: Navigate to the workflows configuration page --------------
  36 |     await page.goto(WORKFLOWS_URL);
  37 |     await page.waitForLoadState('networkidle');
  38 | 
  39 |     // --- Step 2 & 3: Locate + expand the review workflow -------------------
  40 |     await openReviewWorkflow(page);
  41 | 
  42 |     // Capture the original value so we can restore it later (idempotency).
  43 |     const input = excludedFilePatternsInput(page);
  44 |     const originalValue = (await input.inputValue().catch(() => '')) ?? '';
  45 | 
  46 |     // --- Step 4: Enter '*.yaml' into the excludedFilePatterns field --------
  47 |     await input.click();
  48 |     await input.fill('');
  49 |     await input.fill('*.yaml');
  50 |     await expect(input).toHaveValue('*.yaml');
  51 | 
  52 |     // --- Step 5: Click the save button to persist --------------------------
  53 |     const saveButton = page
  54 |       .getByRole('button', { name: /^save$/i })
  55 |       .first();
> 56 |     await saveButton.waitFor({ state: 'visible', timeout: 10_000 });
     |                      ^ TimeoutError: locator.waitFor: Timeout 10000ms exceeded.
  57 |     await saveButton.click();
  58 | 
  59 |     // Give the save request time to complete.
  60 |     await page.waitForLoadState('networkidle');
  61 | 
  62 |     // --- Step 6: Reload the workflows page ---------------------------------
  63 |     await page.goto(WORKFLOWS_URL);
  64 |     await page.waitForLoadState('networkidle');
  65 | 
  66 |     // --- Step 7: Re-open the review workflow section -----------------------
  67 |     await openReviewWorkflow(page);
  68 | 
  69 |     // --- Assertion: the field is populated with '*.yaml' -------------------
  70 |     const reloadedInput = excludedFilePatternsInput(page);
  71 |     await expect(reloadedInput).toHaveValue('*.yaml', { timeout: 10_000 });
  72 | 
  73 |     // --- Cleanup: restore the previous value so subsequent tests see the
  74 |     // same starting state (idempotency). ------------------------------------
  75 |     await reloadedInput.click();
  76 |     await reloadedInput.fill('');
  77 |     if (originalValue.length > 0) {
  78 |       await reloadedInput.fill(originalValue);
  79 |     }
  80 | 
  81 |     const cleanupSave = page.getByRole('button', { name: /^save$/i }).first();
  82 |     if (await cleanupSave.isVisible().catch(() => false)) {
  83 |       await cleanupSave.click();
  84 |       await page.waitForLoadState('networkidle');
  85 |     }
  86 |   });
  87 | });
  88 | 

@clicki-mc-testface clicki-mc-testface left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

E2E Test Run for PR #254

Framework: playwright
Preview environment: http://46.225.226.81:20041
Source SHA: 4d9bf73b
Outcome: ❌ FAILED (0/1 passed)

Test Status Duration
tests/excluded-file-patterns-persistence.spec.ts
Excluded file patterns value persists after saving the review workflow config
❌ FAILED 11.21s

1/1 failed

@max-california

Copy link
Copy Markdown
Collaborator

🤖 Follow-up

The failing E2E is very likely caused by a UI persistence regression in the workflow configuration form.

Likely root cause

The new field is defined as:

  • ReviewWorkflow.paramsSchema() adds EXCLUDED_FILE_PATTERNS as WorkflowParamField.ParamType.TEXT
  • In the template, TEXT fields are rendered as a <textarea>:
    • src/main/resources/templates/system-settings/workflow-configurations/workflows.html:99-104

That means the browser saves/restores this field via the generic workflow-params plumbing, which is fine on the backend. I don’t see a backend persistence bug:

  • request extraction accepts all params.<workflowKey>.<fieldName> entries:
    • WorkflowConfigurationController.extractWorkflowParams(...) at src/main/java/org/remus/giteabot/prworkflow/config/WorkflowConfigurationController.java:177-216
  • validation preserves TEXT values as plain strings:
    • WorkflowParamsValidator.coerce(...) at src/main/java/org/remus/giteabot/prworkflow/config/WorkflowParamsValidator.java:134-173
  • saved params are reloaded and shown back in the form via row.persistedParams:
    • WorkflowSelectionService.loadAvailableWorkflows(...) at src/main/java/org/remus/giteabot/prworkflow/config/WorkflowSelectionService.java:41-72
    • template value binding at workflows.html:85-104

Why the E2E probably failed

The most probable issue is the test selector no longer matches the field.

Because this param was introduced as TEXT, the DOM element is now a <textarea>, not an <input type="text">. If the Playwright test excluded-file-patterns-persistence.spec.ts is using an input-specific selector or action, it would fail even though backend persistence is implemented correctly.

The rendered control has:

  • name="params.review.excludedFilePatterns" (assuming workflow key review)
  • id="param-review-excludedFilePatterns"
  • tag: textarea

from:

  • src/main/resources/templates/system-settings/workflow-configurations/workflows.html:99-104

Concise answer

I don’t see evidence of a server-side save/load bug for excludedFilePatterns; the request parsing and persistence path look correct. The E2E failure is most likely because the new field is rendered as a <textarea>, so the Playwright test likely still expects a text <input> or uses a selector that no longer matches.


Read-only agentic review follow-up by AI Git Bot

@tmseidel

Copy link
Copy Markdown
Owner Author

@clicki-mc-testface regenerate-tests Can you please add a test where the user navigates to the default worflow-configuration system-settings/workflow-configurations/1/workflows selectes the review workflow, expands the section, enters in the field excludedFilePatterns e.g. *.yaml and saves the page (the label of the save button is Save selection)- the value should be persisted and if the page is opened again, the value should be present in the form-field.
The field for the perssted value has the id param-review-excludedFilePatterns, here is the html-snippet:

<textarea class="form-control" rows="3" id="param-review-excludedFilePatterns" name="params.review.excludedFilePatterns"></textarea>

@clicki-mc-testface

Copy link
Copy Markdown
Collaborator

E2E Test Run for PR #254

🤖 Starting end-to-end test run — this typically takes several minutes.

  • Framework: playwright
  • Suite lifecycle: offer-as-pr

I'll deploy a preview environment, generate the test suite and post the results here when the run finishes. Use @bot rerun-tests to re-execute or @bot regenerate-tests <feedback> to re-plan the suite.

@clicki-mc-testface

Copy link
Copy Markdown
Collaborator

Error context: excludedFilePatterns input not found (23.8 KiB)

# Instructions

- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.

# Test info

- Name: review-workflow-excluded-file-patterns-persists.spec.ts >> Review workflow excluded file patterns persistence >> excludedFilePatterns value persists after saving and reloading
- Location: tests/review-workflow-excluded-file-patterns-persists.spec.ts:58:3

# Error details

Error: Could not locate the 'excludedFilePatterns' input field.


# Page snapshot

```yaml
- generic [ref=e2]:
  - navigation [ref=e3]:
    - generic [ref=e4]:
      - link "AI Git Bot logo AI Git Bot" [ref=e5] [cursor=pointer]:
        - /url: /dashboard
        - img "AI Git Bot logo" [ref=e6]
        - text: AI Git Bot
      - generic [ref=e7]:
        - list [ref=e8]:
          - listitem [ref=e9]:
            - link " Dashboard" [ref=e10] [cursor=pointer]:
              - /url: /dashboard
              - generic [ref=e11]: 
              - text: Dashboard
          - listitem [ref=e12]:
            - link " Bots" [ref=e13] [cursor=pointer]:
              - /url: /bots
              - generic [ref=e14]: 
              - text: Bots
          - listitem [ref=e15]:
            - link " AI Integrations" [ref=e16] [cursor=pointer]:
              - /url: /ai-integrations
              - generic [ref=e17]: 
              - text: AI Integrations
          - listitem [ref=e18]:
            - link " Git Integrations" [ref=e19] [cursor=pointer]:
              - /url: /git-integrations
              - generic [ref=e20]: 
              - text: Git Integrations
          - listitem [ref=e21]:
            - link " Usage" [ref=e22] [cursor=pointer]:
              - /url: /usage
              - generic [ref=e23]: 
              - text: Usage
          - listitem [ref=e24]:
            - link " System settings" [ref=e25] [cursor=pointer]:
              - /url: /system-settings
              - generic [ref=e26]: 
              - text: System settings
        - list [ref=e27]:
          - listitem [ref=e28]:
            - button "Toggle color theme" [ref=e29] [cursor=pointer]:
              - generic [ref=e30]: 
          - listitem [ref=e31]:
            - button " Logout" [ref=e33] [cursor=pointer]:
              - generic [ref=e34]: 
              - text: Logout
  - main [ref=e35]:
    - generic [ref=e36]:
      - generic [ref=e37]:
        - heading " Workflows for «Default»" [level=2] [ref=e38]:
          - generic [ref=e39]: 
          - text: Workflows for «Default»
        - generic [ref=e40]:
          - button " Expand all" [ref=e41] [cursor=pointer]:
            - generic [ref=e42]: 
            - text: Expand all
          - button " Collapse all" [ref=e43] [cursor=pointer]:
            - generic [ref=e44]: 
            - text: Collapse all
          - link " Back to System settings" [ref=e45] [cursor=pointer]:
            - /url: /system-settings
            - generic [ref=e46]: 
            - text: Back to System settings
      - paragraph [ref=e47]: Tick the PR workflows that should run on every pull-request webhook for bots using this configuration. Workflows are executed sequentially in stable order (lexicographic by workflow key).
      - generic [ref=e48]:
        - generic [ref=e49]:
          - generic [ref=e50]:
            - button "" [expanded] [ref=e51] [cursor=pointer]:
              - generic [ref=e52]: 
            - generic [ref=e53]:
              - checkbox "Agentic PR Review agentic-review REVIEW" [checked] [active] [ref=e54]
              - generic [ref=e55]:
                - strong [ref=e56]: Agentic PR Review
                - code [ref=e57]: agentic-review
                - generic [ref=e58]: REVIEW
              - generic [ref=e59]: Reviews the pull request with an LLM that can iteratively call read-only repository and MCP tools to gather context before writing its findings as a Markdown comment. When enabled, may optionally post a formal review decision (approve / request changes) based on the operator-configured criteria.
          - generic [ref=e61]:
            - generic [ref=e62]:
              - generic [ref=e63]: Max tool-exploration rounds
              - spinbutton "Max tool-exploration rounds" [ref=e64]: "12"
              - generic [ref=e65]: Upper bound on how many explore/answer rounds the agent may take while reading the repository (1-30). Higher values allow deeper analysis at higher token cost.
            - generic [ref=e66]:
              - generic [ref=e67]:
                - checkbox "Enable formal review decision" [ref=e68]
                - generic [ref=e69]: Enable formal review decision
              - generic [ref=e70]: When enabled, the bot may post a formal PR review decision (approve or request changes) based on the criteria configured below.
            - generic [ref=e71]:
              - generic [ref=e72]: Approval decision prompt
              - textbox "Approval decision prompt" [ref=e73]: "# Formal Review Decision Based on your review findings, decide whether to approve, request changes, or leave the PR review state unchanged. Use the following guidelines: - **APPROVE** — The PR is correct, follows best practices, and has no significant issues that should block merging. Minor style nits or optional suggestions do not warrant blocking. - **REQUEST_CHANGES** — The PR has non-trivial bugs, security concerns, missing error handling, broken tests, or significant code quality issues that must be fixed before merging. Regression risk alone is not enough — there must be an identifiable problem. - **NONE** — There are minor issues or suggestions, but nothing blocking. Also use NONE when you lack sufficient information to make a confident decision, or when the change is too large to assess reliably from the diff alone."
              - generic [ref=e74]: Criteria for when the bot should approve, request changes, or leave the PR review state unchanged. Only applies when the "Enable formal review decision" checkbox is ticked.
        - generic [ref=e75]:
          - generic [ref=e76]:
            - button "" [expanded] [ref=e77] [cursor=pointer]:
              - generic [ref=e78]: 
            - generic [ref=e79]:
              - checkbox "E2E Tests e2e-test TESTING" [ref=e80]
              - generic [ref=e81]:
                - strong [ref=e82]: E2E Tests
                - code [ref=e83]: e2e-test
                - generic [ref=e84]: TESTING
              - generic [ref=e85]: Deploys a preview of the pull request to its configured deployment target, then generates and runs end-to-end tests against the live preview and posts a Markdown summary. Requires a deployment target; supports re-run / regenerate slash commands.
          - generic [ref=e87]:
            - generic [ref=e88]:
              - generic [ref=e89]: Test framework
              - generic [ref=e90]:
                - radio "Playwright — Node.js — default, well-tested." [checked] [ref=e91]
                - generic [ref=e92]:
                  - strong [ref=e93]: Playwright
                  - text: — Node.js — default, well-tested.
              - generic [ref=e94]:
                - radio "pytest — Python — experimental." [ref=e95]
                - generic [ref=e96]:
                  - strong [ref=e97]: pytest
                  - text: — Python — experimental.
              - generic [ref=e98]:
                - radio "k6 — Grafana k6 load testing — experimental." [ref=e99]
                - generic [ref=e100]:
                  - strong [ref=e101]: k6
                  - text: — Grafana k6 load testing — experimental.
              - generic [ref=e102]:
                - radio "Cypress — Node.js — experimental." [ref=e103]
                - generic [ref=e104]:
                  - strong [ref=e105]: Cypress
                  - text: — Node.js — experimental.
            - generic [ref=e106]:
              - generic [ref=e107]: Max retries per test
              - spinbutton "Max retries per test" [ref=e108]: "1"
              - generic [ref=e109]: Per-test retry budget. A test that passes after retries is tagged FLAKY.
            - generic [ref=e110]:
              - generic [ref=e111]: Max test cases per suite
              - spinbutton "Max test cases per suite" [ref=e112]: "20"
              - generic [ref=e113]: Cost guard. Capped at 100 regardless of the configured value.
            - generic [ref=e114]:
              - generic [ref=e115]: Suite lifecycle
              - generic [ref=e116]:
                - radio "Ephemeral — Delete suite and test cases when the PR closes (default)." [checked] [ref=e117]
                - generic [ref=e118]:
                  - strong [ref=e119]: Ephemeral
                  - text: — Delete suite and test cases when the PR closes (default).
              - generic [ref=e120]:
                - radio "Offer as PR — Open a follow-up PR with the generated tests against the feature branch." [ref=e121]
                - generic [ref=e122]:
                  - strong [ref=e123]: Offer as PR
                  - text: — Open a follow-up PR with the generated tests against the feature branch.
              - generic [ref=e124]:
                - radio "Promote on merge — Once the parent PR merges, open a follow-up PR against the default branch." [ref=e125]
                - generic [ref=e126]:
                  - strong [ref=e127]: Promote on merge
                  - text: — Once the parent PR merges, open a follow-up PR against the default branch.
              - generic [ref=e128]:
                - radio "Commit to PR — Commit the generated tests directly onto the feature branch." [ref=e129]
                - generic [ref=e130]:
                  - strong [ref=e131]: Commit to PR
                  - text: — Commit the generated tests directly onto the feature branch.
            - generic [ref=e132]:
              - generic [ref=e133]: Promotion pass-rate threshold (%)
              - spinbutton "Promotion pass-rate threshold (%)" [ref=e134]: "100"
              - generic [ref=e135]: "Minimum percentage of executed test cases that must pass for the suite to be promoted (applies to offer-as-pr / commit-to-pr / promote-on-merge). 100 (default) means only fully green suites are promoted; lower it to e.g. 80 to also promote when at least 80% of the tests pass. Suites that ERROR or are SKIPPED are never promoted regardless of this value. Tip: LLM-generated tests are rarely 100% runnable on the first try — treat them as a regression-test baseline, not as ground truth."
        - generic [ref=e136]:
          - generic [ref=e137]:
            - button "" [expanded] [ref=e138] [cursor=pointer]:
              - generic [ref=e139]: 
            - generic [ref=e140]:
              - checkbox "PR Review review REVIEW" [checked] [ref=e141]
              - generic [ref=e142]:
                - strong [ref=e143]: PR Review
                - code [ref=e144]: review
                - generic [ref=e145]: REVIEW
              - generic [ref=e146]: Posts an AI code review as a single PR comment whenever a pull request is opened or updated, responds to @-mentions and inline review comments with conversational follow-ups, and optionally approves or requests changes based on the bot's configured post-review action.
          - generic [ref=e148]:
            - generic [ref=e149]:
              - generic [ref=e150]: Max diff chars per chunk
              - spinbutton "Max diff chars per chunk" [ref=e151]: "120000"
              - generic [ref=e152]: Maximum characters per diff chunk before splitting. The full PR diff is split into chunks of this size for review.
            - generic [ref=e153]:
              - generic [ref=e154]: Max diff chunks
              - spinbutton "Max diff chunks" [ref=e155]: "8"
              - generic [ref=e156]: Maximum number of diff chunks to review. Further chunks beyond this limit are skipped.
            - generic [ref=e157]:
              - generic [ref=e158]: Retry truncated chunk chars
              - spinbutton "Retry truncated chunk chars" [ref=e159]: "60000"
              - generic [ref=e160]: When a chunk is too large for the model's context window (prompt-too-long error), the chunk is truncated to this many characters and retried once.
            - generic [ref=e161]:
              - generic [ref=e162]: Excluded file patterns
              - textbox "Excluded file patterns" [ref=e163]
              - generic [ref=e164]: Comma-separated glob or filename patterns for files to exclude from the review diff (e.g. *.lock, *.min.js, package-lock.json, **/generated/**). Matching file sections are stripped before the diff is sent to the AI, reducing token usage and noise. Leave blank to review all files.
        - generic [ref=e165]:
          - generic [ref=e166]:
            - button "" [expanded] [ref=e167] [cursor=pointer]:
              - generic [ref=e168]: 
            - generic [ref=e169]:
              - checkbox "AI Unit Tests unit-test-author TESTING" [ref=e170]
              - generic [ref=e171]:
                - strong [ref=e172]: AI Unit Tests
                - code [ref=e173]: unit-test-author
                - generic [ref=e174]: TESTING
              - generic [ref=e175]: Generates white-box unit tests for the pull-request diff, runs them with the project's own test runner, and (optionally) commits the passing tests onto the PR branch. Works on a checkout of the PR head — no preview deployment needed.
          - generic [ref=e177]:
            - generic [ref=e178]:
              - generic [ref=e179]: Build/test toolchain
              - generic [ref=e180]:
                - radio "Auto-detect — Detect from pom.xml / build.gradle / package.json / go.mod / Cargo.toml / *.csproj / Gemfile / pyproject.toml / Makefile (default)." [checked] [ref=e181]
                - generic [ref=e182]:
                  - strong [ref=e183]: Auto-detect
                  - text: — Detect from pom.xml / build.gradle / package.json / go.mod / Cargo.toml / *.csproj / Gemfile / pyproject.toml / Makefile (default).
              - generic [ref=e184]:
                - 'radio "Maven (JUnit) — JVM — runs `mvn test`." [ref=e185]'
                - generic [ref=e186]:
                  - strong [ref=e187]: Maven (JUnit)
                  - text: "— JVM — runs `mvn test`."
              - generic [ref=e188]:
                - 'radio "Gradle (JUnit) — JVM — runs `gradle test`." [ref=e189]'
                - generic [ref=e190]:
                  - strong [ref=e191]: Gradle (JUnit)
                  - text: "— JVM — runs `gradle test`."
              - generic [ref=e192]:
                - 'radio "npm (Jest/Vitest) — Node.js — runs `npm test`." [ref=e193]'
                - generic [ref=e194]:
                  - strong [ref=e195]: npm (Jest/Vitest)
                  - text: "— Node.js — runs `npm test`."
              - generic [ref=e196]:
                - 'radio "pytest — Python — runs `python3 -m pytest`." [ref=e197]'
                - generic [ref=e198]:
                  - strong [ref=e199]: pytest
                  - text: "— Python — runs `python3 -m pytest`."
              - generic [ref=e200]:
                - 'radio "Go — Go — runs `go test ./...`." [ref=e201]'
                - generic [ref=e202]:
                  - strong [ref=e203]: Go
                  - text: "— Go — runs `go test ./...`."
              - generic [ref=e204]:
                - 'radio "Cargo (Rust) — Rust — runs `cargo test`." [ref=e205]'
                - generic [ref=e206]:
                  - strong [ref=e207]: Cargo (Rust)
                  - text: "— Rust — runs `cargo test`."
              - generic [ref=e208]:
                - 'radio ".NET — .NET — runs `dotnet test`." [ref=e209]'
                - generic [ref=e210]:
                  - strong [ref=e211]: .NET
                  - text: "— .NET — runs `dotnet test`."
              - generic [ref=e212]:
                - 'radio "Bundler (Ruby) — Ruby — runs `bundle exec rake test`." [ref=e213]'
                - generic [ref=e214]:
                  - strong [ref=e215]: Bundler (Ruby)
                  - text: "— Ruby — runs `bundle exec rake test`."
              - generic [ref=e216]:
                - 'radio "Make — Generic — runs `make test`." [ref=e217]'
                - generic [ref=e218]:
                  - strong [ref=e219]: Make
                  - text: "— Generic — runs `make test`."
              - generic [ref=e220]:
                - 'radio "C (gcc) — C — runs `make test` against the project Makefile." [ref=e221]'
                - generic [ref=e222]:
                  - strong [ref=e223]: C (gcc)
                  - text: "— C — runs `make test` against the project Makefile."
              - generic [ref=e224]:
                - 'radio "C++ (g++) — C++ — runs `make test` against the project Makefile." [ref=e225]'
                - generic [ref=e226]:
                  - strong [ref=e227]: C++ (g++)
                  - text: "— C++ — runs `make test` against the project Makefile."
            - generic [ref=e228]:
              - generic [ref=e229]: Max suite retries
              - spinbutton "Max suite retries" [ref=e230]: "1"
              - generic [ref=e231]: How many times to re-run a failing suite before reporting it failed (0-5).
            - generic [ref=e232]:
              - generic [ref=e233]: Max test files per PR
              - spinbutton "Max test files per PR" [ref=e234]: "10"
              - generic [ref=e235]: Cost guard. Capped at 50 regardless of the value.
            - generic [ref=e236]:
              - generic [ref=e237]: Generated-test lifecycle
              - generic [ref=e238]:
                - radio "Commit to PR — Commit the generated tests directly onto the PR branch (default)." [checked] [ref=e239]
                - generic [ref=e240]:
                  - strong [ref=e241]: Commit to PR
                  - text: — Commit the generated tests directly onto the PR branch (default).
              - generic [ref=e242]:
                - radio "Report only — Run and report the tests but do not commit them." [ref=e243]
                - generic [ref=e244]:
                  - strong [ref=e245]: Report only
                  - text: — Run and report the tests but do not commit them.
        - generic [ref=e246]:
          - button " Save selection" [ref=e247] [cursor=pointer]:
            - generic [ref=e248]: 
            - text: Save selection
          - link "Cancel" [ref=e249] [cursor=pointer]:
            - /url: /system-settings

Test source

  1   | import { test, expect, Page } from '@playwright/test';
  2   | 
  3   | const WORKFLOWS_URL = '/system-settings/workflow-configurations/1/workflows';
  4   | const PATTERN_VALUE = '*.yaml';
  5   | 
  6   | async function expandReviewSection(page: Page): Promise<void> {
  7   |   // Try to find a header/button/row that references the 'review' workflow.
  8   |   // We attempt several resilient locators to handle a variety of UI shells.
  9   |   const candidates = [
  10  |     page.getByRole('button', { name: /^review$/i }),
  11  |     page.getByRole('button', { name: /review/i }),
  12  |     page.getByRole('tab', { name: /^review$/i }),
  13  |     page.locator('[data-workflow="review"]'),
  14  |     page.locator('text=/^\\s*review\\s*$/i').first(),
  15  |   ];
  16  | 
  17  |   for (const candidate of candidates) {
  18  |     try {
  19  |       if (await candidate.count() > 0) {
  20  |         const first = candidate.first();
  21  |         await first.waitFor({ state: 'visible', timeout: 2000 });
  22  |         await first.click();
  23  |         // Give the section time to expand.
  24  |         await page.waitForTimeout(300);
  25  |         return;
  26  |       }
  27  |     } catch {
  28  |       // Try next candidate.
  29  |     }
  30  |   }
  31  | 
  32  |   throw new Error("Could not locate the 'review' workflow section to expand.");
  33  | }
  34  | 
  35  | async function getExcludedFilePatternsInput(page: Page) {
  36  |   // Wait for the input to become available inside the expanded review section.
  37  |   const byLabel = page.getByLabel(/excludedFilePatterns/i);
  38  |   const byPlaceholder = page.getByPlaceholder(/excludedFilePatterns/i);
  39  |   const byName = page.locator('input[name="excludedFilePatterns"], textarea[name="excludedFilePatterns"]');
  40  |   const byId = page.locator('#excludedFilePatterns');
  41  | 
  42  |   const locators = [byName, byLabel, byPlaceholder, byId];
  43  |   for (const loc of locators) {
  44  |     try {
  45  |       if (await loc.count() > 0) {
  46  |         const first = loc.first();
  47  |         await first.waitFor({ state: 'visible', timeout: 3000 });
  48  |         return first;
  49  |       }
  50  |     } catch {
  51  |       // continue
  52  |     }
  53  |   }
> 54  |   throw new Error("Could not locate the 'excludedFilePatterns' input field.");
      |         ^ Error: Could not locate the 'excludedFilePatterns' input field.
  55  | }
  56  | 
  57  | test.describe('Review workflow excluded file patterns persistence', () => {
  58  |   test("excludedFilePatterns value persists after saving and reloading", async ({ page }) => {
  59  |     let originalValue = '';
  60  | 
  61  |     try {
  62  |       // 1. Navigate to workflows configuration page.
  63  |       await page.goto(WORKFLOWS_URL);
  64  |       await page.waitForLoadState('networkidle');
  65  | 
  66  |       // 2. Expand the 'review' workflow section.
  67  |       await expandReviewSection(page);
  68  | 
  69  |       // 3. Locate the excludedFilePatterns input.
  70  |       let input = await getExcludedFilePatternsInput(page);
  71  | 
  72  |       // Capture original value so we can restore it.
  73  |       originalValue = (await input.inputValue().catch(() => '')) ?? '';
  74  | 
  75  |       // 4. Clear the field and enter the new value.
  76  |       await input.click();
  77  |       await input.fill('');
  78  |       await input.fill(PATTERN_VALUE);
  79  |       await expect(input).toHaveValue(PATTERN_VALUE);
  80  | 
  81  |       // 5. Click the 'Save selection' button.
  82  |       const saveButton = page.getByRole('button', { name: /save selection/i }).first();
  83  |       await saveButton.waitFor({ state: 'visible', timeout: 5000 });
  84  |       await saveButton.click();
  85  | 
  86  |       // 6. Wait for the save to complete (network activity settles).
  87  |       await page.waitForLoadState('networkidle');
  88  |       await page.waitForTimeout(500);
  89  | 
  90  |       // 7. Reload by navigating again.
  91  |       await page.goto(WORKFLOWS_URL);
  92  |       await page.waitForLoadState('networkidle');
  93  | 
  94  |       // 8. Re-expand the review section.
  95  |       await expandReviewSection(page);
  96  | 
  97  |       // Assertion: value still contains '*.yaml'.
  98  |       input = await getExcludedFilePatternsInput(page);
  99  |       await expect(input).toHaveValue(PATTERN_VALUE);
  100 |     } finally {
  101 |       // Cleanup: restore the original value so this test is idempotent.
  102 |       try {
  103 |         await page.goto(WORKFLOWS_URL);
  104 |         await page.waitForLoadState('networkidle');
  105 |         await expandReviewSection(page);
  106 |         const input = await getExcludedFilePatternsInput(page);
  107 |         await input.click();
  108 |         await input.fill('');
  109 |         if (originalValue.length > 0) {
  110 |           await input.fill(originalValue);
  111 |         }
  112 |         const saveButton = page.getByRole('button', { name: /save selection/i }).first();
  113 |         await saveButton.waitFor({ state: 'visible', timeout: 5000 });
  114 |         await saveButton.click();
  115 |         await page.waitForLoadState('networkidle');
  116 |         await page.waitForTimeout(500);
  117 |       } catch {
  118 |         // Best-effort cleanup; do not mask original test failure.
  119 |       }
  120 |     }
  121 |   });
  122 | });
  123 | 

@clicki-mc-testface clicki-mc-testface left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

E2E Test Run for PR #254

Framework: playwright
Preview environment: http://46.225.226.81:20042
Source SHA: 4d9bf73b
Outcome: ❌ FAILED (0/1 passed)

Test Status Duration
tests/review-workflow-excluded-file-patterns-persists.spec.ts
Excluded file patterns value persists on review workflow configuration
❌ FAILED 2.31s

1/1 failed

@max-california

Copy link
Copy Markdown
Collaborator

🤖 Follow-up

The failure is most likely not in persistence logic on the backend, but in the UI/test contract around the new field.

What looks correct

The new parameter is wired through the backend end-to-end:

  • ReviewWorkflow.paramsSchema() adds excludedFilePatterns as a TEXT field.
  • The workflow config form template renders all TEXT params as <textarea>.
  • WorkflowConfigurationController.extractWorkflowParams(...) collects params.<workflowKey>.<fieldName>.
  • WorkflowSelectionService.saveSelection(...) persists validated params.
  • ReviewWorkflow.run() reads excludedFilePatterns back via resolveParams(...).

So persistence support exists.

Most likely reason the Playwright test failed

The E2E test name is:

Excluded file patterns value persists on review workflow configuration

Given the implementation, the most probable issue is that the test is looking for a single-line input or a specific selector/field type, while the new field was implemented as:

  • label: “Excluded file patterns”
  • type: WorkflowParamField.ParamType.TEXT
  • rendered as: <textarea> in src/main/resources/templates/system-settings/workflow-configurations/workflows.html

So if the test expects an <input> or uses a selector tied to the previous conventions, it would fail even though persistence is implemented.

One thing to verify

There is also a secondary possibility: the field is described to users as comma-separated, but rendered as a multiline textarea. That is valid, but if the test fills multiline content and later asserts exact formatting, the validator trims only outer whitespace and persists the string as-is. Depending on how the test re-reads the field, formatting expectations could mismatch.

Concise answer

I don’t see an obvious backend bug that would prevent saving/reloading this value. The likely cause of the failed E2E is that the new Excluded file patterns field is implemented as a textarea in the workflow configuration UI, and the test likely isn’t targeting/asserting it the way the UI now renders it. If you want, I can help narrow it further by checking the expected selector/interaction pattern in the Playwright test file once it’s available.


Read-only agentic review follow-up by AI Git Bot

@tmseidel tmseidel merged commit c7ee03a into develop Jul 10, 2026
1 check passed
@tmseidel tmseidel deleted the feature/review-with-exclude-pattern branch July 10, 2026 10:19
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.

feat: exclude files from PR review by extension

3 participants