Context
Multi-column layout (row/column/cell axes) shipped in feat/columns_support. Manual QA covered the cases in plan.md §Chunk 8 but no automated coverage exists. e2e infra is set up (frontend/playwright.config.ts, helpers in frontend/e2e/helpers/) — extend it.
Scope
New file: frontend/e2e/specs/form-layout.spec.ts.
Helper extensions in frontend/e2e/helpers/form-builder.ts:
fieldCard(label), ejectField(label)
dragFieldToColumnZone(sourceLabel, atRow, atCol)
dragFieldToRowZone(sourceLabel, atRow)
dragFieldOntoCell(sourceLabel, targetLabel, position)
rowCount(), columnCount(rowIdx), cellCount(rowIdx, colIdx)
Test cases
| # |
Description |
| 1 |
Drop field onto existing field cell stacks it into that column |
| 2 |
Drop on ColumnDropZone creates a new column |
| 3 |
Drop on RowDropZone creates a new row |
| 4 |
Eject button on multi-cell column moves cell to new row |
| 5 |
Cross-row drag collapses source column when emptied |
| 6 |
Within-column reorder renumbers cell_index |
| 7 |
Mobile viewport stacks columns vertically (/p/<route>) |
| 8 |
Conditional-visibility hides field + collapses empty column |
Drag mechanics
SortableJS + Playwright: synthetic locator.dragTo() often misses swap thresholds. Use real mouse path:
await page.mouse.move(srcX, srcY);
await page.mouse.down();
await page.mouse.move(midX, midY, { steps: 8 });
await page.mouse.move(dstX, dstY, { steps: 8 });
await page.mouse.up();
May need forceFallback: true on <draggableComponent> in FormBuilderContent.vue for deterministic headless behavior. Test early, tag flaky cases test.fixme until stable.
Context
Multi-column layout (row/column/cell axes) shipped in
feat/columns_support. Manual QA covered the cases inplan.md§Chunk 8 but no automated coverage exists. e2e infra is set up (frontend/playwright.config.ts, helpers infrontend/e2e/helpers/) — extend it.Scope
New file:
frontend/e2e/specs/form-layout.spec.ts.Helper extensions in
frontend/e2e/helpers/form-builder.ts:fieldCard(label),ejectField(label)dragFieldToColumnZone(sourceLabel, atRow, atCol)dragFieldToRowZone(sourceLabel, atRow)dragFieldOntoCell(sourceLabel, targetLabel, position)rowCount(),columnCount(rowIdx),cellCount(rowIdx, colIdx)Test cases
ColumnDropZonecreates a new columnRowDropZonecreates a new rowcell_index/p/<route>)Drag mechanics
SortableJS + Playwright: synthetic
locator.dragTo()often misses swap thresholds. Use real mouse path:May need
forceFallback: trueon<draggableComponent>inFormBuilderContent.vuefor deterministic headless behavior. Test early, tag flaky casestest.fixmeuntil stable.