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 13 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
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ jobs:
- run:
command: |
# See https://circleci.com/docs/2.0/env-vars/#setting-an-environment-variable-in-a-shell-command
TAG=$CIRCLE_SHA1 docker-compose -f ./docker/compose/docker-compose.yml up -d
TAG=$CIRCLE_SHA1 docker-compose -f ./docker/compose/docker-compose.test.yml up -d
apedroferreira marked this conversation as resolved.
Show resolved Hide resolved
yarn workspace @mui/toolpad-app waitForApp --url=http://172.17.0.1:3000/
docker run -it --rm \
--ipc=host \
Expand All @@ -179,7 +179,7 @@ jobs:
-e PLAYWRIGHT_TEST_BASE_URL=http://172.17.0.1:3000/ \
mcr.microsoft.com/playwright:v1.23.1-focal \
yarn test:integration --forbid-only
docker-compose -f ./docker/compose/docker-compose.yml down
docker-compose -f ./docker/compose/docker-compose.test.yml down

# push the image
- when:
Expand Down
30 changes: 30 additions & 0 deletions docker/compose/docker-compose.test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
version: '3.7'
Copy link
Member

@Janpot Janpot Sep 2, 2022

Choose a reason for hiding this comment

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

This file is not needed anymore I think

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah i didn't clean this up yet, just rebased


services:
toolpad:
image: muicom/toolpad:${TAG:-latest}
environment:
- TOOLPAD_DATABASE_URL=postgresql://toolpad-app:secretpw@postgres:5432/postgres?connect_timeout=10
- TOOLPAD_CREATE_WITH_DOM=true
- PORT=3000
- EXTERNAL_URL=http://localhost:3000/
ports:
- '3000:3000'

postgres:
image: postgres:14.2
restart: always
environment:
- POSTGRES_USER=toolpad-app
- POSTGRES_PASSWORD=secretpw
logging:
options:
max-size: 10m
max-file: '3'
expose:
- '5432'
volumes:
- postgres-data:/var/lib/postgresql/data

volumes:
postgres-data:
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 @@ -50,7 +50,7 @@ function ComponentPropsEditor<P>({ componentConfig, node }: ComponentPropsEditor
const hasLayoutControls = hasLayoutHorizontalControls || hasLayoutVerticalControls;

return (
<ComponentPropsEditorRoot>
<ComponentPropsEditorRoot data-testid="component-props-editor">
{hasLayoutControls ? (
<React.Fragment>
<Typography variant="subtitle2" sx={{ mt: 1 }}>
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
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, 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
86 changes: 86 additions & 0 deletions test/integration/editor/domInput.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"
}
}
}
157 changes: 157 additions & 0 deletions test/integration/editor/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { test, expect, Page, Locator } from '@playwright/test';
import createApp from '../../utils/createApp';
import clickCenter from '../../utils/clickCenter';
import generateId from '../../utils/generateId';
import { canvasFrame, componentCatalog, pageRoot } from '../../utils/locators';
import domInput from './domInput.json';

async function dragCenterToCenter(page: Page, sourceLocator: Locator, targetLocator: Locator) {
const sourceBoundingBox = await sourceLocator.boundingBox();
const targetBoundingBox = await targetLocator.boundingBox();

expect(sourceBoundingBox).toBeDefined();
expect(targetBoundingBox).toBeDefined();

await page.mouse.move(
sourceBoundingBox!.x + sourceBoundingBox!.width / 2,
sourceBoundingBox!.y + sourceBoundingBox!.height / 2,
{ steps: 5 },
);
await page.mouse.down();
await page.mouse.move(
targetBoundingBox!.x + targetBoundingBox!.width / 2,
targetBoundingBox!.y + targetBoundingBox!.height / 2,
{ steps: 5 },
);
await page.mouse.up();
}

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);
Copy link
Member

Choose a reason for hiding this comment

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

For the future we could think of an Object model for this. Been trying that out in #838
Doesn't have to be change for this PR

Copy link
Member Author

Choose a reason for hiding this comment

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

didn't know about it, yeah we could use to abstract the things we end up reusing a lot
i might still include it in this PR when we take a look at the firefox stuff again, shouldn't be too time consuming


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

await canvasPageRootLocator.waitFor();

await expect(canvasInputLocator).toHaveCount(0);

// Drag in a first component

await componentCatalogLocator.hover();

const textFieldDragSourceLocator = componentCatalogLocator.locator(
':has-text("TextField")[draggable]',
);
await dragCenterToCenter(page, textFieldDragSourceLocator, canvasPageRootLocator);

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

// Drag in a second component

await componentCatalogLocator.hover();
await dragCenterToCenter(page, textFieldDragSourceLocator, canvasPageRootLocator);

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(domInput));

const canvasFrameLocator = page.frameLocator(canvasFrame);

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

await canvasPageRootLocator.waitFor();

const firstTextFieldLocator = canvasInputLocator.first();
const secondTextFieldLocator = canvasInputLocator.nth(1);

await firstTextFieldLocator.focus();
await firstTextFieldLocator.type('textField1');

await secondTextFieldLocator.focus();
await secondTextFieldLocator.type('textField2');

await expect(firstTextFieldLocator).toHaveAttribute('value', 'textField1');
await expect(secondTextFieldLocator).toHaveAttribute('value', 'textField2');

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

// Move first element by dragging it to the right side of second element

await clickCenter(page, firstTextFieldLocator);

const canvasMoveElementHandleBoundingBox = await canvasMoveElementHandleLocator.boundingBox();
const secondTextFieldBoundingBox = await secondTextFieldLocator.boundingBox();

expect(canvasMoveElementHandleBoundingBox).toBeDefined();
expect(secondTextFieldBoundingBox).toBeDefined();

await page.mouse.move(
canvasMoveElementHandleBoundingBox!.x + canvasMoveElementHandleBoundingBox!.width / 2,
canvasMoveElementHandleBoundingBox!.y + canvasMoveElementHandleBoundingBox!.height / 2,
{ steps: 5 },
);
await page.mouse.down();
await page.mouse.move(
secondTextFieldBoundingBox!.x + secondTextFieldBoundingBox!.width,
secondTextFieldBoundingBox!.y + secondTextFieldBoundingBox!.height / 2,
{ steps: 5 },
);
await page.mouse.up();

await expect(firstTextFieldLocator).toHaveAttribute('value', 'textField2');
await expect(secondTextFieldLocator).toHaveAttribute('value', 'textField1');
});

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

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

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 firstTextFieldLocator = canvasInputLocator.first();

await clickCenter(page, firstTextFieldLocator);
await canvasRemoveElementButtonLocator.click();

await expect(canvasInputLocator).toHaveCount(1);

// Delete element by pressing key

await clickCenter(page, firstTextFieldLocator);
await page.keyboard.press('Backspace');

await expect(canvasInputLocator).toHaveCount(0);
});
Loading