Skip to content

fix: use Page Object pattern for flaky Geb tests across all subprojects#15410

Open
jamesfredley wants to merge 8 commits intoapache:7.0.xfrom
jamesfredley:fix/flaky-geb-tests
Open

fix: use Page Object pattern for flaky Geb tests across all subprojects#15410
jamesfredley wants to merge 8 commits intoapache:7.0.xfrom
jamesfredley:fix/flaky-geb-tests

Conversation

@jamesfredley
Copy link
Contributor

Summary

Extends the Page Object pattern from #15394 to all remaining Geb functional tests across the repository, eliminating flaky bare title == 'X' assertions that fail intermittently due to race conditions between driver.get() and page title availability.

Changes

28 new Page Object classes created across 8 subprojects following the exact convention from #15394:

  • static String pageTitle - page title constant
  • static url - relative URL path
  • static at = { title == pageTitle } - at-check closure
  • static content - only when needed (e.g., form elements)

24 spec files refactored:

  • go('/url') replaced with to(PageObject) for initial navigation
  • Bare title == 'X' in then: blocks replaced with at(PageObject)
  • waitFor { title == PageObject.pageTitle } used only for post-click async transitions
  • go() + waitFor retained for special cases (context paths, interceptor routing, parameterized URLs)

Subprojects affected: app1, app2, app3, exploded, geb, hibernate5, mongodb (base + hibernate5), namespaces, scaffolding-fields, grails-geb (template)

Special Cases

  • app2/NotFoundHandlerSpec: Same URL returns different pages based on interceptor - kept go() with waitFor and Page Object pageTitle references
  • namespaces/ContextPathSpec: Context path navigation doesn't map to Page Object URLs - kept go() with waitFor
  • grails-geb template: Code generation template can't reference project-specific Page Objects - uses waitFor directly
  • geb subproject specs: Already used to(PageObject) correctly - reverted unnecessary waitFor additions
  • grails-data-neo4j: Not in current build, uses old GebSpec - skipped

Verification

  • ./gradlew codeStyle passes (all CodeNarc checks clean)
  • All new Page Object files include Apache license headers
  • No wildcard imports added
  • Follows existing 4-space indentation and code conventions

Refactor all Geb functional tests to use the Page Object pattern
established in PR apache#15394, replacing direct go() navigation and bare
title assertions with to(PageObject)/at(PageObject) calls.

Changes across 8 subprojects (app1, app2, app3, exploded, geb,
hibernate5, mongodb, namespaces) and scaffolding-fields:

- Create 28 new Page Object classes with static url, pageTitle, and
  at checks following the PR apache#15394 convention
- Replace go('/url') with to(PageObject) for initial page navigation
- Replace bare title == 'X' in then: blocks with at(PageObject)
- Use waitFor { title == PageObject.pageTitle } for post-click async
  page transitions only
- Keep go() with waitFor for special cases (context paths, interceptor
  routing, parameterized URLs)
- Update grails-geb FunctionalSpec template with waitFor for title
  checks since templates cannot reference project-specific Page Objects
- Revert unnecessary waitFor additions to geb subproject specs that
  already used to(PageObject) correctly

Assisted-by: Claude Code <Claude@Claude.ai>
Copilot AI review requested due to automatic review settings February 19, 2026 01:46
@jamesfredley jamesfredley moved this to In Progress in Apache Grails Feb 19, 2026
@jamesfredley jamesfredley self-assigned this Feb 19, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request extends the Geb Page Object pattern from PR #15394 to eliminate flaky test assertions across 8 subprojects in the repository. The PR aims to stabilize Geb functional tests by replacing direct go() + bare title == assertions with the Page Object pattern using to() and at() methods.

Changes:

  • Created 28 new Page Object classes following a consistent structure with pageTitle, url, and at properties
  • Refactored 24 spec files to use Page Objects for navigation and assertions
  • Removed debug println statements and unused imports (PendingFeature)

Reviewed changes

Copilot reviewed 49 out of 49 changed files in this pull request and generated 30 comments.

Show a summary per file
File Description
grails-test-examples/scaffolding-fields/src/integration-test/groovy/scaffoldingfields/FieldTypesSpec.groovy Refactored to use EmployeeCreatePage for setup navigation
grails-test-examples/scaffolding-fields/src/integration-test/groovy/scaffoldingfields/CrudFunctionalSpec.groovy Refactored CRUD test navigation to use Page Objects with proper waitFor for async transitions
grails-test-examples/namespaces/.../FrontendPagePage.groovy New Page Object for frontend page (naming issue: redundant "Page")
grails-test-examples/namespaces/.../AdminPagePage.groovy New Page Object for admin page (naming issue: redundant "Page")
grails-test-examples/namespaces/.../ReportPage.groovy New Page Object for admin report (non-namespaced)
grails-test-examples/namespaces/.../AdminReportPage.groovy New Page Object for admin report (namespaced)
grails-test-examples/namespaces/.../ReportControllerSpec.groovy Refactored to use Page Objects for navigation
grails-test-examples/namespaces/.../PageControllerSpec.groovy Refactored to use Page Objects, removed unused import
grails-test-examples/namespaces/.../ContextPathSpec.groovy Correctly kept go() with waitFor for context path testing
grails-test-examples/mongodb/hibernate5/.../BookShowPage.groovy New Page Object for book show page
grails-test-examples/mongodb/hibernate5/.../BookListPage.groovy New Page Object for book list page
grails-test-examples/mongodb/hibernate5/.../AuthorShowPage.groovy New Page Object for author show page
grails-test-examples/mongodb/hibernate5/.../AuthorListPage.groovy New Page Object for author list page
grails-test-examples/mongodb/hibernate5/.../BookControllerSpec.groovy Refactored to use Page Objects with waitFor for async save
grails-test-examples/mongodb/hibernate5/.../AuthorControllerSpec.groovy Refactored to use Page Objects with waitFor for async save
grails-test-examples/mongodb/base/.../BookShowPage.groovy New Page Object for book show page
grails-test-examples/mongodb/base/.../BookListPage.groovy New Page Object for book list page
grails-test-examples/mongodb/base/.../BookControllerSpec.groovy Refactored to use Page Objects with waitFor for async save
grails-test-examples/hibernate5/grails-hibernate/.../BookShowPage.groovy New Page Object for book show page
grails-test-examples/hibernate5/grails-hibernate/.../BookListPage.groovy New Page Object for book list page
grails-test-examples/hibernate5/grails-hibernate/.../BookControllerSpec.groovy Refactored to use Page Objects with waitFor for async save
grails-test-examples/exploded/.../LoginAuthPage.groovy New Page Object for login page
grails-test-examples/exploded/.../LoadAfterSpec.groovy Refactored to use LoginAuthPage
grails-test-examples/app3/.../LoginAuthPage.groovy New Page Object for login page
grails-test-examples/app3/.../LoadAfterSpec.groovy Refactored to use LoginAuthPage
grails-test-examples/app2/.../PageNotFoundPage.groovy New Page Object for 404 page
grails-test-examples/app2/.../FooListPage.groovy New Page Object for foo list with query param
grails-test-examples/app2/.../NotFoundHandlerSpec.groovy Special case: correctly uses go() with waitFor and Page Object constants
grails-test-examples/app1/.../FooFunctionalSpec.groovy Refactored to use FooListPage
grails-test-examples/app1/.../BarFunctionalSpec.groovy Refactored to use BarListPage
grails-test-examples/app1/.../PartialPage.groovy New Page Object for partial template page
grails-test-examples/app1/.../LoginAuthPage.groovy New Page Object for login page
grails-test-examples/app1/.../HomePage.groovy New Page Object for home page
grails-test-examples/app1/.../FooListPage.groovy New Page Object for foo list
grails-test-examples/app1/.../FooLayoutSnippetPage.groovy New Page Object for layout snippet
grails-test-examples/app1/.../FooLayoutPage.groovy New Page Object for layout page
grails-test-examples/app1/.../ConventionLayoutPage.groovy New Page Object for convention layout
grails-test-examples/app1/.../BookShowPage.groovy New Page Object for book show page
grails-test-examples/app1/.../BookListPage.groovy New Page Object for book list page
grails-test-examples/app1/.../BookCreatePage.groovy New Page Object with content definitions for form elements
grails-test-examples/app1/.../BarListPage.groovy New Page Object for bar list
grails-test-examples/app1/.../ActionReturnsNullPage.groovy New Page Object for action returns null test
grails-test-examples/app1/.../LayoutWithTemplateSpec.groovy Refactored to use PartialPage
grails-test-examples/app1/.../LayoutFunctionalSpec.groovy Refactored to use layout Page Objects
grails-test-examples/app1/.../MiscFunctionalSpec.groovy Refactored to use ActionReturnsNullPage
grails-test-examples/app1/.../LoadAfterSpec.groovy Refactored to use LoginAuthPage
grails-test-examples/app1/.../HomeSpec.groovy Refactored to use HomePage, removed debug code
grails-test-examples/app1/.../BookFunctionalSpec.groovy Refactored to use Page Objects, removed debug code, critical bug with parameterized navigation
grails-geb/src/main/templates/FunctionalSpec.groovy Updated template to use waitFor pattern for generated tests
Comments suppressed due to low confidence (2)

grails-test-examples/scaffolding-fields/src/integration-test/groovy/scaffoldingfields/CrudFunctionalSpec.groovy:237

  • For consistency with other waitFor statements in this file and to avoid hardcoding the title, consider using waitFor { title == EmployeeCreatePage.pageTitle } instead of the hardcoded string 'Create Employee'.
    grails-test-examples/app1/src/integration-test/groovy/functionaltests/pages/HomePage.groovy:27
  • The static url property should have a leading slash to match the established codebase convention. Change to url = '' for the root page (note: this is already an empty string, but existing HomePage in geb uses '/' for clarity).
    static url = ''

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

jamesfredley and others added 3 commits February 18, 2026 21:02
…minPagePage/FrontendPagePage

- Add leading slash to static url in all 28 new Page Objects for
  consistency with existing geb and scaffolding-fields conventions
- Rename AdminPagePage to AdminPage and FrontendPagePage to
  FrontendPage to eliminate redundant 'Page' suffix
- Update PageControllerSpec imports and usages for renamed classes

Assisted-by: Claude Code <Claude@Claude.ai>
@jdaugherty
Copy link
Contributor

Assuming @matrei is ok with these changes, I'm ok to merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

3 participants

Comments