Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Editor integration tests #831

Merged
merged 31 commits into from
Sep 19, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a13efd4
Place element integration test
apedroferreira Aug 19, 2022
c924d93
Uniformize test ids
apedroferreira Aug 19, 2022
618227a
Delete elements test
apedroferreira Aug 19, 2022
f8e290d
Move element test (plus fixes and refactors)
apedroferreira Aug 22, 2022
17f5f69
Merge remote-tracking branch 'origin/master' into editor-integration-…
apedroferreira Aug 22, 2022
e54c0be
Remove test.only
apedroferreira Aug 22, 2022
6ea3149
Add TOOLPAD_CREATE_WITH_DOM environment variable in CI
apedroferreira Aug 23, 2022
fc7ced3
Make tests less specific to implementation
apedroferreira Aug 23, 2022
bc6d1d5
Fix command env variable
apedroferreira Aug 23, 2022
824e7dc
Merge remote-tracking branch 'origin/master' into editor-integration-…
apedroferreira Aug 23, 2022
a1f5d2b
Small refactor
apedroferreira Aug 23, 2022
d3127ae
Add prop controls integration test (#840)
apedroferreira Aug 25, 2022
029255f
Merge remote-tracking branch 'origin/master' into editor-integration-…
apedroferreira Aug 25, 2022
c46e557
Merge remote-tracking branch 'origin/master' into editor-integration-…
apedroferreira Aug 30, 2022
5022ab0
Merge remote-tracking branch 'origin/master' into editor-integration-…
apedroferreira Sep 1, 2022
b8e9d12
Use inputValue in tests
apedroferreira Sep 1, 2022
cdf0e9e
Refactor & code review fixes
apedroferreira Sep 2, 2022
7493a47
Remove unnecessary wait
apedroferreira Sep 2, 2022
83846bd
Fix element move test in Firefox
apedroferreira Sep 5, 2022
1e763da
Fix element add new component test in Firefox
apedroferreira Sep 5, 2022
3d90c12
Refactor and fix tests
apedroferreira Sep 6, 2022
135e1b5
Merge remote-tracking branch 'origin/master' into editor-integration-…
apedroferreira Sep 6, 2022
49b379d
Attempt to make CI pass
apedroferreira Sep 6, 2022
35ca944
Revert "Attempt to make CI pass"
apedroferreira Sep 6, 2022
d47a040
Another attempt
apedroferreira Sep 6, 2022
c055560
Latest
apedroferreira Sep 6, 2022
86eb6ee
Merge remote-tracking branch 'origin/master' into editor-integration-…
apedroferreira Sep 8, 2022
bce8fe0
Flaky CI tests, fix attempt
apedroferreira Sep 8, 2022
5271a22
More flaky CI fix attempts
apedroferreira Sep 8, 2022
23ba962
Merge remote-tracking branch 'origin/master' into editor-integration-…
apedroferreira Sep 19, 2022
fc21241
Rebase and add link to Firefox Playwright issue
apedroferreira Sep 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export default function ComponentCatalog({ className }: ComponentCatalogProps) {

return (
<ComponentCatalogRoot
data-testid="component-catalog"
className={className}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1326,6 +1326,7 @@ export default function RenderOverlay({ canvasHostRef }: RenderOverlayProps) {

return (
<OverlayRoot
data-testid="page-overlay"
ref={overlayRef}
className={clsx({
[overlayClasses.nodeDrag]: isDraggingOver,
Expand Down
2 changes: 1 addition & 1 deletion packages/toolpad-app/src/toolpad/ToolpadShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ function Header({ actions, navigation }: HeaderProps) {
>
<HomeIcon fontSize="medium" />
</IconButton>
<Typography data-test-id="brand" variant="h6" color="inherit" component="div">
<Typography data-testid="brand" variant="h6" color="inherit" component="div">
MUI Toolpad {process.env.TOOLPAD_TARGET}
</Typography>
{navigation}
Expand Down
86 changes: 86 additions & 0 deletions test/domTemplates/twoTextFields.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"root": "y3c19mb",
"nodes": {
"ft63r75": {
"id": "ft63r75",
"name": "textField1",
"type": "element",
"props": {
"label": {
"type": "const",
"value": "textField1"
}
},
"layout": {},
"parentId": "w173rcy",
"attributes": {
"component": {
"type": "const",
"value": "TextField"
}
},
"parentProp": "children",
"parentIndex": "a0"
},
"rl83rbf": {
"id": "rl83rbf",
"name": "textField2",
"type": "element",
"props": {
"label": {
"type": "const",
"value": "textField2"
}
},
"layout": {},
"parentId": "w173rcy",
"attributes": {
"component": {
"type": "const",
"value": "TextField"
}
},
"parentProp": "children",
"parentIndex": "a1"
},
"w173rcy": {
"id": "w173rcy",
"name": "pageRow",
"type": "element",
"props": {},
"layout": {},
"parentId": "y4d19z0",
"attributes": {
"component": {
"type": "const",
"value": "PageRow"
}
},
"parentProp": "children",
"parentIndex": "a0"
},
"y3c19mb": {
"id": "y3c19mb",
"name": "Application",
"type": "app",
"parentId": null,
"attributes": {},
"parentProp": null,
"parentIndex": null
},
"y4d19z0": {
"id": "y4d19z0",
"name": "page1",
"type": "page",
"parentId": "y3c19mb",
"attributes": {
"title": {
"type": "const",
"value": "Page 1"
}
},
"parentProp": "pages",
"parentIndex": "a0"
}
}
}
13 changes: 2 additions & 11 deletions test/integration/appRename.spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import { test, expect, Request, Page } from '@playwright/test';
import { test, expect } from '@playwright/test';
import createApp from '../utils/createApp';
import generateId from '../utils/generateId';
import * as locators from '../utils/locators';

async function createApp(page: Page, name: string) {
await page.locator('button:has-text("create new")').click();

await page.fill('[role="dialog"] label:has-text("name")', name);

await page.click('[role="dialog"] button:has-text("create")');

await page.waitForNavigation({ url: /\/_toolpad\/app\/[^/]+\/editor\/pages\/[^/]+/ });
}

test('app create/rename flow', async ({ page }) => {
const appName1 = `App ${generateId()}`;
const appName2 = `App ${generateId()}`;
Expand Down
154 changes: 154 additions & 0 deletions test/integration/editor.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { test, expect, Page, Locator } from '@playwright/test';
import createApp from '../utils/createApp';
import generateId from '../utils/generateId';
import { canvasFrame, componentCatalog, pageRoot, pageOverlay } from '../utils/locators';
import twoFieldsDomTemplate from '../domTemplates/twoTextFields.json';
apedroferreira marked this conversation as resolved.
Show resolved Hide resolved

async function selectComponent(page: Page, componentLocator: Locator) {
const canvasFrameLocator = page.frameLocator(canvasFrame);

const canvasPageOverlayLocator = canvasFrameLocator.locator(pageOverlay);
const canvasPageOverlayBoundingBox = await canvasPageOverlayLocator.boundingBox();

const componentBoundingBox = await componentLocator.boundingBox();

expect(canvasPageOverlayBoundingBox).toBeDefined();

await canvasPageOverlayLocator.click({
position: {
x:
componentBoundingBox!.x + componentBoundingBox!.width / 2 - canvasPageOverlayBoundingBox!.x,
y:
componentBoundingBox!.y +
componentBoundingBox!.height / 2 -
canvasPageOverlayBoundingBox!.y,
},
});
}

test('can place new components from catalog', async ({ page }) => {
const appId = generateId();

await page.goto('/');
await createApp(page, `App ${appId}`);

const canvasFrameLocator = page.frameLocator(canvasFrame);

const componentCatalogLocator = page.locator(componentCatalog);
const canvasPageRootLocator = canvasFrameLocator.locator(pageRoot);
const canvasPageOverlayLocator = canvasFrameLocator.locator(pageOverlay);
const canvasInputLocator = canvasFrameLocator.locator('input');

await canvasPageRootLocator.waitFor();

await expect(canvasInputLocator).toHaveCount(0);

await componentCatalogLocator.hover();
await componentCatalogLocator
.locator(':has-text("TextField")[draggable]')
.dragTo(canvasPageOverlayLocator);

await expect(canvasInputLocator).toHaveCount(1);
await expect(canvasInputLocator).toBeVisible();

// Drag in a second component

await componentCatalogLocator.hover();
await componentCatalogLocator
.locator(':has-text("TextField")[draggable]')
.dragTo(canvasPageOverlayLocator);

await expect(canvasInputLocator).toHaveCount(2);
});

test('can move elements in page', async ({ page }) => {
const appId = generateId();

await page.goto('/');
await createApp(page, `App ${appId}`, JSON.stringify(twoFieldsDomTemplate));

const canvasFrameLocator = page.frameLocator(canvasFrame);

const canvasPageRootLocator = canvasFrameLocator.locator(pageRoot);
const canvasPageOverlayLocator = canvasFrameLocator.locator(pageOverlay);
const canvasInputLocator = canvasFrameLocator.locator('input');
const canvasMoveElementHandleLocator = canvasFrameLocator.locator(
':has-text("TextField")[draggable]',
);

await canvasPageRootLocator.waitFor();

await canvasInputLocator.first().type('textField1');
await canvasInputLocator.nth(1).type('textField2');

await expect(canvasInputLocator.first()).toHaveAttribute('value', 'textField1');

// Move element by dragging

await expect(canvasMoveElementHandleLocator).not.toBeVisible();

const firstInputLocator = canvasInputLocator.first();
await selectComponent(page, firstInputLocator);

const canvasPageOverlayBoundingBox = await canvasPageOverlayLocator.boundingBox();

const secondTextFieldLocator = canvasInputLocator.nth(1);
const secondTextFieldBoundingBox = await secondTextFieldLocator.boundingBox();

expect(canvasPageOverlayBoundingBox).toBeDefined();
expect(secondTextFieldBoundingBox).toBeDefined();

await canvasMoveElementHandleLocator.dragTo(canvasPageOverlayLocator, {
apedroferreira marked this conversation as resolved.
Show resolved Hide resolved
targetPosition: {
x:
secondTextFieldBoundingBox!.x +
secondTextFieldBoundingBox!.width -
canvasPageOverlayBoundingBox!.x,
y:
secondTextFieldBoundingBox!.y +
secondTextFieldBoundingBox!.height / 2 -
canvasPageOverlayBoundingBox!.y,
},
});

await expect(canvasInputLocator.first()).toHaveAttribute('value', 'textField2');
});

test('can delete elements from page', async ({ page }) => {
const appId = generateId();

await page.goto('/');
await createApp(page, `App ${appId}`, JSON.stringify(twoFieldsDomTemplate));

const canvasFrameLocator = page.frameLocator(canvasFrame);

const canvasPageRootLocator = canvasFrameLocator.locator(pageRoot);
const canvasInputLocator = canvasFrameLocator.locator('input');
const canvasRemoveElementButtonLocator = canvasFrameLocator.locator(
'button[aria-label="Remove element"]',
);

await canvasPageRootLocator.waitFor();

await expect(canvasInputLocator).toHaveCount(2);

// Delete element by clicking

await expect(canvasRemoveElementButtonLocator).not.toBeVisible();

const firstInputLocator = canvasInputLocator.first();
await selectComponent(page, firstInputLocator);

await canvasRemoveElementButtonLocator.click();

await expect(canvasInputLocator).toHaveCount(1);

// Delete element by pressing key

const remainingTextFieldLocator = canvasInputLocator.first();
await selectComponent(page, remainingTextFieldLocator);

await page.keyboard.press('Backspace');

await expect(canvasInputLocator).toHaveCount(0);
});
2 changes: 1 addition & 1 deletion test/integration/smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ test('basic app creation flow', async ({ page }) => {
const appName = `App ${generateId()}`;

await page.goto('/');
const brand = page.locator('data-test-id=brand');
const brand = page.locator('data-testid=brand');
await expect(brand).toHaveText('MUI Toolpad CE');

await page.locator('button:has-text("create new")').click();
Expand Down
15 changes: 15 additions & 0 deletions test/utils/createApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Page } from '@playwright/test';

export default async function createApp(page: Page, name: string, dom?: string) {
await page.locator('button:has-text("create new")').click();

await page.fill('[role="dialog"] label:has-text("name")', name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could improve dialog selectors a bit: https://github.com/mui/mui-toolpad/pull/838/files#diff-e537b99227068da9665d973e6573a1cd6da8d36be778d2aba43b159890a43d99R30

But we can merge this as is and use the ToolpadHome model as an optimization later

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, i just took this function from the other tests where it was
we can use that model if we merge it first


if (dom) {
await page.fill('[role="dialog"] label:has-text("dom")', dom);
}

await page.click('[role="dialog"] button:has-text("create")');

await page.waitForNavigation({ url: /\/_toolpad\/app\/[^/]+\/editor\/pages\/[^/]+/ });
}
7 changes: 7 additions & 0 deletions test/utils/locators.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
export function toolpadHomeAppRow(appName: string): string {
apedroferreira marked this conversation as resolved.
Show resolved Hide resolved
return `[role="row"] >> has="input[value='${appName}']"`;
}

export const componentCatalog = 'data-testid=component-catalog';

export const canvasFrame = 'iframe[data-toolpad-canvas]';

export const pageRoot = 'data-testid=page-root';
export const pageOverlay = 'data-testid=page-overlay';