From c613c7da603f71baa8491152215972ca55b2d6eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Tulach?= Date: Tue, 2 Sep 2025 09:00:41 +0200 Subject: [PATCH 01/10] Whole test finished, not going through. --- app/ide-desktop/client/tests/electronTest.ts | 25 ++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/app/ide-desktop/client/tests/electronTest.ts b/app/ide-desktop/client/tests/electronTest.ts index bc2548be1395..9e05b9b24741 100644 --- a/app/ide-desktop/client/tests/electronTest.ts +++ b/app/ide-desktop/client/tests/electronTest.ts @@ -93,6 +93,11 @@ export async function loginAsTestUser(page: Page) { await page.getByRole('textbox', { name: 'password' }).fill(process.env.ENSO_TEST_USER_PASSWORD) await page.getByRole('button', { name: TEXT.login, exact: true }).click() + await expect( + page + .getByRole('group', { name: TEXT.licenseAgreementCheckbox }) + .getByText(TEXT.licenseAgreementCheckbox), + ).toBeVisible({ timeout: 60000 }) await page .getByRole('group', { name: TEXT.licenseAgreementCheckbox }) .getByText(TEXT.licenseAgreementCheckbox) @@ -157,3 +162,23 @@ export async function getNewestProject(page: Page): Promise { return numbered.reduce((a, b) => (a.num > b.num ? a : b)).locator } + +/** + * Clicking the eye button and making visualization visible + */ +export async function visualizeData(page: Page) { + const showViz = page.getByLabel('Show visualization (Space)') + await expect(showViz).toBeVisible({ timeout: 5000 }) + await showViz.click() +} + +/** + * Creating new component tied to the last created one + */ +export async function createNewComponent(page: Page) { + const moreButton = page.getByTestId('more-button').getByRole('button', { name: 'More' }).last() + await expect(moreButton).toBeVisible() + await moreButton.click() + + await page.keyboard.press('Enter') +} From 32bf7e990a805b705ff84a4c2932609011430ed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Tulach?= Date: Mon, 8 Sep 2025 10:48:48 +0200 Subject: [PATCH 02/10] Component creation unified --- .../client/tests/gettingStarted2.spec.ts | 220 ++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 app/ide-desktop/client/tests/gettingStarted2.spec.ts diff --git a/app/ide-desktop/client/tests/gettingStarted2.spec.ts b/app/ide-desktop/client/tests/gettingStarted2.spec.ts new file mode 100644 index 000000000000..13a6b90495ba --- /dev/null +++ b/app/ide-desktop/client/tests/gettingStarted2.spec.ts @@ -0,0 +1,220 @@ +/** @file A series of tests designed for testing 'Getting Started with Enso Analytics'. */ + +import { expect } from 'playwright/test' +import { + closeWelcome, + createNewComponent, + createNewProject, + fillText, + createComponentText, + loginAsTestUser, + test, + visualizeData, +} from './electronTest' + +// First excercise in Enso Analytics 101 +test('Exercise 2', async ({ page }) => { + await loginAsTestUser(page) + await closeWelcome(page) + + // ---------------- Objective 1 ---------------- + await test.step('Objective 1: Let’s read a single sheet in from Excel', async () => { + await createNewProject(page) + + // Close documentation + await page.getByRole('tab', { name: 'Documentation' }).click() + + const addComponent = page.getByLabel('Add Component (Enter)') + await expect(addComponent).toBeVisible() + await addComponent.click() + + const dataReadEntry = page.locator('.ComponentEntry', { hasText: /^Data\.read$/ }) + await expect(dataReadEntry).toBeVisible() + await dataReadEntry.click() + + // Fill in the url + await fillText(page, 'path‘‘', 'Samples/Data/sample_bank_data.xlsx') + + await Promise.race([ + page.getByLabel('Show visualization (Space)').waitFor({ state: 'visible', timeout: 5000 }), + page + .getByText(/file not found/i) + .waitFor({ state: 'visible', timeout: 5000 }) + .catch(() => null), + ]) + + await visualizeData(page) + + // Choosing the first sheet + const sheet1 = await page.getByText('Sheet1') + await expect(sheet1).toBeVisible() + await sheet1.dblclick() + + await visualizeData(page) + }) + + // ---------------- Objective 2 ---------------- + await test.step('Objective 1: Aggregating and ranking Account Type', async () => { + // Decrease zoom + await page.getByLabel('Decrease Zoom').click() + + // Adding aggregate component + await createComponentText(page, "readquery‘Sheet1’") + await page.locator('.ComponentEntry', { hasText: 'aggregate' }).click() + + // Choosing parameters + const groupBy = page.getByText('group_by', { exact: true }) + await expect(groupBy).toBeVisible() + await groupBy.click() + await page.getByRole('button', { name: 'product_name', exact: true }).click() + + // Close the dropdown + await page.getByText('aggregate').click() + + // Click the plus + await page.locator('div.WidgetTopLevelArgument', { hasText: 'columns' }) + .getByRole('list').filter({ hasText: /^$/ }) + .getByLabel('Add a new item') + .click(); + + // Visualize and assert the result + await visualizeData(page) + await expect(page.getByText("1026")).toBeVisible() + + // Adding sort component + await createComponentText(page, "aggregate") + await page.locator('.ComponentEntry', { hasText: 'sort' }).click() + + // Click the plus + await page.locator('div.WidgetTopLevelArgument', { hasText: 'columns' }).nth(1) + .getByRole('list').filter({ hasText: /^$/ }) + .getByLabel('Add a new item') + .click(); + + // Choosing parameters + await page.locator('div').filter({ hasText: /^‘product_name’$/ }).nth(4).click() + await page.getByRole('button', { name: 'Count', exact: true }).click() + + await page.getByText('direction', { exact: true }).click() + await page.getByRole('button', { name: '..Descending', exact: true }).click() + + await visualizeData(page) + }) + + // ---------------- Objective 3 ---------------- + await test.step('Objective 3: Create table of currencies by product names', async () => { + // Scroll into view + await page.mouse.wheel(0, -200); + + // Creating cross_tab component + const readComponent = page.getByText('read', { exact: true }).nth(2); + await readComponent.click({ button: 'right' }); + + await page.keyboard.press('Enter') + await page.locator('.ComponentEntry', { hasText: 'cross_tab' }).click() + + // Choosing the right parameters + const crossGroup = await page.getByText('group_by', { exact: true }).nth(1) + await expect(crossGroup).toBeVisible() + await crossGroup.click() + await page.getByRole('button', { name: 'product_name', exact: true }).click() + + await page.locator('.WidgetSelection.clickable').filter({ hasText: 'names' }).click() + const curRCode = page.getByRole('button', { name: 'currency_code' }).first() + await expect(curRCode).toBeVisible() + await curRCode.click() + + await page.getByText('values', { exact: true }).click() + await page.getByRole('button', { name: '..Count_Distinct', exact: true }).click() + + // Click the plus and select argument + const plus = page.locator('div.WidgetTopLevelArgument', { hasText: 'columns' }) + .getByRole('list').filter({ hasText: /^$/ }) + .getByLabel('Add a new item') + await expect(plus).toBeVisible() + await plus.click(); + await page.getByRole('button', { name: 'account_id', exact: true }).click() + + await visualizeData(page) + + // Move cross_tab a bit down for more clearance + const crossTab = page.getByText('cross_tab') + + await crossTab.hover(); + await page.mouse.down(); + const box = await crossTab.boundingBox(); + if (box) { + await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2 + 200); + } + await page.mouse.up(); + }) + + // ---------------- Objective 4 ---------------- + await test.step('Objective 4: Using the Zoom controls to show more or less of the workflow', async () => { + // Scroll into view + await page.mouse.wheel(0, -200); + + // Creating set component + const readComponent = page.getByText('read', { exact: true }).nth(2); + await readComponent.click({ button: 'right' }); + + await page.keyboard.press('Enter') + await page.locator('.ComponentEntry', { hasText: 'set' }).click() + + // Choosing right parameters + await page.getByText('value', { exact: true }).click() + await page.getByRole('button', { name: '', exact: true }).click() + + await page.locator('.widgetApplyPadding', { hasText: 'input' }).click() + await page.getByRole('button', { name: 'currency_code', exact: true }).click() + + await page.locator('.widgetApplyPadding', { hasText: 'operation' }).click() + await page.getByRole('button', { name: 'if', exact: true }).click() + + await page.locator('.widgetApplyPadding', { hasText: 'condition' }).click() + await page.getByRole('button', { name: '..Equal', exact: true }).click() + + await page.locator('.WidgetSelection.clickable').filter({ hasText: /^to$/ }).click() + await page.getByRole('button', { name: '' }).click() + + // Write in the textbox + const inputTo = page.locator('div').filter({ hasText: /^\.\.Equal“”$/ }).locator('label') + await expect(inputTo).toBeVisible() + await page.pause() + await inputTo.fill('G') + + await page.locator('.widgetApplyPadding', { hasText: 'true_value' }).click() + await page.getByRole('button', { name: '', exact: true }).click() + + // Write in the textbox + const inputTrue = page.locator('div').filter({ hasText: /^“”$/ }).nth(1) + await expect(inputTrue).toBeVisible() + await page.pause() + await inputTrue.fill('GBP') + + await page.locator('.widgetApplyPadding', { hasText: 'false_value' }).click() + const option = page.getByRole('button', { name: 'currency_code', exact: true }) + await option.hover(); + await expect(option).toBeVisible() + await option.click() + + // Write in the textbox + const inputAs = page.getByText('“”') + await expect(inputAs).toBeVisible() + await inputAs.fill('currency_code') + + // Draging the connectors + await page.getByText('set', { exact : true }).click() + + const dragLine = page.locator('g:nth-child(19) > g > g > .portClip > .clickable > .outputPortHoverArea') + const crossEnd = page.locator('div').filter({ hasText: /^cross_tabgroup_by\[\]names‘currency_code’$/ }).getByRole('img') + + // Moving the line + await dragLine.hover(); + await page.mouse.down(); + await crossEnd.hover() + await page.mouse.up(); + + await page.getByText('s', { exact : true }).click() + }) +}) From fe2fdfda6d06beb6a0730681a991bdcbd9aef6e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Tulach?= Date: Mon, 8 Sep 2025 11:12:32 +0200 Subject: [PATCH 03/10] Text filling unified. --- app/ide-desktop/client/tests/electronTest.ts | 20 +++++++++++++ .../client/tests/gettingStarted2.spec.ts | 30 +++++++++++-------- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/app/ide-desktop/client/tests/electronTest.ts b/app/ide-desktop/client/tests/electronTest.ts index 9e05b9b24741..8237af3aed99 100644 --- a/app/ide-desktop/client/tests/electronTest.ts +++ b/app/ide-desktop/client/tests/electronTest.ts @@ -182,3 +182,23 @@ export async function createNewComponent(page: Page) { await page.keyboard.press('Enter') } + +/** + * Creating new component from the name of its parent component + */ +export async function createComponentText(page: Page, parentComponent: string) { + await page.getByText(parentComponent, { exact: true }).click({ button: 'right' }) + await page.keyboard.press('Enter') +} + +/** + * Creating new component from the name of its parent component + */ +export async function fillText(page: Page, containerName: string, value: string) { + const cont = page.getByText(containerName, { exact:true }) + + // Ensuring the texbox is empty + const box = cont.getByTestId('widget-text-content').filter({hasText: /^(“”|‘’)?$/}) + await expect(box).toBeVisible() + await box.fill(value) +} diff --git a/app/ide-desktop/client/tests/gettingStarted2.spec.ts b/app/ide-desktop/client/tests/gettingStarted2.spec.ts index 13a6b90495ba..f9ab37dc814c 100644 --- a/app/ide-desktop/client/tests/gettingStarted2.spec.ts +++ b/app/ide-desktop/client/tests/gettingStarted2.spec.ts @@ -178,19 +178,23 @@ test('Exercise 2', async ({ page }) => { await page.getByRole('button', { name: '' }).click() // Write in the textbox - const inputTo = page.locator('div').filter({ hasText: /^\.\.Equal“”$/ }).locator('label') - await expect(inputTo).toBeVisible() - await page.pause() - await inputTo.fill('G') + // const inputTo = page.locator('div').filter({ hasText: /^\.\.Equal“”$/ }).locator('label') + // await expect(inputTo).toBeVisible() + // await page.pause() + // await inputTo.fill('G') + + await fillText(page, '..Equal“”', 'G') await page.locator('.widgetApplyPadding', { hasText: 'true_value' }).click() await page.getByRole('button', { name: '', exact: true }).click() - // Write in the textbox - const inputTrue = page.locator('div').filter({ hasText: /^“”$/ }).nth(1) - await expect(inputTrue).toBeVisible() - await page.pause() - await inputTrue.fill('GBP') + // // Write in the textbox + // const inputTrue = page.locator('div').filter({ hasText: /^“”$/ }).nth(1) + // await expect(inputTrue).toBeVisible() + // await page.pause() + // await inputTrue.fill('GBP') + + await fillText(page, '..If(..Equal“G”)“”', 'GBP') await page.locator('.widgetApplyPadding', { hasText: 'false_value' }).click() const option = page.getByRole('button', { name: 'currency_code', exact: true }) @@ -199,9 +203,11 @@ test('Exercise 2', async ({ page }) => { await option.click() // Write in the textbox - const inputAs = page.getByText('“”') - await expect(inputAs).toBeVisible() - await inputAs.fill('currency_code') + // const inputAs = page.getByText('“”') + // await expect(inputAs).toBeVisible() + await page.pause() + await fillText(page, 'as“”', 'currency_code') + // await inputAs.fill('currency_code') // Draging the connectors await page.getByText('set', { exact : true }).click() From 8c207a659e0116ea00f5501dbd24dabe20653c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Tulach?= Date: Mon, 8 Sep 2025 11:20:29 +0200 Subject: [PATCH 04/10] Avoiding the use of widgetApplyPadding. --- app/ide-desktop/client/tests/electronTest.ts | 4 +- .../client/tests/gettingStarted2.spec.ts | 116 +++++++++--------- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/app/ide-desktop/client/tests/electronTest.ts b/app/ide-desktop/client/tests/electronTest.ts index 8237af3aed99..74b786d88c49 100644 --- a/app/ide-desktop/client/tests/electronTest.ts +++ b/app/ide-desktop/client/tests/electronTest.ts @@ -195,10 +195,10 @@ export async function createComponentText(page: Page, parentComponent: string) { * Creating new component from the name of its parent component */ export async function fillText(page: Page, containerName: string, value: string) { - const cont = page.getByText(containerName, { exact:true }) + const cont = page.getByText(containerName, { exact: true }) // Ensuring the texbox is empty - const box = cont.getByTestId('widget-text-content').filter({hasText: /^(“”|‘’)?$/}) + const box = cont.getByTestId('widget-text-content').filter({ hasText: /^(“”|‘’)?$/ }) await expect(box).toBeVisible() await box.fill(value) } diff --git a/app/ide-desktop/client/tests/gettingStarted2.spec.ts b/app/ide-desktop/client/tests/gettingStarted2.spec.ts index f9ab37dc814c..0a49317c2aa8 100644 --- a/app/ide-desktop/client/tests/gettingStarted2.spec.ts +++ b/app/ide-desktop/client/tests/gettingStarted2.spec.ts @@ -3,10 +3,9 @@ import { expect } from 'playwright/test' import { closeWelcome, - createNewComponent, + createComponentText, createNewProject, fillText, - createComponentText, loginAsTestUser, test, visualizeData, @@ -59,7 +58,7 @@ test('Exercise 2', async ({ page }) => { await page.getByLabel('Decrease Zoom').click() // Adding aggregate component - await createComponentText(page, "readquery‘Sheet1’") + await createComponentText(page, 'readquery‘Sheet1’') await page.locator('.ComponentEntry', { hasText: 'aggregate' }).click() // Choosing parameters @@ -72,27 +71,36 @@ test('Exercise 2', async ({ page }) => { await page.getByText('aggregate').click() // Click the plus - await page.locator('div.WidgetTopLevelArgument', { hasText: 'columns' }) - .getByRole('list').filter({ hasText: /^$/ }) - .getByLabel('Add a new item') - .click(); + await page + .locator('div.WidgetTopLevelArgument', { hasText: 'columns' }) + .getByRole('list') + .filter({ hasText: /^$/ }) + .getByLabel('Add a new item') + .click() // Visualize and assert the result await visualizeData(page) - await expect(page.getByText("1026")).toBeVisible() + await expect(page.getByText('1026')).toBeVisible() // Adding sort component - await createComponentText(page, "aggregate") + await createComponentText(page, 'aggregate') await page.locator('.ComponentEntry', { hasText: 'sort' }).click() - + // Click the plus - await page.locator('div.WidgetTopLevelArgument', { hasText: 'columns' }).nth(1) - .getByRole('list').filter({ hasText: /^$/ }) - .getByLabel('Add a new item') - .click(); + await page + .locator('div.WidgetTopLevelArgument', { hasText: 'columns' }) + .nth(1) + .getByRole('list') + .filter({ hasText: /^$/ }) + .getByLabel('Add a new item') + .click() // Choosing parameters - await page.locator('div').filter({ hasText: /^‘product_name’$/ }).nth(4).click() + await page + .locator('div') + .filter({ hasText: /^‘product_name’$/ }) + .nth(4) + .click() await page.getByRole('button', { name: 'Count', exact: true }).click() await page.getByText('direction', { exact: true }).click() @@ -104,11 +112,11 @@ test('Exercise 2', async ({ page }) => { // ---------------- Objective 3 ---------------- await test.step('Objective 3: Create table of currencies by product names', async () => { // Scroll into view - await page.mouse.wheel(0, -200); + await page.mouse.wheel(0, -200) // Creating cross_tab component - const readComponent = page.getByText('read', { exact: true }).nth(2); - await readComponent.click({ button: 'right' }); + const readComponent = page.getByText('read', { exact: true }).nth(2) + await readComponent.click({ button: 'right' }) await page.keyboard.press('Enter') await page.locator('.ComponentEntry', { hasText: 'cross_tab' }).click() @@ -128,11 +136,13 @@ test('Exercise 2', async ({ page }) => { await page.getByRole('button', { name: '..Count_Distinct', exact: true }).click() // Click the plus and select argument - const plus = page.locator('div.WidgetTopLevelArgument', { hasText: 'columns' }) - .getByRole('list').filter({ hasText: /^$/ }) - .getByLabel('Add a new item') + const plus = page + .locator('div.WidgetTopLevelArgument', { hasText: 'columns' }) + .getByRole('list') + .filter({ hasText: /^$/ }) + .getByLabel('Add a new item') await expect(plus).toBeVisible() - await plus.click(); + await plus.click() await page.getByRole('button', { name: 'account_id', exact: true }).click() await visualizeData(page) @@ -140,23 +150,23 @@ test('Exercise 2', async ({ page }) => { // Move cross_tab a bit down for more clearance const crossTab = page.getByText('cross_tab') - await crossTab.hover(); - await page.mouse.down(); - const box = await crossTab.boundingBox(); + await crossTab.hover() + await page.mouse.down() + const box = await crossTab.boundingBox() if (box) { - await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2 + 200); + await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2 + 200) } - await page.mouse.up(); + await page.mouse.up() }) // ---------------- Objective 4 ---------------- await test.step('Objective 4: Using the Zoom controls to show more or less of the workflow', async () => { // Scroll into view - await page.mouse.wheel(0, -200); + await page.mouse.wheel(0, -200) // Creating set component - const readComponent = page.getByText('read', { exact: true }).nth(2); - await readComponent.click({ button: 'right' }); + const readComponent = page.getByText('read', { exact: true }).nth(2) + await readComponent.click({ button: 'right' }) await page.keyboard.press('Enter') await page.locator('.ComponentEntry', { hasText: 'set' }).click() @@ -165,62 +175,52 @@ test('Exercise 2', async ({ page }) => { await page.getByText('value', { exact: true }).click() await page.getByRole('button', { name: '', exact: true }).click() - await page.locator('.widgetApplyPadding', { hasText: 'input' }).click() + await page.getByText('input', { exact: true }).click() await page.getByRole('button', { name: 'currency_code', exact: true }).click() - await page.locator('.widgetApplyPadding', { hasText: 'operation' }).click() + await page.getByText('operation', { exact: true }).click() await page.getByRole('button', { name: 'if', exact: true }).click() - await page.locator('.widgetApplyPadding', { hasText: 'condition' }).click() + await page.getByText('condition', { exact: true }).click() await page.getByRole('button', { name: '..Equal', exact: true }).click() await page.locator('.WidgetSelection.clickable').filter({ hasText: /^to$/ }).click() await page.getByRole('button', { name: '' }).click() // Write in the textbox - // const inputTo = page.locator('div').filter({ hasText: /^\.\.Equal“”$/ }).locator('label') - // await expect(inputTo).toBeVisible() - // await page.pause() - // await inputTo.fill('G') - await fillText(page, '..Equal“”', 'G') - await page.locator('.widgetApplyPadding', { hasText: 'true_value' }).click() + await page.getByText('true_value', { exact: true }).click() await page.getByRole('button', { name: '', exact: true }).click() // // Write in the textbox - // const inputTrue = page.locator('div').filter({ hasText: /^“”$/ }).nth(1) - // await expect(inputTrue).toBeVisible() - // await page.pause() - // await inputTrue.fill('GBP') - await fillText(page, '..If(..Equal“G”)“”', 'GBP') - await page.locator('.widgetApplyPadding', { hasText: 'false_value' }).click() + await page.getByText('false_value', { exact: true }).click() const option = page.getByRole('button', { name: 'currency_code', exact: true }) - await option.hover(); - await expect(option).toBeVisible() + await option.hover() await option.click() // Write in the textbox - // const inputAs = page.getByText('“”') - // await expect(inputAs).toBeVisible() - await page.pause() await fillText(page, 'as“”', 'currency_code') - // await inputAs.fill('currency_code') // Draging the connectors - await page.getByText('set', { exact : true }).click() + await page.getByText('set', { exact: true }).click() - const dragLine = page.locator('g:nth-child(19) > g > g > .portClip > .clickable > .outputPortHoverArea') - const crossEnd = page.locator('div').filter({ hasText: /^cross_tabgroup_by\[\]names‘currency_code’$/ }).getByRole('img') + const dragLine = page.locator( + 'g:nth-child(19) > g > g > .portClip > .clickable > .outputPortHoverArea', + ) + const crossEnd = page + .locator('div') + .filter({ hasText: /^cross_tabgroup_by\[\]names‘currency_code’$/ }) + .getByRole('img') // Moving the line - await dragLine.hover(); - await page.mouse.down(); + await dragLine.hover() + await page.mouse.down() await crossEnd.hover() - await page.mouse.up(); + await page.mouse.up() - await page.getByText('s', { exact : true }).click() + await page.getByText('s', { exact: true }).click() }) }) From 092f4b1414a2013eef3bce95d871a6694b8927dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Tulach?= Date: Mon, 8 Sep 2025 11:58:54 +0200 Subject: [PATCH 05/10] Avoinding error when clicking 'group_by'. Node dragging removed. --- .../client/tests/gettingStarted2.spec.ts | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/app/ide-desktop/client/tests/gettingStarted2.spec.ts b/app/ide-desktop/client/tests/gettingStarted2.spec.ts index 0a49317c2aa8..dca35c2b14be 100644 --- a/app/ide-desktop/client/tests/gettingStarted2.spec.ts +++ b/app/ide-desktop/client/tests/gettingStarted2.spec.ts @@ -63,7 +63,9 @@ test('Exercise 2', async ({ page }) => { // Choosing parameters const groupBy = page.getByText('group_by', { exact: true }) - await expect(groupBy).toBeVisible() + + // Ensuring 'plus' is visible, to avoid clicking too early + await expect(page.locator('div').getByLabel('Add a new item').last()).toBeVisible() await groupBy.click() await page.getByRole('button', { name: 'product_name', exact: true }).click() @@ -123,7 +125,9 @@ test('Exercise 2', async ({ page }) => { // Choosing the right parameters const crossGroup = await page.getByText('group_by', { exact: true }).nth(1) - await expect(crossGroup).toBeVisible() + + // Ensuring 'plus' is visible, to avoid clicking too early + await expect(page.locator('div').getByLabel('Add a new item').last()).toBeVisible() await crossGroup.click() await page.getByRole('button', { name: 'product_name', exact: true }).click() @@ -203,24 +207,7 @@ test('Exercise 2', async ({ page }) => { // Write in the textbox await fillText(page, 'as“”', 'currency_code') - - // Draging the connectors - await page.getByText('set', { exact: true }).click() - - const dragLine = page.locator( - 'g:nth-child(19) > g > g > .portClip > .clickable > .outputPortHoverArea', - ) - const crossEnd = page - .locator('div') - .filter({ hasText: /^cross_tabgroup_by\[\]names‘currency_code’$/ }) - .getByRole('img') - - // Moving the line - await dragLine.hover() - await page.mouse.down() - await crossEnd.hover() - await page.mouse.up() - - await page.getByText('s', { exact: true }).click() + await visualizeData(page) + await expect(page.getByText('191')).toBeVisible() }) }) From 4f7c3efe278c14913fb1843acd8f59b4c1a5f4db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Tulach?= Date: Mon, 8 Sep 2025 12:02:29 +0200 Subject: [PATCH 06/10] Renaming objectives. --- app/ide-desktop/client/tests/gettingStarted2.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/ide-desktop/client/tests/gettingStarted2.spec.ts b/app/ide-desktop/client/tests/gettingStarted2.spec.ts index dca35c2b14be..91bb2bf8cedb 100644 --- a/app/ide-desktop/client/tests/gettingStarted2.spec.ts +++ b/app/ide-desktop/client/tests/gettingStarted2.spec.ts @@ -53,7 +53,7 @@ test('Exercise 2', async ({ page }) => { }) // ---------------- Objective 2 ---------------- - await test.step('Objective 1: Aggregating and ranking Account Type', async () => { + await test.step('Objective 2: Aggregating and ranking Account Type', async () => { // Decrease zoom await page.getByLabel('Decrease Zoom').click() @@ -164,7 +164,7 @@ test('Exercise 2', async ({ page }) => { }) // ---------------- Objective 4 ---------------- - await test.step('Objective 4: Using the Zoom controls to show more or less of the workflow', async () => { + await test.step('Objective 4: Fixing Dirty Data', async () => { // Scroll into view await page.mouse.wheel(0, -200) From aa815f88d2dcfe080153710a6588b107e69efa4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Tulach?= Date: Mon, 8 Sep 2025 14:37:13 +0200 Subject: [PATCH 07/10] Preventing flakyness due to dropdown not opening. --- app/ide-desktop/client/tests/gettingStarted2.spec.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/ide-desktop/client/tests/gettingStarted2.spec.ts b/app/ide-desktop/client/tests/gettingStarted2.spec.ts index 91bb2bf8cedb..9315392d8171 100644 --- a/app/ide-desktop/client/tests/gettingStarted2.spec.ts +++ b/app/ide-desktop/client/tests/gettingStarted2.spec.ts @@ -67,7 +67,15 @@ test('Exercise 2', async ({ page }) => { // Ensuring 'plus' is visible, to avoid clicking too early await expect(page.locator('div').getByLabel('Add a new item').last()).toBeVisible() await groupBy.click() - await page.getByRole('button', { name: 'product_name', exact: true }).click() + + const productBtn = page.getByRole('button', { name: 'product_name', exact: true }) + + // If dropdown menu doesn't open, click groupBy again to avoid test flakyness + if (!(await productBtn.isVisible())) { + await groupBy.click() + } + await productBtn.isVisible() + await productBtn.click() // Close the dropdown await page.getByText('aggregate').click() From 1a5943aaacb84bc4525d2821ee8d5f14531294db Mon Sep 17 00:00:00 2001 From: Adam Obuchowicz Date: Fri, 21 Nov 2025 15:35:40 +0100 Subject: [PATCH 08/10] Fixes in tests --- app/electron-client/tests/electronTest.ts | 14 ++++++- .../tests/gettingStarted.spec.ts | 37 ++++++++----------- .../GraphEditor/widgets/WidgetVector.vue | 1 + 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/app/electron-client/tests/electronTest.ts b/app/electron-client/tests/electronTest.ts index fdfb20482146..60daea91050c 100644 --- a/app/electron-client/tests/electronTest.ts +++ b/app/electron-client/tests/electronTest.ts @@ -199,11 +199,17 @@ export async function openComponentBrowser(page: Page, parentComponent: string) /** * Find textbox located in parent component and fill in text value */ -export async function fillWidgetText(page: Page, containerName: string, value: string) { +export async function fillWidgetText( + page: Page, + containerName: string, + value: string, + index?: number, +) { const cont = page.getByText(containerName) const box = cont.getByTestId('widget-text-content') - await box.fill(value) + if (index) return box.nth(index).fill(value) + else return box.fill(value) } /** @@ -224,3 +230,7 @@ export async function waitForDownload(pathToFile: string): Promise { } } } + +export function openSelectionWidget(page: Page, label: string) { + return page.locator('.WidgetSelection:has(.arrow)', { hasText: new RegExp(`^${label}$`) }).click() +} diff --git a/app/electron-client/tests/gettingStarted.spec.ts b/app/electron-client/tests/gettingStarted.spec.ts index 2d75ed4b948e..c0742c222019 100644 --- a/app/electron-client/tests/gettingStarted.spec.ts +++ b/app/electron-client/tests/gettingStarted.spec.ts @@ -8,6 +8,7 @@ import { fillWidgetText, loginAsTestUser, openComponentBrowser, + openSelectionWidget, test, visualizeData, waitForDownload, @@ -61,7 +62,7 @@ test('Exercise 1', async ({ page, projectsDir }) => { await page.locator('.ComponentEntry', { hasText: 'set' }).click() // Set parameters - await page.locator('.WidgetSelection.clickable').filter({ hasText: 'value' }).click() + await openSelectionWidget(page, 'value') await page.getByRole('button', { name: '', exact: true }).click() await page.getByText('input', { exact: true }).click() @@ -80,24 +81,19 @@ test('Exercise 1', async ({ page, projectsDir }) => { await openComponentBrowser(page, 'set') await page.locator('.ComponentEntry', { hasText: 'filter' }).click() - await page.locator('.WidgetSelection.clickable').filter({ hasText: 'column' }).click() + await openSelectionWidget(page, 'column') // Click with the assurance of component being in vision const option = page.getByRole('button', { name: 'currency_code_length', exact: true }) await option.scrollIntoViewIfNeeded() await option.click() - // Choosing the right filter. Wait until selection loads (which should show an arrow). - const filter = page.locator('.WidgetSelection:has(.arrow)', { hasText: 'filter' }) - - await filter.click() - - // Ensuring visibility + openSelectionWidget(page, 'filter') const notEqualBtn = page.getByRole('button', { name: '..Not_Equal', exact: true }) await notEqualBtn.waitFor({ state: 'visible', timeout: 10000 }) await notEqualBtn.click() - await page.locator('.WidgetSelection.clickable').filter({ hasText: /^to$/ }).click() + await openSelectionWidget(page, 'to') await page.getByRole('button', { name: '' }).click() // Set the actual filtered number value @@ -123,7 +119,7 @@ test('Exercise 1', async ({ page, projectsDir }) => { await page.keyboard.press('Enter') await page.locator('.ComponentEntry', { hasText: 'filter' }).click() - await page.locator('.WidgetSelection.clickable').filter({ hasText: 'column' }).click() + await openSelectionWidget(page, 'column') // Click with the assurance of component being in vision const option2 = page.getByRole('button', { name: 'product_name', exact: true }) @@ -132,12 +128,9 @@ test('Exercise 1', async ({ page, projectsDir }) => { await option2.click() // Choosing the right parameters - const filter2 = page.locator('.WidgetSelection:has(.arrow)', { hasText: 'filter' }) - await filter2.click() - + await openSelectionWidget(page, 'filter') await page.getByRole('button', { name: '..Equal', exact: true }).click() - - await page.locator('.WidgetSelection.clickable').filter({ hasText: /^to$/ }).click() + await openSelectionWidget(page, 'to') await page.getByRole('button', { name: '' }).click() // Set the filtered text value @@ -276,7 +269,7 @@ test('Exercise 2', async ({ page }) => { await crossGroup.click() await page.getByRole('button', { name: 'product_name', exact: true }).click() - await page.locator('.WidgetSelection.clickable').filter({ hasText: 'names' }).click() + await openSelectionWidget(page, 'names') const curRCode = page.getByRole('button', { name: 'currency_code' }).first() await expect(curRCode).toBeVisible() await curRCode.click() @@ -321,19 +314,19 @@ test('Exercise 2', async ({ page }) => { await page.locator('.ComponentEntry', { hasText: 'set' }).click() // Choosing right parameters - await page.getByText('value', { exact: true }).click() + await openSelectionWidget(page, 'value') await page.getByRole('button', { name: '', exact: true }).click() - await page.getByText('input', { exact: true }).click() + await openSelectionWidget(page, 'input') await page.getByRole('button', { name: 'currency_code', exact: true }).click() - await page.getByText('operation', { exact: true }).click() + await openSelectionWidget(page, 'operation') await page.getByRole('button', { name: 'if', exact: true }).click() - await page.getByText('condition', { exact: true }).click() + await openSelectionWidget(page, 'condition') await page.getByRole('button', { name: '..Equal', exact: true }).click() - await page.locator('.WidgetSelection.clickable').filter({ hasText: /^to$/ }).click() + await openSelectionWidget(page, 'to') await page.getByRole('button', { name: '' }).click() // Write in the textbox @@ -343,7 +336,7 @@ test('Exercise 2', async ({ page }) => { await page.getByRole('button', { name: '', exact: true }).click() // // Write in the textbox - await fillWidgetText(page, '..If(..Equal“G”)“”', 'GBP') + await fillWidgetText(page, '..If(..Equal“G”)“”', 'GBP', 1) await page.getByText('false_value', { exact: true }).click() const option = page.getByRole('button', { name: 'currency_code', exact: true }) diff --git a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetVector.vue b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetVector.vue index c351af8af133..e4cc13f7d0d9 100644 --- a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetVector.vue +++ b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetVector.vue @@ -150,6 +150,7 @@ export const widgetDefinition = defineWidget( score: (props) => props.input.dynamicConfig?.kind === 'Vector_Editor' ? Score.Perfect : props.input.dynamicConfig?.kind === 'SomeOfFunctionCalls' ? Score.Perfect + : props.input.dynamicConfig?.kind === 'Pending' ? Score.Mismatch : props.input.value instanceof Ast.Vector ? Score.Good : props.input.expectedType?.startsWith('Standard.Base.Data.Vector.Vector') ? Score.Good : Score.Mismatch, From 7070316d658308e22a85d5dba3847e21cf3d2b88 Mon Sep 17 00:00:00 2001 From: Adam Obuchowicz Date: Fri, 21 Nov 2025 17:18:02 +0100 Subject: [PATCH 09/10] Fixes --- app/electron-client/tests/electronTest.ts | 6 +- .../tests/gettingStarted.spec.ts | 79 +++++++------------ 2 files changed, 32 insertions(+), 53 deletions(-) diff --git a/app/electron-client/tests/electronTest.ts b/app/electron-client/tests/electronTest.ts index 60daea91050c..100dba6a57a3 100644 --- a/app/electron-client/tests/electronTest.ts +++ b/app/electron-client/tests/electronTest.ts @@ -231,6 +231,10 @@ export async function waitForDownload(pathToFile: string): Promise { } } -export function openSelectionWidget(page: Page, label: string) { +export function openDropdownInWidget(page: Page, label: string) { return page.locator('.WidgetSelection:has(.arrow)', { hasText: new RegExp(`^${label}$`) }).click() } + +export function addFirstElementToWidgetVector(locator: Locator) { + return locator.getByRole('list').filter({ hasText: /^$/ }).getByLabel('Add a new item').click() +} diff --git a/app/electron-client/tests/gettingStarted.spec.ts b/app/electron-client/tests/gettingStarted.spec.ts index c0742c222019..3f3e418844bc 100644 --- a/app/electron-client/tests/gettingStarted.spec.ts +++ b/app/electron-client/tests/gettingStarted.spec.ts @@ -3,12 +3,13 @@ import path from 'path' import { expect } from 'playwright/test' import { + addFirstElementToWidgetVector, closeWelcome, createNewProject, fillWidgetText, loginAsTestUser, openComponentBrowser, - openSelectionWidget, + openDropdownInWidget, test, visualizeData, waitForDownload, @@ -62,7 +63,7 @@ test('Exercise 1', async ({ page, projectsDir }) => { await page.locator('.ComponentEntry', { hasText: 'set' }).click() // Set parameters - await openSelectionWidget(page, 'value') + await openDropdownInWidget(page, 'value') await page.getByRole('button', { name: '', exact: true }).click() await page.getByText('input', { exact: true }).click() @@ -81,19 +82,19 @@ test('Exercise 1', async ({ page, projectsDir }) => { await openComponentBrowser(page, 'set') await page.locator('.ComponentEntry', { hasText: 'filter' }).click() - await openSelectionWidget(page, 'column') + await openDropdownInWidget(page, 'column') // Click with the assurance of component being in vision const option = page.getByRole('button', { name: 'currency_code_length', exact: true }) await option.scrollIntoViewIfNeeded() await option.click() - openSelectionWidget(page, 'filter') + openDropdownInWidget(page, 'filter') const notEqualBtn = page.getByRole('button', { name: '..Not_Equal', exact: true }) await notEqualBtn.waitFor({ state: 'visible', timeout: 10000 }) await notEqualBtn.click() - await openSelectionWidget(page, 'to') + await openDropdownInWidget(page, 'to') await page.getByRole('button', { name: '' }).click() // Set the actual filtered number value @@ -119,7 +120,7 @@ test('Exercise 1', async ({ page, projectsDir }) => { await page.keyboard.press('Enter') await page.locator('.ComponentEntry', { hasText: 'filter' }).click() - await openSelectionWidget(page, 'column') + await openDropdownInWidget(page, 'column') // Click with the assurance of component being in vision const option2 = page.getByRole('button', { name: 'product_name', exact: true }) @@ -128,9 +129,9 @@ test('Exercise 1', async ({ page, projectsDir }) => { await option2.click() // Choosing the right parameters - await openSelectionWidget(page, 'filter') + await openDropdownInWidget(page, 'filter') await page.getByRole('button', { name: '..Equal', exact: true }).click() - await openSelectionWidget(page, 'to') + await openDropdownInWidget(page, 'to') await page.getByRole('button', { name: '' }).click() // Set the filtered text value @@ -141,8 +142,8 @@ test('Exercise 1', async ({ page, projectsDir }) => { // Hardly testable }) -// Second excercise in Enso Analytics 101 -test('Exercise 2', async ({ page }) => { +// Second exercise in Enso Analytics 101 +test.only('Exercise 2', async ({ page }) => { await loginAsTestUser(page) await closeWelcome(page) @@ -199,24 +200,14 @@ test('Exercise 2', async ({ page }) => { await groupBy.click() const productBtn = page.getByRole('button', { name: 'product_name', exact: true }) - - // If dropdown menu doesn't open, click groupBy again to avoid test flakyness - if (!(await productBtn.isVisible())) { - await groupBy.click() - } - await productBtn.isVisible() await productBtn.click() // Close the dropdown await page.getByText('aggregate').click() - // Click the plus - await page - .locator('div.WidgetTopLevelArgument', { hasText: 'columns' }) - .getByRole('list') - .filter({ hasText: /^$/ }) - .getByLabel('Add a new item') - .click() + await addFirstElementToWidgetVector( + page.locator('div.WidgetTopLevelArgument', { hasText: 'columns' }), + ) // Visualize and assert the result await visualizeData(page) @@ -226,14 +217,9 @@ test('Exercise 2', async ({ page }) => { await openComponentBrowser(page, 'aggregate') await page.locator('.ComponentEntry', { hasText: 'sort' }).click() - // Click the plus - await page - .locator('div.WidgetTopLevelArgument', { hasText: 'columns' }) - .nth(1) - .getByRole('list') - .filter({ hasText: /^$/ }) - .getByLabel('Add a new item') - .click() + await addFirstElementToWidgetVector( + page.locator('div.WidgetTopLevelArgument', { hasText: 'columns' }), + ) // Choosing parameters await page @@ -269,7 +255,7 @@ test('Exercise 2', async ({ page }) => { await crossGroup.click() await page.getByRole('button', { name: 'product_name', exact: true }).click() - await openSelectionWidget(page, 'names') + await openDropdownInWidget(page, 'names') const curRCode = page.getByRole('button', { name: 'currency_code' }).first() await expect(curRCode).toBeVisible() await curRCode.click() @@ -278,27 +264,16 @@ test('Exercise 2', async ({ page }) => { await page.getByRole('button', { name: '..Count_Distinct', exact: true }).click() // Click the plus and select argument - const plus = page - .locator('div.WidgetTopLevelArgument', { hasText: 'columns' }) - .getByRole('list') - .filter({ hasText: /^$/ }) - .getByLabel('Add a new item') - await expect(plus).toBeVisible() - await plus.click() + await addFirstElementToWidgetVector( + page.locator('div.WidgetTopLevelArgument', { hasText: 'columns' }), + ) await page.getByRole('button', { name: 'account_id', exact: true }).click() await visualizeData(page) // Move cross_tab a bit down for more clearance const crossTab = page.getByText('cross_tab') - - await crossTab.hover() - await page.mouse.down() - const box = await crossTab.boundingBox() - if (box) { - await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2 + 200) - } - await page.mouse.up() + await crossTab.dragTo(crossTab, { targetPosition: { x: 0, y: 200 }, force: true }) }) // ---------------- Objective 4 ---------------- @@ -314,19 +289,19 @@ test('Exercise 2', async ({ page }) => { await page.locator('.ComponentEntry', { hasText: 'set' }).click() // Choosing right parameters - await openSelectionWidget(page, 'value') + await openDropdownInWidget(page, 'value') await page.getByRole('button', { name: '', exact: true }).click() - await openSelectionWidget(page, 'input') + await openDropdownInWidget(page, 'input') await page.getByRole('button', { name: 'currency_code', exact: true }).click() - await openSelectionWidget(page, 'operation') + await openDropdownInWidget(page, 'operation') await page.getByRole('button', { name: 'if', exact: true }).click() - await openSelectionWidget(page, 'condition') + await openDropdownInWidget(page, 'condition') await page.getByRole('button', { name: '..Equal', exact: true }).click() - await openSelectionWidget(page, 'to') + await openDropdownInWidget(page, 'to') await page.getByRole('button', { name: '' }).click() // Write in the textbox From 140d9cbcfb1ffb60bb1b37c2b5b16d5f4165eaad Mon Sep 17 00:00:00 2001 From: Adam Obuchowicz Date: Mon, 24 Nov 2025 10:28:01 +0100 Subject: [PATCH 10/10] Fix linter --- app/electron-client/tests/electronTest.ts | 4 +++- app/electron-client/tests/gettingStarted.spec.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/electron-client/tests/electronTest.ts b/app/electron-client/tests/electronTest.ts index 100dba6a57a3..2ffbb723d3be 100644 --- a/app/electron-client/tests/electronTest.ts +++ b/app/electron-client/tests/electronTest.ts @@ -231,10 +231,12 @@ export async function waitForDownload(pathToFile: string): Promise { } } +/** Open drop-down menu in WidgetSelection with given label. */ export function openDropdownInWidget(page: Page, label: string) { - return page.locator('.WidgetSelection:has(.arrow)', { hasText: new RegExp(`^${label}$`) }).click() + return page.locator('.WidgetSelection', { hasText: new RegExp(`^${label}$`) }).click() } +/** Find and click + button in an empty Vector Widget inside provided locator. */ export function addFirstElementToWidgetVector(locator: Locator) { return locator.getByRole('list').filter({ hasText: /^$/ }).getByLabel('Add a new item').click() } diff --git a/app/electron-client/tests/gettingStarted.spec.ts b/app/electron-client/tests/gettingStarted.spec.ts index 3f3e418844bc..8590b4bfc84f 100644 --- a/app/electron-client/tests/gettingStarted.spec.ts +++ b/app/electron-client/tests/gettingStarted.spec.ts @@ -143,7 +143,7 @@ test('Exercise 1', async ({ page, projectsDir }) => { }) // Second exercise in Enso Analytics 101 -test.only('Exercise 2', async ({ page }) => { +test('Exercise 2', async ({ page }) => { await loginAsTestUser(page) await closeWelcome(page)