From b777a939d9d2ea9101e7655a9eb741c233984828 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Wed, 23 Jul 2025 21:45:24 +0200 Subject: [PATCH 1/7] test: verify that background color changes apply to all selected shapes --- .../multi-select-bg-and-common-props.spec.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 e2e/props/multi-select-bg-and-common-props.spec.ts diff --git a/e2e/props/multi-select-bg-and-common-props.spec.ts b/e2e/props/multi-select-bg-and-common-props.spec.ts new file mode 100644 index 00000000..313cefcd --- /dev/null +++ b/e2e/props/multi-select-bg-and-common-props.spec.ts @@ -0,0 +1,52 @@ +import { test, expect } from '@playwright/test'; +import { dragAndDrop, getLocatorPosition, getTransformer } from '../helpers'; +import { getShapeBackgroundColor } from '../helpers'; + +test('when selecting a button and a rectangle, select both, change background color to red, both should update their bg to red', async ({ + page, +}) => { + await page.goto(''); + + // Drag & drop button in canvas + const button = page.getByAltText('Button', { exact: true }); + + const position = await getLocatorPosition(button); + const targetPosition = { x: position.x + 500, y: position.y }; + await dragAndDrop(page, position, targetPosition); + + // Drag & drop rectangle in canvas + await page.getByText('Basic Shapes').click(); + const rectangle = page.getByText('Rectangle', { exact: true }).locator('..'); + + const position2 = await getLocatorPosition(rectangle); + const targetPosition2 = { x: position2.x + 500, y: position2.y - 100 }; + await dragAndDrop(page, position2, targetPosition2); + + await page.mouse.click(800, 130); + + // Perform items selection + await dragAndDrop(page, { x: 260, y: 130 }, { x: 1000, y: 550 }); + + // Confirm both items are selected + const selectedItems = await getTransformer(page); + expect(selectedItems._nodes.length).toEqual(2); + + // Change background color to red + const bgSelector = page + .getByText('Background') + .locator('..') + .locator('button'); + await bgSelector.click(); + + const redColorBox = page.locator( + 'div[style*="background-color: rgb(221, 0, 0)"]' + ); + await redColorBox.click(); + + // Verify that both items have red background + const buttonBgColor = await getShapeBackgroundColor(page, 'button'); + const rectangleBgColor = await getShapeBackgroundColor(page, 'rectangle'); + + expect(buttonBgColor).toBe('#DD0000'); + expect(rectangleBgColor).toBe('#DD0000'); +}); From 3710ebbea15ef33c0b29776681fbf0ec4def9102 Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Wed, 23 Jul 2025 21:46:34 +0200 Subject: [PATCH 2/7] add getShapeBackgroundColor helper for property testing --- e2e/helpers/index.ts | 1 + e2e/helpers/properties.helpers.ts | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 e2e/helpers/properties.helpers.ts diff --git a/e2e/helpers/index.ts b/e2e/helpers/index.ts index dd15b414..f2e60f94 100644 --- a/e2e/helpers/index.ts +++ b/e2e/helpers/index.ts @@ -1,2 +1,3 @@ export * from './konva-testing.helpers'; export * from './position.helpers'; +export * from './properties.helpers'; diff --git a/e2e/helpers/properties.helpers.ts b/e2e/helpers/properties.helpers.ts new file mode 100644 index 00000000..447d3ed9 --- /dev/null +++ b/e2e/helpers/properties.helpers.ts @@ -0,0 +1,11 @@ +import { Page } from '@playwright/test'; +import { getByShapeType } from './konva-testing.helpers'; +import { Group } from 'konva/lib/Group'; + +export const getShapeBackgroundColor = async ( + page: Page, + shapeType: string +): Promise => { + const shape = (await getByShapeType(page, shapeType)) as Group; + return shape?.children?.[0]?.attrs?.fill; +}; From a1239f322aa0a1a00c0e310903a13adab0014bde Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Thu, 24 Jul 2025 10:27:42 +0200 Subject: [PATCH 3/7] feat: add helpers for adding components from different categories and selecting all canvas elements --- e2e/helpers/position.helpers.ts | 102 ++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/e2e/helpers/position.helpers.ts b/e2e/helpers/position.helpers.ts index c5964c90..40fe7bf5 100644 --- a/e2e/helpers/position.helpers.ts +++ b/e2e/helpers/position.helpers.ts @@ -5,6 +5,11 @@ export interface Position { y: number; } +export interface ComponentWithCategory { + name: string; + category?: string; +} + export const getLocatorPosition = async ( locator: Locator ): Promise => { @@ -73,10 +78,107 @@ export const addComponentsToCanvas = async ( } }; +export const addComponentsWithDifferentCategoriesToCanvas = async ( + page: Page, + components: ComponentWithCategory[], + displacementQty: number = 120 +) => { + // Handle empty array + if (components.length === 0) { + return; + } + + const stageCanvas = await page.locator('#konva-stage canvas').nth(1); + const canvasPosition = await stageCanvas.boundingBox(); + if (!canvasPosition) throw new Error('No canvas found'); + + let currentCategory: string | undefined = undefined; + + for await (const [index, componentConfig] of components.entries()) { + try { + // Change category only if it's different from current one + if ( + componentConfig.category && + componentConfig.category !== currentCategory + ) { + const categoryButton = page.getByText(componentConfig.category, { + exact: true, + }); + + // Check if category exists before clicking + await categoryButton.waitFor({ state: 'visible', timeout: 3000 }); + await categoryButton.click(); + + // Wait a bit for the category change to take effect + await page.waitForTimeout(500); + currentCategory = componentConfig.category; + } + + // Find component with better handling for duplicates + let component = page.getByAltText(componentConfig.name, { exact: true }); + + // Check if there are multiple elements with the same alt text + const componentCount = await component.count(); + + if (componentCount > 1) { + // Handle duplicates by selecting the first visible one in the current category context + console.warn( + `Multiple components found with name "${componentConfig.name}". Using first visible one.` + ); + component = component.first(); + } + + // Wait for component to be available + await component.waitFor({ state: 'visible', timeout: 5000 }); + await component.scrollIntoViewIfNeeded(); + const position = await getLocatorPosition(component); + + const targetPosition = ( + displacementQty: number, + multiplyFactor: number + ) => { + const positionDisplacement = displacementQty * (multiplyFactor + 1); + return { + x: canvasPosition.x + displacementQty + positionDisplacement, + y: canvasPosition.y + positionDisplacement, + }; + }; + + await dragAndDrop(page, position, targetPosition(displacementQty, index)); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + throw new Error( + `Failed to add component "${componentConfig.name}" from category "${componentConfig.category || 'default'}": ${errorMessage}` + ); + } + } +}; + export const getShapePosition = async (shape: Group): Promise => { return { x: shape?.attrs.x, y: shape?.attrs.y }; }; +export const selectAllComponentsInCanvas = async ( + page: Page, + selectionArea?: { start: Position; end: Position } +) => { + // Clear any existing selection first + await page.mouse.click(800, 130); + + // Small delay to ensure the click is processed + await page.waitForTimeout(100); + + const selectionStart = selectionArea?.start || { x: 260, y: 130 }; + const selectionEnd = selectionArea?.end || { x: 1000, y: 650 }; + + // Perform drag selection using the proven coordinates + await dragAndDrop(page, selectionStart, selectionEnd); + + // Small delay to ensure selection is processed + await page.waitForTimeout(200); +}; + export const moveSelected = ( page: Page, direction: string, From b08baba9a233cd3606ebb16147b018bc05e5605c Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Thu, 24 Jul 2025 10:28:44 +0200 Subject: [PATCH 4/7] refactor(test): use reusable helpers to add components and select all elements in canvas --- .../multi-select-bg-and-common-props.spec.ts | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/e2e/props/multi-select-bg-and-common-props.spec.ts b/e2e/props/multi-select-bg-and-common-props.spec.ts index 313cefcd..57d61c61 100644 --- a/e2e/props/multi-select-bg-and-common-props.spec.ts +++ b/e2e/props/multi-select-bg-and-common-props.spec.ts @@ -1,31 +1,26 @@ import { test, expect } from '@playwright/test'; -import { dragAndDrop, getLocatorPosition, getTransformer } from '../helpers'; -import { getShapeBackgroundColor } from '../helpers'; +import { + getTransformer, + ComponentWithCategory, + getShapeBackgroundColor, + addComponentsWithDifferentCategoriesToCanvas, + selectAllComponentsInCanvas, +} from '../helpers'; test('when selecting a button and a rectangle, select both, change background color to red, both should update their bg to red', async ({ page, }) => { await page.goto(''); - // Drag & drop button in canvas - const button = page.getByAltText('Button', { exact: true }); + // Add components to canvas + const components: ComponentWithCategory[] = [ + { name: 'Button' }, // Button is in default 'Components' category + { name: 'Rectangle', category: 'Basic Shapes' }, + ]; + await addComponentsWithDifferentCategoriesToCanvas(page, components); - const position = await getLocatorPosition(button); - const targetPosition = { x: position.x + 500, y: position.y }; - await dragAndDrop(page, position, targetPosition); - - // Drag & drop rectangle in canvas - await page.getByText('Basic Shapes').click(); - const rectangle = page.getByText('Rectangle', { exact: true }).locator('..'); - - const position2 = await getLocatorPosition(rectangle); - const targetPosition2 = { x: position2.x + 500, y: position2.y - 100 }; - await dragAndDrop(page, position2, targetPosition2); - - await page.mouse.click(800, 130); - - // Perform items selection - await dragAndDrop(page, { x: 260, y: 130 }, { x: 1000, y: 550 }); + // Select all components in canvas + await selectAllComponentsInCanvas(page); // Confirm both items are selected const selectedItems = await getTransformer(page); From c463cb783102f71ce9f7ccd1fa5f125fa5fa0bdd Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Thu, 24 Jul 2025 10:30:41 +0200 Subject: [PATCH 5/7] feat: add utility to check if component properties are visible in the UI --- e2e/helpers/properties.helpers.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/e2e/helpers/properties.helpers.ts b/e2e/helpers/properties.helpers.ts index 447d3ed9..4c750d6b 100644 --- a/e2e/helpers/properties.helpers.ts +++ b/e2e/helpers/properties.helpers.ts @@ -1,4 +1,4 @@ -import { Page } from '@playwright/test'; +import { Page, expect } from '@playwright/test'; import { getByShapeType } from './konva-testing.helpers'; import { Group } from 'konva/lib/Group'; @@ -9,3 +9,13 @@ export const getShapeBackgroundColor = async ( const shape = (await getByShapeType(page, shapeType)) as Group; return shape?.children?.[0]?.attrs?.fill; }; + +export const checkPropertiesExist = async ( + page: Page, + properties: string[] +) => { + for (const property of properties) { + const propLocator = page.getByText(property, { exact: true }); + await expect(propLocator).toBeVisible(); + } +}; From f0e60673b2c02044cb69084cb23d4a32219dcb7d Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Thu, 24 Jul 2025 10:31:30 +0200 Subject: [PATCH 6/7] test: verify common properties are shown when multiple components are selected --- .../multi-select-bg-and-common-props.spec.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/e2e/props/multi-select-bg-and-common-props.spec.ts b/e2e/props/multi-select-bg-and-common-props.spec.ts index 57d61c61..5dd6c766 100644 --- a/e2e/props/multi-select-bg-and-common-props.spec.ts +++ b/e2e/props/multi-select-bg-and-common-props.spec.ts @@ -5,6 +5,7 @@ import { getShapeBackgroundColor, addComponentsWithDifferentCategoriesToCanvas, selectAllComponentsInCanvas, + checkPropertiesExist, } from '../helpers'; test('when selecting a button and a rectangle, select both, change background color to red, both should update their bg to red', async ({ @@ -45,3 +46,34 @@ test('when selecting a button and a rectangle, select both, change background co expect(buttonBgColor).toBe('#DD0000'); expect(rectangleBgColor).toBe('#DD0000'); }); + +test('verify that in the props we can find the common props of both items', async ({ + page, +}) => { + await page.goto(''); + + // Add components to canvas + const components: ComponentWithCategory[] = [ + { name: 'Button' }, + { name: 'Rectangle', category: 'Basic Shapes' }, + ]; + await addComponentsWithDifferentCategoriesToCanvas(page, components); + + // Select all components in canvas + await selectAllComponentsInCanvas(page); + + // Confirm both items are selected + const selectedItems = await getTransformer(page); + expect(selectedItems._nodes.length).toEqual(2); + + const commonProps: string[] = [ + 'Layering', + 'Stroke', + 'Stroke style', + 'Background', + 'Border-radius', + ]; + + // Verify common properties are visible in the properties panel + await checkPropertiesExist(page, commonProps); +}); From 565061bda6104e6544366e7b7a40403081d2866d Mon Sep 17 00:00:00 2001 From: Guste Gaubaite <219.guste@gmail.com> Date: Thu, 24 Jul 2025 11:23:29 +0200 Subject: [PATCH 7/7] refactor: extract target position calculation into utility function --- e2e/helpers/position.helpers.ts | 48 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/e2e/helpers/position.helpers.ts b/e2e/helpers/position.helpers.ts index 40fe7bf5..7e519880 100644 --- a/e2e/helpers/position.helpers.ts +++ b/e2e/helpers/position.helpers.ts @@ -49,6 +49,18 @@ export const dragAndDrop = async ( await page.mouse.up(); }; +const getTargetPosition = ( + canvasPosition: { x: number; y: number }, + displacementQty: number, + multiplyFactor: number +): Position => { + const positionDisplacement = displacementQty * (multiplyFactor + 1); + return { + x: canvasPosition.x + displacementQty + positionDisplacement, + y: canvasPosition.y + positionDisplacement, + }; +}; + export const addComponentsToCanvas = async ( page: Page, components: string[], @@ -63,18 +75,12 @@ export const addComponentsToCanvas = async ( await component.scrollIntoViewIfNeeded(); const position = await getLocatorPosition(component); - const targetPosition = ( - displacementQty: number, - multiplyFactor: number - ) => { - const positionDisplacement = displacementQty * (multiplyFactor + 1); - return { - x: canvasPosition.x + displacementQty + positionDisplacement, - y: canvasPosition.y + positionDisplacement, - }; - }; - - await dragAndDrop(page, position, targetPosition(displacementQty, index)); + const targetPosition = getTargetPosition( + canvasPosition, + displacementQty, + index + ); + await dragAndDrop(page, position, targetPosition); } }; @@ -133,18 +139,12 @@ export const addComponentsWithDifferentCategoriesToCanvas = async ( await component.scrollIntoViewIfNeeded(); const position = await getLocatorPosition(component); - const targetPosition = ( - displacementQty: number, - multiplyFactor: number - ) => { - const positionDisplacement = displacementQty * (multiplyFactor + 1); - return { - x: canvasPosition.x + displacementQty + positionDisplacement, - y: canvasPosition.y + positionDisplacement, - }; - }; - - await dragAndDrop(page, position, targetPosition(displacementQty, index)); + const targetPosition = getTargetPosition( + canvasPosition, + displacementQty, + index + ); + await dragAndDrop(page, position, targetPosition); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error);