fix(apptest): manifest mirrors the rendered UI; runner handles empty/tree/calendar layouts - #6323
Merged
Merged
Conversation
…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>
…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, '\\$&') + '$')); |
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Running the generated
<name>.testmanifests 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/testrunner. 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)readOnly:,uuid, calculated, aggregate (totals footer) anddependsOn(auto-populated) fields render without an editable input - the runner must not fill them (fillForm hung 60s on#f_Uuidacross six modules).hierarchy: truefor tree entities (Account rendersrole=treeitem, no columnheaders);MANAGE_CALENDAR → calendar,MANAGE_SLOTS → slots(aview: range/slotsentity was reportedmanage-list).apiAbsoluteowner-module controller URL (resolved viaCrossModelSupport, 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.function: EntityStatusrelations render as a pill, value from theinit:DB default - never pick/post).api(a relation to a composition DETAIL - PayrollEntry→Payslip - is not in the manifest's entities).{relation, filterBy}+ where{by, value}+ exactlyOne check sets + leafOnly{hierarchyProperty}ride into the manifest so the runner picks offered, consistent, valid samples.npm/testrunnerEditalso matched the "Credit Notes" sidebar item (navigating away mid-flow),Newmatched the empty-state's "New ",Typematched "Chart Type". Action buttons exact; comboboxes anchored-prefix (their accessible name includes the placeholder).whereoption filters, leafOnly targets (a row no other row parents), exactlyOne field sets (keep the first), optional relations with no rows / no label left unset.response.request()(absent onAPIResponse) - the thrower itself threw and masked every real REST failure. Timestamp samples are full ISO instants (java.time.Instantrejects 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)
documentTitlefield is un-enterable in the Harmonia document create form (DOCUMENT_NUMBERrenders only in the title);release.ymldoes not publish@aerokit/testto npmjs;Testing
AppTestIntentGeneratorTestextended each wave - 6/6 green (readOnly/hierarchy/cross-model/dependsOn/where/exactlyOne/leafOnly emission).opts.extendskip mechanism).🤖 Generated with Claude Code