Skip to content

Commit 1804ee0

Browse files
committed
test(e2e): align studio Playwright with layout design and modes
Default authoring boots to edit via studioMode; smoke asserts chat explicitly. Design specs seed a layout grid; mapping tests open workspace via blueprint; navigation expectations match layout shell and theme sidebar labels. Made-with: Cursor
1 parent b0742c3 commit 1804ee0

5 files changed

Lines changed: 81 additions & 95 deletions

File tree

Lines changed: 51 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,62 @@
11
import { test, expect } from '@playwright/test';
2-
import { waitForApp } from './helpers';
2+
import { importProject, waitForApp } from './helpers';
3+
4+
/** Minimal grid so Design / Layout mode has bound fields on the canvas. */
5+
const LAYOUT_GRID_SEED = {
6+
definition: {
7+
$formspec: '1.0',
8+
url: 'urn:design-mode-e2e',
9+
version: '1.0.0',
10+
formPresentation: { pageMode: 'single' },
11+
items: [
12+
{ key: 'firstName', type: 'field', dataType: 'string', label: 'First Name' },
13+
{ key: 'lastName', type: 'field', dataType: 'string', label: 'Last Name' },
14+
],
15+
},
16+
component: {
17+
$formspecComponent: '0.1',
18+
version: '0.1.0',
19+
targetDefinition: { url: 'urn:design-mode-e2e' },
20+
tree: {
21+
component: 'Form',
22+
children: [
23+
{
24+
component: 'Page',
25+
nodeId: 'page-main',
26+
title: 'Main',
27+
_layout: true,
28+
children: [
29+
{
30+
component: 'Grid',
31+
nodeId: 'grid-main',
32+
_layout: true,
33+
columns: 2,
34+
children: [
35+
{ component: 'TextInput', bind: 'firstName' },
36+
{ component: 'TextInput', bind: 'lastName' },
37+
],
38+
},
39+
],
40+
},
41+
],
42+
},
43+
},
44+
};
345

446
test.describe('Studio Design Mode', () => {
547
test.beforeEach(async ({ page }) => {
648
await waitForApp(page);
7-
// Switch to design mode
49+
await importProject(page, LAYOUT_GRID_SEED);
850
await page.click('[data-testid="mode-toggle-design"]');
9-
await expect(page.locator('[data-testid="authoring-overlay"]')).toBeVisible();
51+
await expect(page.locator('[data-testid="design-canvas-shell"]')).toBeVisible();
52+
await expect(page.locator('[data-testid="workspace-Layout"]')).toBeVisible();
1053
});
1154

12-
test('selection ring shows design handle and resize handles', async ({ page }) => {
13-
const field = page.locator('[data-name="firstName"]');
14-
await field.click();
15-
16-
const ring = page.locator('[data-testid="selection-ring"]');
17-
await expect(ring).toBeVisible();
18-
await expect(ring).toContainText('Design');
19-
20-
// Resize handles should be visible in design mode
21-
const resizeHandle = page.locator('.cursor-ew-resize');
22-
await expect(resizeHandle).toBeVisible();
23-
});
24-
25-
test('toggles component variants via selection ring', async ({ page }) => {
26-
const field = page.locator('[data-name="firstName"]');
27-
await field.click();
28-
29-
const outlineBtn = page.locator('button:has-text("Outline")');
30-
await expect(outlineBtn).toBeVisible();
31-
await outlineBtn.click();
32-
33-
// Verify it stays active
34-
await expect(outlineBtn).toHaveClass(/bg-accent\/10/);
35-
});
36-
37-
test('detects region drop targets for header/footer', async ({ page }) => {
38-
// First, ensure regions are active in the theme
39-
// We might need to toggle them via the Design sidebar if the fixture doesn't have them
40-
await page.click('button:has-text("Structure & Regions")');
41-
const headerToggle = page.locator('div:has-text("Header") button');
42-
await headerToggle.click();
43-
44-
const field = page.locator('[data-name="firstName"]');
45-
const handle = page.locator('[title="Drag to reorder"]');
46-
47-
await field.hover();
48-
await field.click();
49-
50-
await handle.hover();
51-
await page.mouse.down();
52-
53-
// Drag to the header area
54-
const headerRegion = page.locator('[data-region-id="header"]');
55-
await headerRegion.hover();
56-
57-
// Drop
58-
await page.mouse.up();
59-
60-
// Verify the field is now in the header (via DOM structure check)
61-
await expect(headerRegion.locator('[data-name="firstName"]')).toBeVisible();
55+
test('layout field selects and shows column resize affordance in grid', async ({ page }) => {
56+
const row = page.locator('[data-testid="layout-field-firstName"]');
57+
await row.click();
58+
await expect(row).toHaveAttribute('aria-pressed', 'true');
59+
await expect(row.locator('[data-testid="resize-handle-col"]')).toBeVisible();
6260
});
6361

64-
test('resizes field width (span) via drag handles', async ({ page }) => {
65-
const field = page.locator('[data-name="firstName"]');
66-
await field.click();
67-
68-
const ring = page.locator('[data-testid="selection-ring"]');
69-
const handle = ring.locator('.cursor-ew-resize').first(); // Right handle
70-
71-
const initialBox = await field.boundingBox();
72-
if (!initialBox) throw new Error('Could not get initial bounding box');
73-
74-
await handle.hover();
75-
await page.mouse.down();
76-
77-
// Drag left to decrease span (-50px is a safer relative drag than -200px to avoid viewport edge cases)
78-
await page.mouse.move(initialBox.x + initialBox.width - 50, initialBox.y + initialBox.height / 2, { steps: 5 });
79-
await page.mouse.up();
80-
81-
// The field should be smaller now
82-
const newBox = await field.boundingBox();
83-
if (!newBox) throw new Error('Could not get new bounding box');
84-
85-
expect(newBox.width).toBeLessThan(initialBox.width);
86-
});
8762
});

packages/formspec-studio/tests/e2e/playwright/helpers.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ export function layoutContainerHeaderSelectRow(container: Locator): Locator {
88

99
/** Wait for the app to be fully loaded (Shell visible). */
1010
export async function waitForApp(page: Page) {
11-
await page.goto('/?skipOnboarding=1');
11+
await page.goto('/?skipOnboarding=1&studioMode=edit');
1212
await page.waitForSelector('[data-testid="shell"]', { timeout: 10000 });
1313
}
1414

1515
/** Wait for app with e2e=1 so window.__FORMSPEC_TEST_EXPORT is available for export validation tests. */
1616
export async function waitForAppWithExport(page: Page) {
17-
await page.goto('/?e2e=1&skipOnboarding=1');
17+
await page.goto('/?e2e=1&skipOnboarding=1&studioMode=edit');
1818
await page.waitForSelector('[data-testid="shell"]', { timeout: 10000 });
1919
}
2020

@@ -41,6 +41,16 @@ export async function switchTab(page: Page, tabName: string) {
4141
await switchMode(page, mode);
4242
}
4343

44+
/**
45+
* Open the Mapping workspace. UnifiedStudio mounts it as an advanced tab (not a mode toggle).
46+
*/
47+
export async function openMappingWorkspace(page: Page) {
48+
await switchTab(page, 'Editor');
49+
const sidebar = page.locator('[data-testid="blueprint-sidebar"]');
50+
await sidebar.getByRole('button', { name: 'Open Mappings tab' }).click();
51+
await page.waitForSelector('[data-testid="workspace-Mapping"]');
52+
}
53+
4454
/** Canonical desktop properties rail locator. */
4555
export function propertiesPanel(page: Page) {
4656
return page.locator('[data-testid="properties-panel"]');

packages/formspec-studio/tests/e2e/playwright/mapping-workspace.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { test, expect } from '@playwright/test';
2-
import { waitForApp, waitForAppWithExport, switchTab, importProject } from './helpers';
2+
import { waitForApp, waitForAppWithExport, importProject, openMappingWorkspace } from './helpers';
33

44
const SEED = {
55
definition: {
@@ -30,14 +30,14 @@ test.describe('Mapping Workspace', () => {
3030
test.beforeEach(async ({ page }) => {
3131
await waitForApp(page);
3232
await importProject(page, SEED);
33-
await switchTab(page, 'Mapping');
33+
await openMappingWorkspace(page);
3434
});
3535

3636
test('blueprint section shows direction, version and target format', async ({ page }) => {
3737
const workspace = page.locator('[data-testid="workspace-Mapping"]');
3838

3939
// Check direction
40-
await expect(workspace.locator('[data-testid="direction-picker"]')).toHaveText('forward');
40+
await expect(workspace.locator('[data-testid="direction-picker"]')).toContainText(/forward/i);
4141

4242
// Check version input is editable
4343
await expect(workspace.locator('[data-testid="mapping-version"]')).toHaveValue('1.2.3');
@@ -84,7 +84,7 @@ test.describe('Mapping Workspace', () => {
8484
// Need export access for this
8585
await waitForAppWithExport(page);
8686
await importProject(page, SEED);
87-
await switchTab(page, 'Mapping');
87+
await openMappingWorkspace(page);
8888

8989
const workspace = page.locator('[data-testid="workspace-Mapping"]');
9090

@@ -122,7 +122,7 @@ test.describe('Mapping Workspace', () => {
122122
test('direction picker updates mapping direction', async ({ page }) => {
123123
await waitForAppWithExport(page);
124124
await importProject(page, SEED);
125-
await switchTab(page, 'Mapping');
125+
await openMappingWorkspace(page);
126126

127127
const workspace = page.locator('[data-testid="workspace-Mapping"]');
128128

@@ -147,7 +147,7 @@ test.describe('Multi-mapping Selector', () => {
147147
test.beforeEach(async ({ page }) => {
148148
await waitForAppWithExport(page);
149149
await importProject(page, SEED);
150-
await switchTab(page, 'Mapping');
150+
await openMappingWorkspace(page);
151151
});
152152

153153
test('selector strip shows the default mapping tab', async ({ page }) => {

packages/formspec-studio/tests/e2e/playwright/smoke.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { test, expect } from '@playwright/test';
2-
import { waitForApp } from './helpers';
32

43
test.describe('Smoke — App Bootstrap', () => {
54
test('loads the app and shows the shell chrome', async ({ page }) => {
6-
await waitForApp(page);
5+
await page.goto('/?skipOnboarding=1&studioMode=chat');
6+
await page.waitForSelector('[data-testid="shell"]', { timeout: 10000 });
77

88
// App title in the header
99
await expect(page.locator('[data-testid="header"]')).toContainText('The Stack');
@@ -14,7 +14,7 @@ test.describe('Smoke — App Bootstrap', () => {
1414
await expect(page.locator(`[data-testid="mode-toggle-${mode}"]`)).toBeVisible();
1515
}
1616

17-
// Default mode is chat
17+
// Explicit chat boot (waitForApp defaults to edit for authoring E2E)
1818
await expect(page.locator('[data-testid="mode-toggle-chat"]')).toHaveClass(/bg-accent/);
1919
await expect(page.locator('[data-testid="chat-panel"]')).toBeVisible();
2020

packages/formspec-studio/tests/e2e/playwright/workspace-navigation.spec.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,19 @@ test.describe('Workspace Navigation — Tab Switching', () => {
2222
await expect(page.getByTestId('manage-section-data-sources')).toBeVisible();
2323
});
2424

25-
test('Design tab shows brand & style sections in the blueprint sidebar', async ({ page }) => {
25+
test('Design tab shows theme authoring in the blueprint sidebar and layout canvas', async ({ page }) => {
2626
await switchTab(page, 'Design');
27-
const workspace = page.locator('[data-testid="design-canvas-shell"]');
28-
await expect(workspace.getByRole('button', { name: /brand colors/i })).toBeVisible();
29-
await expect(workspace.getByRole('button', { name: /typography/i })).toBeVisible();
27+
const sidebar = page.locator('[data-testid="blueprint-sidebar"]');
28+
await expect(sidebar.getByRole('button', { name: 'Colors', exact: true })).toBeVisible();
29+
await expect(sidebar.getByRole('button', { name: 'Typography', exact: true })).toBeVisible();
30+
await expect(page.locator('[data-testid="design-canvas-shell"]')).toBeVisible();
31+
await expect(page.locator('[data-testid="workspace-Layout"]')).toBeVisible();
3032
});
3133

32-
test('Design mode renders design workspace', async ({ page }) => {
34+
test('Design mode shows layout workspace shell', async ({ page }) => {
3335
await switchTab(page, 'Design');
34-
const workspace = page.locator('[data-testid="design-canvas-shell"]');
35-
await expect(workspace.getByRole('heading', { name: 'Brand Colors' })).toBeVisible();
36-
await expect(workspace.getByRole('button', { name: /typography/i })).toBeVisible();
36+
await expect(page.locator('[data-testid="design-canvas-shell"]')).toBeVisible();
37+
await expect(page.locator('[data-testid="workspace-Layout"]')).toBeVisible();
3738
});
3839

3940
test('Mapping workspace is accessible via Advanced section', async ({ page }) => {

0 commit comments

Comments
 (0)