codeconsole opened a new issue, #16217:
URL: https://github.com/apache/grails-core/issues/16217

   `grails-test-examples/scaffolding` fails intermittently in CI, on four 
occasions between 24 and 25 August 2026, on four unrelated branches. The root 
cause is not yet known; this issue records what is established so far.
   
   ## The test
   
   `com.example.UserControllerSpec > User list` — 
`grails-test-examples/scaffolding/src/integrationTest/groovy/com/example/UserControllerSpec.groovy`
   
   ```groovy
   void "User list"() {
       when: 'an unauthenticated user requests the user list and signs in when 
prompted'
       via(UserListPage)
       at(LoginPage).login()
   
       then: 'the saved request redirects to the user list'
       at(UserListPage)
       scaffoldTable
   }
   ```
   
   It fails inside `LoginPage.login()`, on the wait that follows the click:
   
   ```groovy
   void login(String username = '[email protected]', String password = 'letmein') 
{
       this.username = username
       this.password = password
       loginButton.click()
       waitFor { title != pageTitle && $('input', name: 'username').empty }   
// times out here
   }
   ```
   
   ```
   geb.waiting.WaitTimeoutException: condition did not pass in 30 seconds 
(failed with exception)
       at com.example.pages.LoginPage.login(LoginPage.groovy:39)
       at com.example.UserControllerSpec.User list(UserControllerSpec.groovy:48)
   
       Caused by: Assertion failed:
   
       title != pageTitle && $('input', name: 'username').empty
       |     |  |         |
       |     |  |         false
       |     |  'Please sign in'
       |     false
       'Please sign in'
   ```
   
   ## Occurrences
   
   | Date (UTC) | PR | Job | Duration |
   |---|---|---|---|
   | 2026-08-24 05:34 | #16150 | [Build Grails with Groovy snapshot (shard 
2)](https://github.com/apache/grails-core/actions/runs/32693786704/job/97332626174)
 | 55m |
   | 2026-08-24 05:34 | #16184 | [Functional Tests (Java 25, 
indy=false)](https://github.com/apache/grails-core/actions/runs/32693980319/job/97332597354)
 | 59m |
   | 2026-08-24 21:35 | #16209 | [Functional Tests (Java 25, 
indy=false)](https://github.com/apache/grails-core/actions/runs/32780264043/job/97600561629)
 | 51m |
   | 2026-08-24 23:29 | #16215 | [Functional Tests (Java 21, 
indy=true)](https://github.com/apache/grails-core/actions/runs/32789589183/job/97628349227)
 | 43m |
   
   None of those four branches touches this application, Spring Security, or 
Geb. It has been seen on Java 21 and Java 25, with indy on and off, and in two 
different workflows. In each run the other two specifications of the same 
application - `BookControllerSpec` and `UserCommunityControllerSpec`, which 
sign in the same way - passed.
   
   ## What the browser was looking at
   
   `@ContainerGebConfiguration(reporting = true)` means Geb captures the page 
at the failure. The capture is the same in all four, to the byte:
   
   - 1089 bytes, `<title>Please sign in</title>` - Spring Security's generated 
login page
   - both fields **empty**, showing their placeholders
   - the focus ring still on the username field, where `autofocus` puts it on 
load
   - **no error**: not `/login?error`, no "Bad credentials" banner
   - a `_csrf` hidden field present
   
   Two things follow. The page was loaded *after* the credentials were typed, 
because a page that had been typed into would hold the values and would have 
the focus on the second field. And the request that produced it was not a 
rejected authentication attempt, because that renders `?error` with a banner.
   
   ## What the failure is not
   
   Determined by injecting faults locally into the same test and comparing the 
captured page:
   
   | Injected fault | Page produced | Matches CI |
   |---|---|---|
   | Session cookie deleted between filling and submitting | 1089 bytes, login 
page, no error, autofocus | **yes, byte for byte** |
   | Valid session, `_csrf` field overwritten with a stale value | 283 bytes, 
`Whitelabel Error Page` (403) | no |
   
   So the form reaches the server **without a session cookie**, rather than 
with a stale or mismatched CSRF token. Missing session and wrong token are 
answered differently: with no session Spring Security treats the caller as 
anonymous and returns the login page, while a wrong token on a live session is 
denied with a 403.
   
   It is also not the credentials or the user data: 
`UserService.loadUserByUsername` looks the user up by email, and a lookup 
failure would produce `?error`, which is absent.
   
   ## What is still unknown
   
   Why the browser has no session cookie when the form is submitted. The 
specification clears cookies in `setup()`:
   
   ```groovy
   void setup() {
       clearCookiesQuietly()
   }
   ```
   
   and `ContainerGebSpec` holds `@Shared static GebTestManager testManager`, so 
the browser is shared between specifications in a worker. Whether the cookie is 
cleared, never set, scoped to a different host than the one the form posts to, 
or lost some other way is not established.
   
   A consequence worth noting for anyone attempting a fix: if the session is 
genuinely gone, Spring Security's saved request goes with it, so simply 
submitting the form again is not enough - the login then succeeds but lands on 
`/` rather than the user list, and the specification fails on 
`at(UserListPage)` instead. Confirmed locally.
   
   ## Reproducing
   
   Not reproduced on demand. Four consecutive local runs of 
`:grails-test-examples-scaffolding:integrationTest` passed. The failure 
signature can be produced deliberately by deleting the browser's cookies 
between filling the form and clicking, which is how the comparison above was 
made.
   
   ## Suggested next step
   
   Capture the browser's cookies and `currentUrl` into the Geb report when that 
wait fails. One further occurrence would then distinguish "no cookie at all" 
from "a cookie scoped to another host", which is what the fix depends on.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to