Skip to content

fix(apptest): manifest mirrors the rendered UI; runner handles empty/tree/calendar layouts - #6323

Merged
delchev merged 6 commits into
masterfrom
fix/apptest-manifest-layouts
Jul 20, 2026
Merged

fix(apptest): manifest mirrors the rendered UI; runner handles empty/tree/calendar layouts#6323
delchev merged 6 commits into
masterfrom
fix/apptest-manifest-layouts

Conversation

@delchev

@delchev delchev commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Running the generated <name>.test manifests across all 28 KeyFolders intent modules (the first fleet-wide consumer of #6289) surfaced six waves of defect classes in the AppTest manifest generator and the @aerokit/test runner. With this PR the whole 28-module fleet is green (verified against a live instance with every module + data/demo companions published).

AppTestIntentGenerator (manifest must mirror the rendered UI)

  • auto-readOnly: authored readOnly:, uuid, calculated, aggregate (totals footer) and dependsOn (auto-populated) fields render without an editable input - the runner must not fill them (fillForm hung 60s on #f_Uuid across six modules).
  • layouts: hierarchy: true for tree entities (Account renders role=treeitem, no columnheaders); MANAGE_CALENDAR → calendar, MANAGE_SLOTS → slots (a view: range/slots entity was reported manage-list).
  • cross-model relations are now emitted WITH an apiAbsolute owner-module controller URL (resolved via CrossModelSupport, the same coordinates the generated dropdowns use) - every transactional document has REQUIRED cross-model FKs (Customer/Employee/Supplier) that previously made both UI and REST create 400.
  • entityStatus marking (function: EntityStatus relations render as a pill, value from the init: DB default - never pick/post).
  • same-model relations carry their relative api (a relation to a composition DETAIL - PayrollEntry→Payslip - is not in the manifest's entities).
  • dependsOn {relation, filterBy} + where {by, value} + exactlyOne check sets + leafOnly {hierarchyProperty} ride into the manifest so the runner picks offered, consistent, valid samples.

npm/test runner

  • empty-state / tree / calendar-slots list handling; strict seed-row assertion kept.
  • exact / anchored locators: Playwright's default name match is a case-insensitive substring - Edit also matched the "Credit Notes" sidebar item (navigating away mid-flow), New matched the empty-state's "New ", Type matched "Chart Type". Action buttons exact; comboboxes anchored-prefix (their accessible name includes the placeholder).
  • row lookup via the toolbar "Search ..." box - the per-column filter row's first input can be a hidden FK filter or a date input.
  • document layout: CREATE lands on the new record's page (line-item editing continues); SAVE from an edit returns to the list.
  • edit-load wait: the record loads async after the form renders - filling before the fetch completes was overwritten by the load and Save persisted the OLD value.
  • consistent samples: dependsOn cascades (dependent row first, its FK re-points the trigger sample; triggers picked before dependents), where option filters, leafOnly targets (a row no other row parents), exactlyOne field sets (keep the first), optional relations with no rows / no label left unset.
  • api client: the error path called response.request() (absent on APIResponse) - the thrower itself threw and masked every real REST failure. Timestamp samples are full ISO instants (java.time.Instant rejects zone-less values); the UI fill slices to the datetime-local shape.

Found along the way (filed in the KeyFolders upstream plan, not part of this PR)

Testing

  • AppTestIntentGeneratorTest extended each wave - 6/6 green (readOnly/hierarchy/cross-model/dependsOn/where/exactlyOne/leafOnly emission).
  • Live verification: manifests regenerated on this branch's jar for all 28 KeyFolders modules; the full Playwright fleet run is green (documented per-module skips for the platform findings above use the runner's existing opts.extend skip mechanism).

🤖 Generated with Claude Code

delchev and others added 4 commits July 20, 2026 18:25
…tree/calendar layouts

Running the generated <name>.test manifests across all 28 KeyFolders intent
modules (the first fleet-wide consumer) surfaced six defect classes; every
fix keeps the pilot (kf-mod-countries) green.

AppTestIntentGenerator:
- readOnly now mirrors the generated form exactly: an authored readOnly, a
  uuid field, or a calculated field renders without an editable input, so
  the runner must not try to fill it (a Company/Customer/Employee Uuid made
  fillForm wait 60s on a non-existent #f_Uuid input).
- emits hierarchy: true for tree entities (Account rendered role=treeitem,
  no columnheaders - the list flow asserted a table that never exists).
- layout maps MANAGE_CALENDAR -> calendar and MANAGE_SLOTS -> slots (a
  view: range/slots entity was reported manage-list and the runner walked a
  table that is a calendar).

npm/test runner:
- list flow: an entity with no rows renders the Harmonia empty state (no
  table at all) - assert columns only when the table is present, keep the
  strict row assertion when expectSeedData; tree entities assert treeitems;
  calendar/slots assert the calendar/slot-picker container.
- crud flow: 'New' button located with exact: true (the empty state adds a
  second 'New <Entity>' button that a substring match also hits); tree and
  calendar/slots entities skip the UI walk (REST covers CRUD); entities
  without a string handle field skip it too instead of throwing.
- rest flow: degrades gracefully without a string handle (create/read/
  delete still assert; the update-value round-trip is skipped).
- pickDropdown: combobox located with exact: true ('Type' also matched
  'Chart Type').
- api client: the error path called response.request(), which does not
  exist on Playwright APIResponse - the thrower itself threw and masked
  every real REST failure.

Unit tests extended (readOnly/hierarchy emission, entity counts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…match

- list.js: the empty-state markup stays in the DOM (x-show) above the table,
  so the unfiltered union's .first() picked the hidden element and failed on
  every list WITH data; filter({ visible: true }) on both arms.
- form.js: exact combobox matching found nothing (the accessible name is the
  label plus placeholder/selected value); an anchored prefix regex keeps the
  'Type' vs 'Chart Type' collision fixed without breaking the normal case.

Countries pilot re-verified green on a live instance after both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ate awareness

The fleet run's remaining systemic failure: every transactional document
(SalesInvoice, ExpenseClaim, SalaryAssignment, PurchaseInvoice, SalesOrder,
...) has REQUIRED cross-model FKs (Customer, Employee, Supplier), which the
manifest omitted - so both the UI create and the REST create posted a
payload missing a required FK and got 400.

AppTestIntentGenerator:
- cross-model to-one relations are now emitted WITH an apiAbsolute
  controller URL in the owner module (resolved via CrossModelSupport, the
  same coordinates the generated dropdowns use) + the owner's label field;
  unresolvable targets are omitted with a warning (the EDM generator
  already fails loudly for truly missing owners).
- a function: EntityStatus relation is marked entityStatus: the form
  templates exclude it from editable inputs (status pill) and its value
  comes from the init: DB default - the runner must neither pick nor post
  it.
- an aggregate field is auto-readOnly (renders in the document totals
  footer, not as an input - CreditNote.Net made fillForm wait forever).

npm/test runner:
- resolveRelationSamples: skips entityStatus relations, resolves
  cross-model rows via apiAbsolute (new api.listPath), leaves an OPTIONAL
  relation unset when its target has no rows (only a required one throws);
  the REST flow now shares this resolution instead of duplicating it.
- crud.js: all action buttons located with exact: true - Playwright's
  default name matching is a case-insensitive substring, so 'Edit' also
  matched the 'CrEDIT Notes' sidebar item and navigated away mid-flow.

Unit tests extended (cross-model apiAbsolute via convention fallback,
aggregate readOnly).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t relations, Instant samples

Fourth defect wave from the 28-module fleet run:

- crud.js filterBy: row lookup goes through the toolbar 'Search <Entity>...'
  box (present on every list layout) instead of the per-column filter row,
  whose first input can belong to an FK column (x-show hidden - fill hung)
  or a date column; master-detail pages have no filter row at all.
- crud.js: a document-layout form deliberately stays on the record after
  Save (header-items editing continues) - assert /edit and navigate back to
  the list instead of expecting the list URL.
- AppTestIntentGenerator + runner: same-model relations carry their
  relative controller path (api), so a relation targeting a composition
  DETAIL (excluded from the manifest's entities list - PayrollEntry ->
  Payslip) still resolves sample rows.
- sample-values/form: timestamp samples are full ISO instants (the
  generated entities bind java.time.Instant, which rejects a zone-less
  value - Appointment REST create got 400); the UI fill slices to the
  datetime-local shape.

Unit tests extended (same-model relation api emission).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread npm/test/src/flows/crud.js Fixed
…ere filters, exactlyOne checks

Fifth (and final) defect wave from the 28-module fleet run - all generic
DSL features the runner must honor:

- crud.js: a document CREATE lands on the new record's page (line-item
  editing continues there), like Save - assert /edit and navigate back.
- dependsOn cascade (Country -> City): independent first-row samples pick
  e.g. Country=Afghanistan + City=Sofia, and the narrowed dropdown then
  offers no matching option. The manifest now carries the relation's
  dependsOn {relation, filterBy}; the runner picks the dependent row first,
  re-points the trigger sample at the row's FK, and fills triggers before
  dependents. A dependsOn FIELD (auto-populated, e.g. SupplierNumber) is
  marked readOnly - the watcher fills it, not the runner.
- where: static option filters (a stock line's Product picker offering
  only Type=1) ride into the manifest; the runner picks a MATCHING row
  client-side instead of the first one.
- exactlyOne checks (journal PostingRule / JournalEntry lines): a sample
  filling every field is rejected with 400 - the manifest carries the
  check's field sets and sampleRecord keeps only the first of each.

Unit tests extended (dependsOn/where/exactlyOne emission).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
await expect(page).toHaveURL(/\/edit$/);
await page.goto(manifest.standaloneShell + entity.route);
} else {
await expect(page).toHaveURL(new RegExp(entity.route.replace(/[#/]/g, '\\$&') + '$'));
Comment thread npm/test/src/flows/crud.js Fixed
…ow, option-click retry

Final defect wave - the full 28-module KeyFolders fleet is green with these:

- leafOnly relations (chart-of-accounts pickers): the generated validation
  rejects a non-leaf target, but the runner sampled the FIRST account row
  ('10 Capital', a group) and got 400 while the UI picker (leaves only)
  passed. The manifest carries leafOnly {hierarchyProperty} (same-model
  from the target's hierarchy:, cross-model from TargetInfo); the runner
  picks a row no other row parents.
- crud edit flow: the record loads async after the form renders - filling
  before the fetch completes got overwritten by the load, and Save
  persisted the OLD value (services Ticket caught it; my manual replay
  passed only because of its think-time waits). The runner now waits for
  the handle input to show the loaded value before typing.
- document layout, definitively: CREATE lands on the new record's page
  (line-item editing continues there); SAVE from an edit returns to the
  list. (The two earlier commits had each half inverted.)
- pickDropdown: an option list re-rendering mid-click (async load reflow)
  made the click retry forever - reopen and force-retry once.
- resolveRelationSamples: an optional relation whose target has no
  name-like label field is left unset instead of clicking blind.

Unit tests extended (leafOnly emission).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@delchev
delchev merged commit 6ec4090 into master Jul 20, 2026
9 of 10 checks passed
@delchev
delchev deleted the fix/apptest-manifest-layouts branch July 20, 2026 18:45
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.

2 participants