From 73f0de4b80e26e2d33e77af18fa26c57dbc39a31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petter=20Holmstr=C3=B6m?= Date: Fri, 3 Jul 2026 11:44:34 +0300 Subject: [PATCH] Add forms mode-selection and TestBench reliability guidance (#5780) Consolidate best-practice guidance that was previously only available in the agent skills into the documentation: - fields-and-binding: add a "Choosing a Mode" scenario table mapping common form situations (Save/Cancel, wizard, inline grid edit, settings, filter bar) to buffered vs write-through Binder mode. - components-binder-beans: recommend explicit forField().bind() over bindInstanceFields for non-trivial forms, and note string property names are best reserved for record FDOs. - reliable-tests: add "Keeping Tests Focused and Independent" (reserve e2e for critical journeys, push coverage down to browserless, keep tests independent) and "Waiting for Asynchronous Updates" (wait via waitForFirst()/waitUntil() rather than Thread.sleep()). Co-authored-by: Claude Opus 4.8 --- .../add-form/fields-and-binding.adoc | 29 +++++++++++++++++++ .../binding-data/components-binder-beans.adoc | 3 ++ .../testing/end-to-end/reliable-tests.adoc | 14 +++++++++ 3 files changed, 46 insertions(+) diff --git a/articles/building-apps/forms-data/add-form/fields-and-binding.adoc b/articles/building-apps/forms-data/add-form/fields-and-binding.adoc index 93ade7e389..04ed140031 100644 --- a/articles/building-apps/forms-data/add-form/fields-and-binding.adoc +++ b/articles/building-apps/forms-data/add-form/fields-and-binding.adoc @@ -235,6 +235,35 @@ When using a JavaBean as an FDO, `Binder` can operate in *buffered* or *write-th * *Buffered mode*: Changes remain in the form until explicitly saved. This prevents side effects but may affect validation behavior. * *Write-through mode*: Updates the FDO immediately as the user edits the form. Business logic in setter methods is triggered immediately. However, invalid states can occur where the form contains errors, but the FDO remains valid. +==== Choosing a Mode + +Prefer buffered mode for most forms: it lets you implement a *Cancel* action without tracking state manually, and it defers writing until the data is valid. Reach for write-through mode when every change should take effect immediately. + +[cols="2,1,3"] +|=== +|Scenario |Mode |Why + +|Form with [guibutton]`Save`/[guibutton]`Cancel` buttons +|Buffered +|The user can discard changes; nothing is written until validation passes. + +|Multi-step wizard +|Buffered +|Validate each step before advancing. + +|Inline row editing in a Grid +|Buffered +|Save or cancel per row. + +|Settings panel +|Write-through +|Each change should apply immediately. + +|Search or filter bar +|Write-through +|Filtering should update live as the user types. +|=== + Form validation is covered in the <> guide. diff --git a/articles/flow/binding-data/components-binder-beans.adoc b/articles/flow/binding-data/components-binder-beans.adoc index 60491149eb..c50baf1b2b 100644 --- a/articles/flow/binding-data/components-binder-beans.adoc +++ b/articles/flow/binding-data/components-binder-beans.adoc @@ -91,6 +91,9 @@ binder.forField(gender) .bind(Person::getGender, Person::setGender); ---- +[TIP] +Prefer explicit [methodname]`forField().bind()` binding with getter and setter method references for anything beyond the simplest forms. It's more readable, is checked at compile time, and doesn't rely on field and property names matching. Automatic [methodname]`bindInstanceFields()` binding is convenient for simple forms, but its reliance on naming conventions and its use of `@PropertyId` and [methodname]`forMemberField()` to handle mismatches make it harder to maintain as a form grows. Binding by string property name is best reserved for record FDOs, whose component accessors can't be referenced as method references. + === Specifying Property Names diff --git a/articles/flow/testing/end-to-end/reliable-tests.adoc b/articles/flow/testing/end-to-end/reliable-tests.adoc index f9376ad6ba..fe488314fb 100644 --- a/articles/flow/testing/end-to-end/reliable-tests.adoc +++ b/articles/flow/testing/end-to-end/reliable-tests.adoc @@ -24,6 +24,13 @@ As with any code, it's important to write tests so that the reader understands t If the test is full of low-level details about how you locate the parts of the application with which you want to interact, it becomes overwhelming to try to decode what the test is actually trying to verify. By using page/view objects, you can abstract away the low-level details about how the view is built and what exact components are used. You can also use <> (BDD) to describe your test scenarios using normal English sentences. +== Keeping Tests Focused and Independent + +End-to-end tests are expensive to run compared to <<{articles}/flow/testing/browserless#,browserless tests>>, so use them deliberately. Cover each critical user journey -- such as logging in, or completing a checkout -- with one focused test rather than trying to exercise every edge case through the browser. Push comprehensive coverage of component logic, validation, and view behavior down to faster browserless tests, and keep the end-to-end suite for the paths that genuinely need a real browser. + +Make each test independent: it should set up the state it needs and not rely on another test having run first. Independent tests can run in any order or in parallel, and a failure points to a single scenario instead of a broken chain. + + == Guarding against Application Changes If your application never changes, you can test it manually once and know that it works. However, applications are usually developed forward and you will need to maintain the tests as the application evolves. @@ -45,6 +52,13 @@ Use `ids` which describe the action that occurs when the button is pressed, not, If your `id` is tied to the hierarchy, you indirectly depend on the hierarchy and lose many benefits of using `ids`. +== Waiting for Asynchronous Updates + +Views often update asynchronously -- after navigation, a server round-trip, or lazy data loading -- so an element you want to interact with may not be present the moment a test looks for it. Don't paper over this with fixed pauses such as `Thread.sleep()`: a sleep that's long enough on a fast machine is often too short on a loaded CI server, and any sleep that's reliable is slower than it needs to be. + +Instead, wait for the condition you actually care about. Use [methodname]`waitForFirst()` on an [classname]`ElementQuery` to wait until a matching element appears, or [methodname]`waitUntil()` to wait for an arbitrary condition. Explicit waits return as soon as the condition is met, so they are both faster and more reliable than fixed delays. + + == Dealing with Test Environment Problems When dealing with browser-based tests, you need to take into account that the environment isn't always as stable as you would want it to be.