From d66ccb9a488da13b1a2d9270bb592671c61a41fa Mon Sep 17 00:00:00 2001 From: Thiago Dallacqua Date: Thu, 26 Sep 2024 15:43:14 -0300 Subject: [PATCH 1/5] start tests --- .../WorkspaceDetails/WorkspaceProjects.ts | 12 + webui/react/src/e2e/tests/projects.spec.ts | 316 +++++++++++------- 2 files changed, 199 insertions(+), 129 deletions(-) diff --git a/webui/react/src/e2e/models/pages/WorkspaceDetails/WorkspaceProjects.ts b/webui/react/src/e2e/models/pages/WorkspaceDetails/WorkspaceProjects.ts index c57ef4f43e2..c3a94968d77 100644 --- a/webui/react/src/e2e/models/pages/WorkspaceDetails/WorkspaceProjects.ts +++ b/webui/react/src/e2e/models/pages/WorkspaceDetails/WorkspaceProjects.ts @@ -10,6 +10,8 @@ import { ProjectDeleteModal } from 'e2e/models/components/ProjectDeleteModal'; import { ProjectMoveModal } from 'e2e/models/components/ProjectMoveModal'; import { HeadRow, InteractiveTable, Row } from 'e2e/models/components/Table/InteractiveTable'; +// import { ProjectActionDropdown } from 'e2e/models/components/ProjectActionDropdown'; + class ProjectHeadRow extends HeadRow { readonly name = new BaseComponent({ parent: this, @@ -21,8 +23,18 @@ class ProjectRow extends Row { readonly name = new BaseComponent({ parent: this, selector: '[data-testid="name"]', + // TODO: add all columns from WorkspaceProjects file here + // TODO: make sure to have the variable to open }); } +// readonly actionMenuContainer = new BaseComponent({ +// parent: this, +// selector: '[aria-label="Action menu"]', +// }); +// readonly actionMenu = new ProjectActionDropdown({ +// clickThisComponentToOpen: this.actionMenuContainer, +// root: this.root, // root of the dropdown +// }); /** * Represents the WorkspaceProjects page in src/pages/WorkspaceDetails/WorkspaceProjects.tsx diff --git a/webui/react/src/e2e/tests/projects.spec.ts b/webui/react/src/e2e/tests/projects.spec.ts index 5e8ded1e06a..bba974f8281 100644 --- a/webui/react/src/e2e/tests/projects.spec.ts +++ b/webui/react/src/e2e/tests/projects.spec.ts @@ -1,13 +1,14 @@ import _ from 'lodash'; import { expect, test } from 'e2e/fixtures/global-fixtures'; -import { ProjectDetails } from 'e2e/models/pages/ProjectDetails'; +// import { ProjectDetails } from 'e2e/models/pages/ProjectDetails'; import { WorkspaceDetails } from 'e2e/models/pages/WorkspaceDetails'; import { WorkspaceProjects } from 'e2e/models/pages/WorkspaceDetails/WorkspaceProjects'; -import { randId, safeName } from 'e2e/utils/naming'; +// import { randId, safeName } from 'e2e/utils/naming'; +import { safeName } from 'e2e/utils/naming'; import { V1Project } from 'services/api-ts-sdk'; -const getCurrentProjectNames = async (workspaceProjects: WorkspaceProjects) => { +const getCurrentProjectCardNames = async (workspaceProjects: WorkspaceProjects) => { await workspaceProjects.projectCards.pwLocator.nth(0).waitFor(); const cardTitles = await workspaceProjects.projectCards.title.pwLocator.all(); @@ -18,124 +19,124 @@ const getCurrentProjectNames = async (workspaceProjects: WorkspaceProjects) => { ); }; -test.describe('Project UI CRUD', () => { - const projectIds: number[] = []; - - test.beforeEach(async ({ authedPage, newWorkspace }) => { - const workspaceDetails = new WorkspaceDetails(authedPage); - await workspaceDetails.gotoWorkspace(newWorkspace.response.workspace.id); - await workspaceDetails.workspaceProjects.showArchived.switch.uncheck(); - }); - - test.afterAll(async ({ backgroundApiProject }) => { - for (const project of projectIds) { - await backgroundApiProject.deleteProject(project); - } - }); - - test('Create a Project', async ({ authedPage, newWorkspace }) => { - const projectName = safeName('test-project'); - const workspaceDetails = new WorkspaceDetails(authedPage); - const projectDetails = new ProjectDetails(authedPage); - - const workspaceProjects = workspaceDetails.workspaceProjects; - - await test.step('Create a Project', async () => { - await workspaceProjects.newProject.pwLocator.click(); - await workspaceProjects.createModal.projectName.pwLocator.fill(projectName); - await workspaceProjects.createModal.description.pwLocator.fill(randId()); - await workspaceProjects.createModal.footer.submit.pwLocator.click(); - projectIds.push(await projectDetails.getIdFromUrl()); - await workspaceDetails.gotoWorkspace(newWorkspace.response.workspace.id); - await workspaceProjects.cardByName(projectName).pwLocator.waitFor(); - }); - - await test.step('Delete a Project', async () => { - await workspaceDetails.gotoWorkspace(newWorkspace.response.workspace.id); - await workspaceDetails.projectsTab.pwLocator.click(); - const projectCard = workspaceProjects.cardByName(projectName); - await projectCard.actionMenu.open(); - await projectCard.actionMenu.delete.pwLocator.click(); - await workspaceProjects.deleteModal.nameConfirmation.pwLocator.fill(projectName); - await workspaceProjects.deleteModal.footer.submit.pwLocator.click(); - }); - }); - - test('Archive and Unarchive Project', async ({ - authedPage, - newWorkspace, - backgroundApiProject, - }) => { - const workspaceDetails = new WorkspaceDetails(authedPage); - - const newProject = await backgroundApiProject.createProject( - newWorkspace.response.workspace.id, - backgroundApiProject.new(), - ); - projectIds.push(newProject.project.id); - const projectCard = workspaceDetails.workspaceProjects.cardByName(newProject.project.name); - const archiveMenuItem = projectCard.actionMenu.archive; - - await test.step('Archive', async () => { - await authedPage.reload(); - await projectCard.actionMenu.open(); - await expect(archiveMenuItem.pwLocator).toHaveText('Archive'); - await archiveMenuItem.pwLocator.click(); - await projectCard.pwLocator.waitFor({ state: 'hidden' }); - }); - - await test.step('Unarchive', async () => { - await workspaceDetails.workspaceProjects.showArchived.switch.pwLocator.click(); - await projectCard.archivedBadge.pwLocator.waitFor(); - await projectCard.actionMenu.open(); - await expect(archiveMenuItem.pwLocator).toHaveText('Unarchive'); - await archiveMenuItem.pwLocator.click(); - await projectCard.archivedBadge.pwLocator.waitFor({ state: 'hidden' }); - }); - }); - - test('Move a Project', async ({ - authedPage, - newWorkspace, - backgroundApiWorkspace, - backgroundApiProject, - }) => { - const workspaceDetails = new WorkspaceDetails(authedPage); - - const destinationWorkspace = ( - await backgroundApiWorkspace.createWorkspace(backgroundApiWorkspace.new()) - ).workspace; - - const newProject = await backgroundApiProject.createProject( - newWorkspace.response.workspace.id, - backgroundApiProject.new(), - ); - projectIds.push(newProject.project.id); - - await authedPage.reload(); - - const workspaceProjects = workspaceDetails.workspaceProjects; - const projectCard = workspaceProjects.cardByName(newProject.project.name); - const moveMenuItem = projectCard.actionMenu.move; - - await projectCard.actionMenu.open(); - await moveMenuItem.pwLocator.click(); - await workspaceProjects.moveModal.destinationWorkspace.pwLocator.fill( - destinationWorkspace.name, - ); - await workspaceProjects.moveModal.destinationWorkspace.pwLocator.press('Enter'); - await workspaceProjects.moveModal.footer.submit.pwLocator.click(); - - await workspaceProjects.moveModal.pwLocator.waitFor({ state: 'hidden' }); - await projectCard.pwLocator.waitFor({ state: 'hidden' }); - - await workspaceDetails.gotoWorkspace(destinationWorkspace.id); - - await projectCard.pwLocator.waitFor(); - - await backgroundApiWorkspace.deleteWorkspace(destinationWorkspace.id); - }); -}); +// test.describe('Project UI CRUD', () => { +// const projectIds: number[] = []; + +// test.beforeEach(async ({ authedPage, newWorkspace }) => { +// const workspaceDetails = new WorkspaceDetails(authedPage); +// await workspaceDetails.gotoWorkspace(newWorkspace.response.workspace.id); +// await workspaceDetails.workspaceProjects.showArchived.switch.uncheck(); +// }); + +// test.afterAll(async ({ backgroundApiProject }) => { +// for (const project of projectIds) { +// await backgroundApiProject.deleteProject(project); +// } +// }); + +// test.skip('Create a Project', async ({ authedPage, newWorkspace }) => { +// const projectName = safeName('test-project'); +// const workspaceDetails = new WorkspaceDetails(authedPage); +// const projectDetails = new ProjectDetails(authedPage); + +// const workspaceProjects = workspaceDetails.workspaceProjects; + +// await test.step('Create a Project', async () => { +// await workspaceProjects.newProject.pwLocator.click(); +// await workspaceProjects.createModal.projectName.pwLocator.fill(projectName); +// await workspaceProjects.createModal.description.pwLocator.fill(randId()); +// await workspaceProjects.createModal.footer.submit.pwLocator.click(); +// projectIds.push(await projectDetails.getIdFromUrl()); +// await workspaceDetails.gotoWorkspace(newWorkspace.response.workspace.id); +// await workspaceProjects.cardByName(projectName).pwLocator.waitFor(); +// }); + +// await test.step('Delete a Project', async () => { +// await workspaceDetails.gotoWorkspace(newWorkspace.response.workspace.id); +// await workspaceDetails.projectsTab.pwLocator.click(); +// const projectCard = workspaceProjects.cardByName(projectName); +// await projectCard.actionMenu.open(); +// await projectCard.actionMenu.delete.pwLocator.click(); +// await workspaceProjects.deleteModal.nameConfirmation.pwLocator.fill(projectName); +// await workspaceProjects.deleteModal.footer.submit.pwLocator.click(); +// }); +// }); + +// test.skip('Archive and Unarchive Project', async ({ +// authedPage, +// newWorkspace, +// backgroundApiProject, +// }) => { +// const workspaceDetails = new WorkspaceDetails(authedPage); + +// const newProject = await backgroundApiProject.createProject( +// newWorkspace.response.workspace.id, +// backgroundApiProject.new(), +// ); +// projectIds.push(newProject.project.id); +// const projectCard = workspaceDetails.workspaceProjects.cardByName(newProject.project.name); +// const archiveMenuItem = projectCard.actionMenu.archive; + +// await test.step('Archive', async () => { +// await authedPage.reload(); +// await projectCard.actionMenu.open(); +// await expect(archiveMenuItem.pwLocator).toHaveText('Archive'); +// await archiveMenuItem.pwLocator.click(); +// await projectCard.pwLocator.waitFor({ state: 'hidden' }); +// }); + +// await test.step('Unarchive', async () => { +// await workspaceDetails.workspaceProjects.showArchived.switch.pwLocator.click(); +// await projectCard.archivedBadge.pwLocator.waitFor(); +// await projectCard.actionMenu.open(); +// await expect(archiveMenuItem.pwLocator).toHaveText('Unarchive'); +// await archiveMenuItem.pwLocator.click(); +// await projectCard.archivedBadge.pwLocator.waitFor({ state: 'hidden' }); +// }); +// }); + +// test.skip('Move a Project', async ({ +// authedPage, +// newWorkspace, +// backgroundApiWorkspace, +// backgroundApiProject, +// }) => { +// const workspaceDetails = new WorkspaceDetails(authedPage); + +// const destinationWorkspace = ( +// await backgroundApiWorkspace.createWorkspace(backgroundApiWorkspace.new()) +// ).workspace; + +// const newProject = await backgroundApiProject.createProject( +// newWorkspace.response.workspace.id, +// backgroundApiProject.new(), +// ); +// projectIds.push(newProject.project.id); + +// await authedPage.reload(); + +// const workspaceProjects = workspaceDetails.workspaceProjects; +// const projectCard = workspaceProjects.cardByName(newProject.project.name); +// const moveMenuItem = projectCard.actionMenu.move; + +// await projectCard.actionMenu.open(); +// await moveMenuItem.pwLocator.click(); +// await workspaceProjects.moveModal.destinationWorkspace.pwLocator.fill( +// destinationWorkspace.name, +// ); +// await workspaceProjects.moveModal.destinationWorkspace.pwLocator.press('Enter'); +// await workspaceProjects.moveModal.footer.submit.pwLocator.click(); + +// await workspaceProjects.moveModal.pwLocator.waitFor({ state: 'hidden' }); +// await projectCard.pwLocator.waitFor({ state: 'hidden' }); + +// await workspaceDetails.gotoWorkspace(destinationWorkspace.id); + +// await projectCard.pwLocator.waitFor(); + +// await backgroundApiWorkspace.deleteWorkspace(destinationWorkspace.id); +// }); +// }); test.describe('Project List', () => { const projects: V1Project[] = []; @@ -174,11 +175,11 @@ test.describe('Project List', () => { } }); - test('Sort', async ({ authedPage }) => { + test.skip('Sort', async ({ authedPage }) => { const workspaceDetails = new WorkspaceDetails(authedPage); const workspaceProjects = workspaceDetails.workspaceProjects; - const namesAfterNewest = await getCurrentProjectNames(workspaceProjects); + const namesAfterNewest = await getCurrentProjectCardNames(workspaceProjects); const idSortedProjectNames = _.orderBy(projects, 'id', 'desc').map((p) => p.name); expect(idSortedProjectNames).toEqual( namesAfterNewest.filter((n) => { @@ -188,7 +189,7 @@ test.describe('Project List', () => { await workspaceProjects.sortSelect.selectMenuOption('Alphabetical'); - const namesAfterAlphabetical = await getCurrentProjectNames(workspaceProjects); + const namesAfterAlphabetical = await getCurrentProjectCardNames(workspaceProjects); const nameSortedProjectNames = _.orderBy(projects, 'name', 'asc').map((p) => p.name); expect(nameSortedProjectNames).toEqual( namesAfterAlphabetical.filter((n) => { @@ -197,7 +198,7 @@ test.describe('Project List', () => { ); }); - test('Filter', async ({ authedPage, apiProject, newWorkspace }) => { + test.skip('Filter', async ({ authedPage, apiProject, newWorkspace }) => { const workspaceDetails = new WorkspaceDetails(authedPage); const workspaceProjects = workspaceDetails.workspaceProjects; @@ -213,24 +214,24 @@ test.describe('Project List', () => { await authedPage.reload(); - const namesAfterAll = await getCurrentProjectNames(workspaceProjects); + const namesAfterAll = await getCurrentProjectCardNames(workspaceProjects); expect(namesAfterAll).toContain(otherUserProjectName); expect(namesAfterAll).toContain(currentUserProjectName); await workspaceProjects.whoseSelect.selectMenuOption("Others' Projects"); - const namesAfterOthers = await getCurrentProjectNames(workspaceProjects); + const namesAfterOthers = await getCurrentProjectCardNames(workspaceProjects); expect(namesAfterOthers).toContain(otherUserProjectName); expect(namesAfterOthers).not.toContain(currentUserProjectName); await workspaceProjects.whoseSelect.selectMenuOption('My Projects'); - const namesAfterMy = await getCurrentProjectNames(workspaceProjects); + const namesAfterMy = await getCurrentProjectCardNames(workspaceProjects); expect(namesAfterMy).toContain(currentUserProjectName); expect(namesAfterMy).not.toContain(otherUserProjectName); await apiProject.deleteProject(currentUserProject.id); }); - test('View Toggle', async ({ authedPage }) => { + test.skip('View Toggle', async ({ authedPage }) => { const workspaceDetails = new WorkspaceDetails(authedPage); const workspaceProjects = workspaceDetails.workspaceProjects; @@ -245,4 +246,61 @@ test.describe('Project List', () => { await firstRow.pwLocator.waitFor({ state: 'hidden' }); await firstCard.pwLocator.waitFor(); }); + + test('Move a Project', async ({ + authedPage, + newWorkspace, + backgroundApiWorkspace, + backgroundApiProject, + }) => { + const workspaceDetails = new WorkspaceDetails(authedPage); + const workspaceProjects = workspaceDetails.workspaceProjects; + + await workspaceProjects.gridListRadioGroup.list.pwLocator.click(); + + const projectsList = workspaceDetails.workspaceProjects.table.table; + + const newProject = await backgroundApiProject.createProject( + newWorkspace.response.workspace.id, + backgroundApiProject.new(), + ); + + await authedPage.reload(); + + const row = await projectsList.rowByAttributeGenerator('name')(newProject.project.name); + + await expect(row).toBeDefined(); + + const destinationWorkspace = ( + await backgroundApiWorkspace.createWorkspace(backgroundApiWorkspace.new()) + ).workspace; + // const moveMenuItem = row.actionMenu.move; + + // await row.actionMenu.open(); + // await moveMenuItem.pwLocator.click(); + await workspaceProjects.moveModal.destinationWorkspace.pwLocator.fill( + destinationWorkspace.name, + ); + await workspaceProjects.moveModal.destinationWorkspace.pwLocator.press('Enter'); + await workspaceProjects.moveModal.footer.submit.pwLocator.click(); + + await workspaceProjects.moveModal.pwLocator.waitFor({ state: 'hidden' }); + await row.pwLocator.waitFor({ state: 'hidden' }); + + const projectsWithoutNewItem = + await workspaceProjects.table.table.rows.pwLocator.allTextContents(); + + await expect(projectsWithoutNewItem.includes(newProject.project.name)).toBeFalsy(); + + await workspaceDetails.gotoWorkspace(destinationWorkspace.id); + + await row.pwLocator.waitFor(); + + // const projectsWithNewItem = await getCurrentProjectRowNames(workspaceProjects); + + // expect(initialProjects.length).toStrictEqual(newStateOfProjects.length); + // expect(newStateOfProjects.includes(newProject.project.name)).toBeFalsy(); + + // await backgroundApiWorkspace.deleteWorkspace(destinationWorkspace.id); + }); }); From 792c28264177a75b70edae1f7e89f2bd0f971f00 Mon Sep 17 00:00:00 2001 From: Thiago Dallacqua Date: Thu, 3 Oct 2024 11:04:01 -0300 Subject: [PATCH 2/5] add to page model --- .../WorkspaceDetails/WorkspaceProjects.ts | 68 +++++++++++++++---- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/webui/react/src/e2e/models/pages/WorkspaceDetails/WorkspaceProjects.ts b/webui/react/src/e2e/models/pages/WorkspaceDetails/WorkspaceProjects.ts index c3a94968d77..8399aea13aa 100644 --- a/webui/react/src/e2e/models/pages/WorkspaceDetails/WorkspaceProjects.ts +++ b/webui/react/src/e2e/models/pages/WorkspaceDetails/WorkspaceProjects.ts @@ -10,31 +10,75 @@ import { ProjectDeleteModal } from 'e2e/models/components/ProjectDeleteModal'; import { ProjectMoveModal } from 'e2e/models/components/ProjectMoveModal'; import { HeadRow, InteractiveTable, Row } from 'e2e/models/components/Table/InteractiveTable'; -// import { ProjectActionDropdown } from 'e2e/models/components/ProjectActionDropdown'; - class ProjectHeadRow extends HeadRow { readonly name = new BaseComponent({ parent: this, selector: '[data-testid="Name"]', }); + readonly description = new BaseComponent({ + parent: this, + selector: '[data-testid="Description"]', + }); + readonly numExperiments = new BaseComponent({ + parent: this, + selector: '[data-testid="NumExperiments"]', + }); + readonly lastUpdated = new BaseComponent({ + parent: this, + selector: '[data-testid="LastUpdated"]', + }); + readonly userId = new BaseComponent({ + parent: this, + selector: '[data-testid="UserId"]', + }); + readonly archived = new BaseComponent({ + parent: this, + selector: '[data-testid="Archived"]', + }); + readonly state = new BaseComponent({ + parent: this, + selector: '[data-testid="State"]', + }); + readonly action = new BaseComponent({ + parent: this, + selector: '[data-testid="Action"]', + }); } class ProjectRow extends Row { readonly name = new BaseComponent({ parent: this, selector: '[data-testid="name"]', - // TODO: add all columns from WorkspaceProjects file here - // TODO: make sure to have the variable to open + }); + readonly description = new BaseComponent({ + parent: this, + selector: '[data-testid="Description"]', + }); + readonly numExperiments = new BaseComponent({ + parent: this, + selector: '[data-testid="NumExperiments"]', + }); + readonly lastUpdated = new BaseComponent({ + parent: this, + selector: '[data-testid="LastUpdated"]', + }); + readonly userId = new BaseComponent({ + parent: this, + selector: '[data-testid="UserId"]', + }); + readonly archived = new BaseComponent({ + parent: this, + selector: '[data-testid="Archived"]', + }); + readonly state = new BaseComponent({ + parent: this, + selector: '[data-testid="State"]', + }); + readonly action = new BaseComponent({ + parent: this, + selector: '[data-testid="Action"]', }); } -// readonly actionMenuContainer = new BaseComponent({ -// parent: this, -// selector: '[aria-label="Action menu"]', -// }); -// readonly actionMenu = new ProjectActionDropdown({ -// clickThisComponentToOpen: this.actionMenuContainer, -// root: this.root, // root of the dropdown -// }); /** * Represents the WorkspaceProjects page in src/pages/WorkspaceDetails/WorkspaceProjects.tsx From 1e85ae6db604a62d8b2f7205bbd15ef79fde86d7 Mon Sep 17 00:00:00 2001 From: Thiago Dallacqua Date: Wed, 9 Oct 2024 13:18:13 -0300 Subject: [PATCH 3/5] Merged origin/main into thiago/ET-757 --- webui/react/package-lock.json | 8 +- webui/react/package.json | 2 +- webui/react/src/e2e/README.md | 6 + webui/react/src/e2e/fixtures/dev.fixture.ts | 26 ---- .../e2e/models/components/F_ExperimentList.ts | 2 +- .../e2e/models/components/MultiSortMenu.ts | 3 +- .../src/e2e/tests/experimentList.spec.ts | 120 ++++++++++++++++++ webui/react/src/e2e/utils/debug.ts | 27 ++++ 8 files changed, 161 insertions(+), 33 deletions(-) diff --git a/webui/react/package-lock.json b/webui/react/package-lock.json index b8921917f7d..864f1b34062 100644 --- a/webui/react/package-lock.json +++ b/webui/react/package-lock.json @@ -98,7 +98,7 @@ "jsdom": "^16.7.0", "morgan": "^1.10.0", "npm-force-resolutions": "0.0.10", - "playwright-page-model-base": "npm:@hpe.com/playwright-page-model-base@^0.2.5", + "playwright-page-model-base": "npm:@hpe.com/playwright-page-model-base@^0.3.1", "prettier": "^3.2.5", "request": "^2.88.2", "resize-observer-polyfill": "^1.5.1", @@ -10926,9 +10926,9 @@ }, "node_modules/playwright-page-model-base": { "name": "@hpe.com/playwright-page-model-base", - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@hpe.com/playwright-page-model-base/-/playwright-page-model-base-0.2.5.tgz", - "integrity": "sha512-cMzFTz1rln0NjjZ3geQJSWsYmAgmH02TTar9gHmSFYyhJLKPww5zJuseLn2O5tg//v6gKLppdfpbwG0Aqbru7Q==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@hpe.com/playwright-page-model-base/-/playwright-page-model-base-0.3.1.tgz", + "integrity": "sha512-wdDPyxZXL+lrZ5pKtUaBlP7S0+IimaIGk1Xewwy/PAZzpMMxmE1fdC1ZVDCpIBI24vJA8P9KiV+bQ06VYFV5wg==", "dev": true }, "node_modules/possible-typed-array-names": { diff --git a/webui/react/package.json b/webui/react/package.json index 888e6dd2800..ffc320d1544 100644 --- a/webui/react/package.json +++ b/webui/react/package.json @@ -134,7 +134,7 @@ "jsdom": "^16.7.0", "morgan": "^1.10.0", "npm-force-resolutions": "0.0.10", - "playwright-page-model-base": "npm:@hpe.com/playwright-page-model-base@^0.2.5", + "playwright-page-model-base": "npm:@hpe.com/playwright-page-model-base@^0.3.1", "prettier": "^3.2.5", "request": "^2.88.2", "resize-observer-polyfill": "^1.5.1", diff --git a/webui/react/src/e2e/README.md b/webui/react/src/e2e/README.md index 0dd240107a9..d30113491f7 100644 --- a/webui/react/src/e2e/README.md +++ b/webui/react/src/e2e/README.md @@ -10,6 +10,8 @@ Determined AI uses [Playwright 🎭](https://playwright.dev/). Everything you need before running tests +**Before you follow the steps bellow, make sure that your VSCode is up-to-date, or you might face issues** + ### `.env` Create `.env` file in `webui/react` like `webui/react/.env` and env variables. (`PW_` prefix stands for Playwright) @@ -55,6 +57,10 @@ CI is setup as `test-e2e-react` in `.circleci/config.yml`. Run individual tests on ci with `e2e-react` `-g [your test]`. +you can also run using the following script (which will run your test, but with the UI open) + +`npx playwright test -g "[your_test]" --ui` + ![trigger pipeline](./docs/images/trigger-pipeline.png) ### Environment diff --git a/webui/react/src/e2e/fixtures/dev.fixture.ts b/webui/react/src/e2e/fixtures/dev.fixture.ts index e501a0342ba..607c5de0319 100644 --- a/webui/react/src/e2e/fixtures/dev.fixture.ts +++ b/webui/react/src/e2e/fixtures/dev.fixture.ts @@ -1,12 +1,5 @@ import { Page } from '@playwright/test'; -import { - BaseComponent, - ComponentBasics, - ComponentContainer, -} from 'playwright-page-model-base/BaseComponent'; -import { expect } from 'e2e/fixtures/global-fixtures'; -import { DeterminedPage } from 'e2e/models/common/base/BasePage'; import { apiUrl } from 'e2e/utils/envVars'; export class DevFixture { @@ -19,23 +12,4 @@ export class DevFixture { // dev.setServerAddress fires a logout request in the background, so we will wait until no network traffic is happening. await page.waitForLoadState('networkidle'); }; - - /** - * Attempts to locate each element in the locator tree. If there is an error at this step, - * the last locator in the error message is the locator that couldn't be found and needs - * to be debugged. If there is no error message, the component could be located and this - * debug line can be removed. - * @param {BaseComponent} component - The component to debug - */ - debugComponentVisible(component: BaseComponent): void { - const componentTree: ComponentContainer[] = []; - let root: ComponentContainer = component; - while (!(root instanceof DeterminedPage)) { - componentTree.unshift(root); - root = (root as ComponentBasics)._parent; - } - componentTree.forEach(async (node) => { - await expect(node.pwLocator).toBeVisible(); - }); - } } diff --git a/webui/react/src/e2e/models/components/F_ExperimentList.ts b/webui/react/src/e2e/models/components/F_ExperimentList.ts index ee41d9ba0c1..b05e3d92e5f 100644 --- a/webui/react/src/e2e/models/components/F_ExperimentList.ts +++ b/webui/react/src/e2e/models/components/F_ExperimentList.ts @@ -32,7 +32,7 @@ class ExperimentHeadRow extends HeadRow {} /** * Represents the ExperimentRow in the F_ExperimentList component */ -class ExperimentRow extends Row { +export class ExperimentRow extends Row { constructor(args: RowArgs) { super(args); this.columnPositions.set('ID', 50); diff --git a/webui/react/src/e2e/models/components/MultiSortMenu.ts b/webui/react/src/e2e/models/components/MultiSortMenu.ts index 7af551c1f7a..f59d9f587b2 100644 --- a/webui/react/src/e2e/models/components/MultiSortMenu.ts +++ b/webui/react/src/e2e/models/components/MultiSortMenu.ts @@ -37,7 +37,8 @@ class MultiSort extends NamedComponent { readonly defaultSelector = '[data-test-component="multiSort"]'; readonly add = new BaseComponent({ parent: this, selector: '[data-test="add"]' }); readonly reset = new BaseComponent({ parent: this, selector: '[data-test="reset"]' }); - readonly rows = new MultiSortRow({ parent: this, selector: '[data-test="rows"]' }); + readonly rowContainer = new BaseComponent({ parent: this, selector: '[data-test="rows"]' }); + readonly rows = new MultiSortRow({ parent: this.rowContainer }); } /** diff --git a/webui/react/src/e2e/tests/experimentList.spec.ts b/webui/react/src/e2e/tests/experimentList.spec.ts index b20409062dd..e2bcc21a50b 100644 --- a/webui/react/src/e2e/tests/experimentList.spec.ts +++ b/webui/react/src/e2e/tests/experimentList.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from 'e2e/fixtures/global-fixtures'; +import { ExperimentRow } from 'e2e/models/components/F_ExperimentList'; import { ProjectDetails } from 'e2e/models/pages/ProjectDetails'; import { detExecSync, fullPath } from 'e2e/utils/detCLI'; import { safeName } from 'e2e/utils/naming'; @@ -393,6 +394,7 @@ test.describe('Experiment List', () => { }).toPass(); }); }); + test.describe('Experiment List Pagination', () => { test.beforeAll(({ newProject }) => { Array(51) @@ -439,4 +441,122 @@ test.describe('Experiment List', () => { expectPageNumber(null); }); }); + test.describe('Experiment List Multi-sort', () => { + const runScenarioAndValidation = (projectDetailsPage: ProjectDetails) => { + const multiSortMenu = projectDetailsPage.f_experimentList.tableActionBar.multiSortMenu; + const validateByColumn = async ( + rows: ExperimentRow[], + colKey: string, + descending: boolean, + ) => { + const valuesToCompare = await Promise.all( + rows.map(async (r) => (await r.getCellByColumnName(colKey)).pwLocator.innerText()), + ); + // eslint-disable-next-line no-console + console.log(valuesToCompare[0]); + + const expectedValues = [...valuesToCompare].sort(); + if (descending) { + expectedValues.reverse(); + } + expect(valuesToCompare).toEqual(expectedValues); + }; + const checkTableOrder = async (firstKey: string, secondKey: string, descending = false) => { + const rows = await projectDetailsPage.f_experimentList.dataGrid.filterRows( + async () => await true, + ); + await validateByColumn(rows, firstKey, descending); + await validateByColumn(rows, secondKey, descending); + }; + const sortingScenario = async ( + firstSortBy: string, + firstSortOrder: string, + secondSortBy: string, + secondSortOrder: string, + scenario: () => Promise, + ): Promise => { + await test.step(`Sort by ${firstSortBy} and ${secondSortBy}`, async () => { + await multiSortMenu.open(); + await multiSortMenu.multiSort.reset.pwLocator.click(); + await multiSortMenu.close(); + await multiSortMenu.open(); + + const firstRow = multiSortMenu.multiSort.rows.nth(0); + await firstRow.column.selectMenuOption(firstSortBy); + await firstRow.order.selectMenuOption(firstSortOrder); + + await multiSortMenu.multiSort.add.pwLocator.click(); + + const secondRow = multiSortMenu.multiSort.rows.nth(1); + await secondRow.column.selectMenuOption(secondSortBy); + await secondRow.order.selectMenuOption(secondSortOrder); + + await multiSortMenu.close(); + await waitTableStable(); + await scenario(); + }); + }; + + return { checkTableOrder, sortingScenario }; + }; + + test.beforeAll(async ({ newProject }) => { + // create a new experiment for comparing Searcher Metric and Trial Count + await detExecSync( + `experiment create ${fullPath('examples/tutorials/core_api_pytorch_mnist/checkpoints.yaml')} --paused --project_id ${newProject.response.project.id}`, + ); + }); + + // set table columns to have just the columns that we are using in the test cases. + test.beforeEach(async ({ authedPage, newProject }) => { + projectDetailsPage = new ProjectDetails(authedPage); + await projectDetailsPage.gotoProject(newProject.response.project.id); + const columnPicker = projectDetailsPage.f_experimentList.tableActionBar.columnPickerMenu; + await columnPicker.open(); + const showAllButton = columnPicker.columnPickerTab.showAll.pwLocator; + await showAllButton.click(); + if ((await showAllButton.textContent()) === 'Hide all') { + await showAllButton.click(); + } + + const columnTitles = ['id', 'searcherType', 'numTrials', 'searcherMetric']; + + for (const title of columnTitles) { + const checkbox = columnPicker.columnPickerTab.columns.listItem(title).checkbox; + await checkbox.pwLocator.check(); + } + + await columnPicker.close(); + + await projectDetailsPage.f_experimentList.dataGrid.headRow.setColumnDefs(); + }); + + test('sort with ID as 0 → 9 and Searcher as A → Z', async () => { + const { sortingScenario, checkTableOrder } = runScenarioAndValidation(projectDetailsPage); + await sortingScenario('ID', '0 → 9', 'Searcher', 'A → Z', async () => { + await checkTableOrder('ID', 'Searcher'); + }); + }); + + test('sort with ID as 9 → 0 and Searcher as Z → A', async () => { + const { sortingScenario, checkTableOrder } = runScenarioAndValidation(projectDetailsPage); + await sortingScenario('ID', '9 → 0', 'Searcher', 'Z → A', async () => { + await checkTableOrder('ID', 'Searcher', true); + }); + }); + + test('sort with Trial count as 0 → 0 and Searcher Metric as A → Z', async () => { + const { sortingScenario, checkTableOrder } = runScenarioAndValidation(projectDetailsPage); + await sortingScenario('Trial count', '0 → 9', 'Searcher Metric', 'A → Z', async () => { + await checkTableOrder('Trial count', 'Searcher Metric'); + }); + }); + + test('sort with Trial count as 9 → 0 and Searcher Metric as Z → A', async () => { + const { sortingScenario, checkTableOrder } = runScenarioAndValidation(projectDetailsPage); + await sortingScenario('Trial count', '9 → 0', 'Searcher Metric', 'Z → A', async () => { + await checkTableOrder('Trial count', 'Searcher Metric', true); + }); + }); + }); }); diff --git a/webui/react/src/e2e/utils/debug.ts b/webui/react/src/e2e/utils/debug.ts index 6664fc72e37..a72531e2cda 100644 --- a/webui/react/src/e2e/utils/debug.ts +++ b/webui/react/src/e2e/utils/debug.ts @@ -1,5 +1,32 @@ +import { + BaseComponent, + ComponentBasics, + ComponentContainer, +} from 'playwright-page-model-base/BaseComponent'; + +import { DeterminedPage } from 'e2e/models/common/base/BasePage'; + export function printMap(map: Map): string { return Array.from(map.entries()) .map(([key, value]) => `${key}: ${value}`) .join('\n'); } + +/** + * Attempts to locate each element in the locator tree. If there is an error at this step, + * the last locator in the error message is the locator that couldn't be found and needs + * to be debugged. If there is no error message, the component could be located and this + * debug line can be removed. + * @param {BaseComponent} component - The component to debug + */ +export async function debugComponentVisible(component: BaseComponent): Promise { + const componentTree: ComponentContainer[] = []; + let root: ComponentContainer = component; + while (!(root instanceof DeterminedPage)) { + componentTree.unshift(root); + root = (root as ComponentBasics)._parent; + } + for (const node of componentTree) { + await node.pwLocator.waitFor(); + } +} From 9635fa3708e2f18d6d604babc56ec9939b1bbc89 Mon Sep 17 00:00:00 2001 From: Thiago Dallacqua Date: Wed, 9 Oct 2024 17:51:42 -0300 Subject: [PATCH 4/5] refactor: Enable sorting, filtering, and moving projects in Project List e2e tests. --- .../components/Table/InteractiveTable.ts | 2 + .../WorkspaceDetails/WorkspaceProjects.ts | 22 +- webui/react/src/e2e/tests/projects.spec.ts | 337 +++++++++--------- .../WorkspaceDetails/WorkspaceProjects.tsx | 2 +- 4 files changed, 188 insertions(+), 175 deletions(-) diff --git a/webui/react/src/e2e/models/components/Table/InteractiveTable.ts b/webui/react/src/e2e/models/components/Table/InteractiveTable.ts index 2194320df03..f93133e6cc8 100644 --- a/webui/react/src/e2e/models/components/Table/InteractiveTable.ts +++ b/webui/react/src/e2e/models/components/Table/InteractiveTable.ts @@ -42,4 +42,6 @@ export class InteractiveTable< readonly table: Table; readonly skeleton = new SkeletonTable({ parent: this }); + + // TODO: add getRowByColumnValue (maybe base on the DataGrid model) } diff --git a/webui/react/src/e2e/models/pages/WorkspaceDetails/WorkspaceProjects.ts b/webui/react/src/e2e/models/pages/WorkspaceDetails/WorkspaceProjects.ts index 8399aea13aa..1c84321504f 100644 --- a/webui/react/src/e2e/models/pages/WorkspaceDetails/WorkspaceProjects.ts +++ b/webui/react/src/e2e/models/pages/WorkspaceDetails/WorkspaceProjects.ts @@ -4,6 +4,7 @@ import { BaseReactFragment } from 'playwright-page-model-base/BaseReactFragment' import { Select } from 'e2e/models/common/hew/Select'; import { Toggle } from 'e2e/models/common/hew/Toggle'; import { GridListRadioGroup } from 'e2e/models/components/GridListRadioGroup'; +import { ProjectActionDropdown } from 'e2e/models/components/ProjectActionDropdown'; import { ProjectCard } from 'e2e/models/components/ProjectCard'; import { ProjectCreateModal } from 'e2e/models/components/ProjectCreateModal'; import { ProjectDeleteModal } from 'e2e/models/components/ProjectDeleteModal'; @@ -52,31 +53,34 @@ class ProjectRow extends Row { }); readonly description = new BaseComponent({ parent: this, - selector: '[data-testid="Description"]', + selector: '[data-testid="description"]', }); readonly numExperiments = new BaseComponent({ parent: this, - selector: '[data-testid="NumExperiments"]', + selector: '[data-testid="numExperiments"]', }); readonly lastUpdated = new BaseComponent({ parent: this, - selector: '[data-testid="LastUpdated"]', + selector: '[data-testid="lastUpdated"]', }); readonly userId = new BaseComponent({ parent: this, - selector: '[data-testid="UserId"]', + selector: '[data-testid="userId"]', }); readonly archived = new BaseComponent({ parent: this, - selector: '[data-testid="Archived"]', + selector: '[data-testid="archived"]', }); readonly state = new BaseComponent({ parent: this, - selector: '[data-testid="State"]', + selector: '[data-testid="state"]', }); - readonly action = new BaseComponent({ - parent: this, - selector: '[data-testid="Action"]', + readonly action = new ProjectActionDropdown({ + clickThisComponentToOpen: new BaseComponent({ + parent: this, + selector: '[data-testid="actionMenu"]', + }), + root: this.root, }); } diff --git a/webui/react/src/e2e/tests/projects.spec.ts b/webui/react/src/e2e/tests/projects.spec.ts index bba974f8281..ba86a7cbd6a 100644 --- a/webui/react/src/e2e/tests/projects.spec.ts +++ b/webui/react/src/e2e/tests/projects.spec.ts @@ -1,12 +1,11 @@ import _ from 'lodash'; import { expect, test } from 'e2e/fixtures/global-fixtures'; -// import { ProjectDetails } from 'e2e/models/pages/ProjectDetails'; +import { ProjectDetails } from 'e2e/models/pages/ProjectDetails'; import { WorkspaceDetails } from 'e2e/models/pages/WorkspaceDetails'; import { WorkspaceProjects } from 'e2e/models/pages/WorkspaceDetails/WorkspaceProjects'; -// import { randId, safeName } from 'e2e/utils/naming'; -import { safeName } from 'e2e/utils/naming'; -import { V1Project } from 'services/api-ts-sdk'; +import { randId, safeName } from 'e2e/utils/naming'; +import { V1PostProjectResponse, V1Project, V1Workspace } from 'services/api-ts-sdk'; const getCurrentProjectCardNames = async (workspaceProjects: WorkspaceProjects) => { await workspaceProjects.projectCards.pwLocator.nth(0).waitFor(); @@ -19,124 +18,124 @@ const getCurrentProjectCardNames = async (workspaceProjects: WorkspaceProjects) ); }; -// test.describe('Project UI CRUD', () => { -// const projectIds: number[] = []; - -// test.beforeEach(async ({ authedPage, newWorkspace }) => { -// const workspaceDetails = new WorkspaceDetails(authedPage); -// await workspaceDetails.gotoWorkspace(newWorkspace.response.workspace.id); -// await workspaceDetails.workspaceProjects.showArchived.switch.uncheck(); -// }); - -// test.afterAll(async ({ backgroundApiProject }) => { -// for (const project of projectIds) { -// await backgroundApiProject.deleteProject(project); -// } -// }); - -// test.skip('Create a Project', async ({ authedPage, newWorkspace }) => { -// const projectName = safeName('test-project'); -// const workspaceDetails = new WorkspaceDetails(authedPage); -// const projectDetails = new ProjectDetails(authedPage); - -// const workspaceProjects = workspaceDetails.workspaceProjects; - -// await test.step('Create a Project', async () => { -// await workspaceProjects.newProject.pwLocator.click(); -// await workspaceProjects.createModal.projectName.pwLocator.fill(projectName); -// await workspaceProjects.createModal.description.pwLocator.fill(randId()); -// await workspaceProjects.createModal.footer.submit.pwLocator.click(); -// projectIds.push(await projectDetails.getIdFromUrl()); -// await workspaceDetails.gotoWorkspace(newWorkspace.response.workspace.id); -// await workspaceProjects.cardByName(projectName).pwLocator.waitFor(); -// }); - -// await test.step('Delete a Project', async () => { -// await workspaceDetails.gotoWorkspace(newWorkspace.response.workspace.id); -// await workspaceDetails.projectsTab.pwLocator.click(); -// const projectCard = workspaceProjects.cardByName(projectName); -// await projectCard.actionMenu.open(); -// await projectCard.actionMenu.delete.pwLocator.click(); -// await workspaceProjects.deleteModal.nameConfirmation.pwLocator.fill(projectName); -// await workspaceProjects.deleteModal.footer.submit.pwLocator.click(); -// }); -// }); - -// test.skip('Archive and Unarchive Project', async ({ -// authedPage, -// newWorkspace, -// backgroundApiProject, -// }) => { -// const workspaceDetails = new WorkspaceDetails(authedPage); - -// const newProject = await backgroundApiProject.createProject( -// newWorkspace.response.workspace.id, -// backgroundApiProject.new(), -// ); -// projectIds.push(newProject.project.id); -// const projectCard = workspaceDetails.workspaceProjects.cardByName(newProject.project.name); -// const archiveMenuItem = projectCard.actionMenu.archive; - -// await test.step('Archive', async () => { -// await authedPage.reload(); -// await projectCard.actionMenu.open(); -// await expect(archiveMenuItem.pwLocator).toHaveText('Archive'); -// await archiveMenuItem.pwLocator.click(); -// await projectCard.pwLocator.waitFor({ state: 'hidden' }); -// }); - -// await test.step('Unarchive', async () => { -// await workspaceDetails.workspaceProjects.showArchived.switch.pwLocator.click(); -// await projectCard.archivedBadge.pwLocator.waitFor(); -// await projectCard.actionMenu.open(); -// await expect(archiveMenuItem.pwLocator).toHaveText('Unarchive'); -// await archiveMenuItem.pwLocator.click(); -// await projectCard.archivedBadge.pwLocator.waitFor({ state: 'hidden' }); -// }); -// }); - -// test.skip('Move a Project', async ({ -// authedPage, -// newWorkspace, -// backgroundApiWorkspace, -// backgroundApiProject, -// }) => { -// const workspaceDetails = new WorkspaceDetails(authedPage); - -// const destinationWorkspace = ( -// await backgroundApiWorkspace.createWorkspace(backgroundApiWorkspace.new()) -// ).workspace; - -// const newProject = await backgroundApiProject.createProject( -// newWorkspace.response.workspace.id, -// backgroundApiProject.new(), -// ); -// projectIds.push(newProject.project.id); - -// await authedPage.reload(); - -// const workspaceProjects = workspaceDetails.workspaceProjects; -// const projectCard = workspaceProjects.cardByName(newProject.project.name); -// const moveMenuItem = projectCard.actionMenu.move; - -// await projectCard.actionMenu.open(); -// await moveMenuItem.pwLocator.click(); -// await workspaceProjects.moveModal.destinationWorkspace.pwLocator.fill( -// destinationWorkspace.name, -// ); -// await workspaceProjects.moveModal.destinationWorkspace.pwLocator.press('Enter'); -// await workspaceProjects.moveModal.footer.submit.pwLocator.click(); - -// await workspaceProjects.moveModal.pwLocator.waitFor({ state: 'hidden' }); -// await projectCard.pwLocator.waitFor({ state: 'hidden' }); - -// await workspaceDetails.gotoWorkspace(destinationWorkspace.id); - -// await projectCard.pwLocator.waitFor(); - -// await backgroundApiWorkspace.deleteWorkspace(destinationWorkspace.id); -// }); -// }); +test.describe('Project UI CRUD', () => { + const projectIds: number[] = []; + + test.beforeEach(async ({ authedPage, newWorkspace }) => { + const workspaceDetails = new WorkspaceDetails(authedPage); + await workspaceDetails.gotoWorkspace(newWorkspace.response.workspace.id); + await workspaceDetails.workspaceProjects.showArchived.switch.uncheck(); + }); + + test.afterAll(async ({ backgroundApiProject }) => { + for (const project of projectIds) { + await backgroundApiProject.deleteProject(project); + } + }); + + test.skip('Create a Project', async ({ authedPage, newWorkspace }) => { + const projectName = safeName('test-project'); + const workspaceDetails = new WorkspaceDetails(authedPage); + const projectDetails = new ProjectDetails(authedPage); + + const workspaceProjects = workspaceDetails.workspaceProjects; + + await test.step('Create a Project', async () => { + await workspaceProjects.newProject.pwLocator.click(); + await workspaceProjects.createModal.projectName.pwLocator.fill(projectName); + await workspaceProjects.createModal.description.pwLocator.fill(randId()); + await workspaceProjects.createModal.footer.submit.pwLocator.click(); + projectIds.push(await projectDetails.getIdFromUrl()); + await workspaceDetails.gotoWorkspace(newWorkspace.response.workspace.id); + await workspaceProjects.cardByName(projectName).pwLocator.waitFor(); + }); + + await test.step('Delete a Project', async () => { + await workspaceDetails.gotoWorkspace(newWorkspace.response.workspace.id); + await workspaceDetails.projectsTab.pwLocator.click(); + const projectCard = workspaceProjects.cardByName(projectName); + await projectCard.actionMenu.open(); + await projectCard.actionMenu.delete.pwLocator.click(); + await workspaceProjects.deleteModal.nameConfirmation.pwLocator.fill(projectName); + await workspaceProjects.deleteModal.footer.submit.pwLocator.click(); + }); + }); + + test.skip('Archive and Unarchive Project', async ({ + authedPage, + newWorkspace, + backgroundApiProject, + }) => { + const workspaceDetails = new WorkspaceDetails(authedPage); + + const newProject = await backgroundApiProject.createProject( + newWorkspace.response.workspace.id, + backgroundApiProject.new(), + ); + projectIds.push(newProject.project.id); + const projectCard = workspaceDetails.workspaceProjects.cardByName(newProject.project.name); + const archiveMenuItem = projectCard.actionMenu.archive; + + await test.step('Archive', async () => { + await authedPage.reload(); + await projectCard.actionMenu.open(); + await expect(archiveMenuItem.pwLocator).toHaveText('Archive'); + await archiveMenuItem.pwLocator.click(); + await projectCard.pwLocator.waitFor({ state: 'hidden' }); + }); + + await test.step('Unarchive', async () => { + await workspaceDetails.workspaceProjects.showArchived.switch.pwLocator.click(); + await projectCard.archivedBadge.pwLocator.waitFor(); + await projectCard.actionMenu.open(); + await expect(archiveMenuItem.pwLocator).toHaveText('Unarchive'); + await archiveMenuItem.pwLocator.click(); + await projectCard.archivedBadge.pwLocator.waitFor({ state: 'hidden' }); + }); + }); + + test.skip('Move a Project', async ({ + authedPage, + newWorkspace, + backgroundApiWorkspace, + backgroundApiProject, + }) => { + const workspaceDetails = new WorkspaceDetails(authedPage); + + const destinationWorkspace = ( + await backgroundApiWorkspace.createWorkspace(backgroundApiWorkspace.new()) + ).workspace; + + const newProject = await backgroundApiProject.createProject( + newWorkspace.response.workspace.id, + backgroundApiProject.new(), + ); + projectIds.push(newProject.project.id); + + await authedPage.reload(); + + const workspaceProjects = workspaceDetails.workspaceProjects; + const projectCard = workspaceProjects.cardByName(newProject.project.name); + const moveMenuItem = projectCard.actionMenu.move; + + await projectCard.actionMenu.open(); + await moveMenuItem.pwLocator.click(); + await workspaceProjects.moveModal.destinationWorkspace.pwLocator.fill( + destinationWorkspace.name, + ); + await workspaceProjects.moveModal.destinationWorkspace.pwLocator.press('Enter'); + await workspaceProjects.moveModal.footer.submit.pwLocator.click(); + + await workspaceProjects.moveModal.pwLocator.waitFor({ state: 'hidden' }); + await projectCard.pwLocator.waitFor({ state: 'hidden' }); + + await workspaceDetails.gotoWorkspace(destinationWorkspace.id); + + await projectCard.pwLocator.waitFor(); + + await backgroundApiWorkspace.deleteWorkspace(destinationWorkspace.id); + }); +}); test.describe('Project List', () => { const projects: V1Project[] = []; @@ -175,7 +174,7 @@ test.describe('Project List', () => { } }); - test.skip('Sort', async ({ authedPage }) => { + test('Sort', async ({ authedPage }) => { const workspaceDetails = new WorkspaceDetails(authedPage); const workspaceProjects = workspaceDetails.workspaceProjects; @@ -198,7 +197,7 @@ test.describe('Project List', () => { ); }); - test.skip('Filter', async ({ authedPage, apiProject, newWorkspace }) => { + test('Filter', async ({ authedPage, apiProject, newWorkspace }) => { const workspaceDetails = new WorkspaceDetails(authedPage); const workspaceProjects = workspaceDetails.workspaceProjects; @@ -231,7 +230,7 @@ test.describe('Project List', () => { await apiProject.deleteProject(currentUserProject.id); }); - test.skip('View Toggle', async ({ authedPage }) => { + test('View Toggle', async ({ authedPage }) => { const workspaceDetails = new WorkspaceDetails(authedPage); const workspaceProjects = workspaceDetails.workspaceProjects; @@ -247,60 +246,68 @@ test.describe('Project List', () => { await firstCard.pwLocator.waitFor(); }); - test('Move a Project', async ({ - authedPage, - newWorkspace, - backgroundApiWorkspace, - backgroundApiProject, - }) => { - const workspaceDetails = new WorkspaceDetails(authedPage); - const workspaceProjects = workspaceDetails.workspaceProjects; - - await workspaceProjects.gridListRadioGroup.list.pwLocator.click(); + test.describe('List View', () => { + let newProject: V1PostProjectResponse; + let destinationWorkspace: V1Workspace; - const projectsList = workspaceDetails.workspaceProjects.table.table; + test.beforeAll(async ({ newWorkspace, backgroundApiProject, backgroundApiWorkspace }) => { + newProject = await backgroundApiProject.createProject( + newWorkspace.response.workspace.id, + backgroundApiProject.new(), + ); + destinationWorkspace = ( + await backgroundApiWorkspace.createWorkspace(backgroundApiWorkspace.new()) + ).workspace; + }); + test.beforeEach(async ({ authedPage }) => { + const workspaceDetails = new WorkspaceDetails(authedPage); - const newProject = await backgroundApiProject.createProject( - newWorkspace.response.workspace.id, - backgroundApiProject.new(), - ); + const workspaceProjects = workspaceDetails.workspaceProjects; - await authedPage.reload(); + await workspaceProjects.gridListRadioGroup.list.pwLocator.click(); + }); + test.afterAll(async ({ backgroundApiWorkspace }) => { + await backgroundApiWorkspace.deleteWorkspace(destinationWorkspace.id); + }); - const row = await projectsList.rowByAttributeGenerator('name')(newProject.project.name); + test('move a project to a different workspace', async ({ authedPage }) => { + const workspaceDetails = new WorkspaceDetails(authedPage); + const workspaceProjects = workspaceDetails.workspaceProjects; - await expect(row).toBeDefined(); + const projectsTable = workspaceProjects.table.table; - const destinationWorkspace = ( - await backgroundApiWorkspace.createWorkspace(backgroundApiWorkspace.new()) - ).workspace; - // const moveMenuItem = row.actionMenu.move; + const newProjectRow = ( + await projectsTable.filterRows( + async (row) => (await row.name.pwLocator.textContent()) === newProject.project.name, + ) + )[0]; - // await row.actionMenu.open(); - // await moveMenuItem.pwLocator.click(); - await workspaceProjects.moveModal.destinationWorkspace.pwLocator.fill( - destinationWorkspace.name, - ); - await workspaceProjects.moveModal.destinationWorkspace.pwLocator.press('Enter'); - await workspaceProjects.moveModal.footer.submit.pwLocator.click(); + expect(await newProjectRow.name.pwLocator.innerText()).toBe(newProject.project.name); - await workspaceProjects.moveModal.pwLocator.waitFor({ state: 'hidden' }); - await row.pwLocator.waitFor({ state: 'hidden' }); + const moveMenuItem = newProjectRow.action; - const projectsWithoutNewItem = - await workspaceProjects.table.table.rows.pwLocator.allTextContents(); + await moveMenuItem.open(); + await moveMenuItem.move.pwLocator.click(); + await workspaceProjects.moveModal.destinationWorkspace.pwLocator.fill( + destinationWorkspace.name, + ); + await workspaceProjects.moveModal.destinationWorkspace.pwLocator.press('Enter'); + await workspaceProjects.moveModal.footer.submit.pwLocator.click(); - await expect(projectsWithoutNewItem.includes(newProject.project.name)).toBeFalsy(); + await workspaceProjects.moveModal.pwLocator.waitFor({ state: 'hidden' }); + await newProjectRow.pwLocator.waitFor({ state: 'hidden' }); - await workspaceDetails.gotoWorkspace(destinationWorkspace.id); + const projectsWithoutNewItem = await projectsTable.filterRows( + async (row) => (await row.name.pwLocator.textContent()) === newProject.project.name, + ); - await row.pwLocator.waitFor(); + await expect(projectsWithoutNewItem.length).toBe(0); - // const projectsWithNewItem = await getCurrentProjectRowNames(workspaceProjects); + await workspaceDetails.gotoWorkspace(destinationWorkspace.id); - // expect(initialProjects.length).toStrictEqual(newStateOfProjects.length); - // expect(newStateOfProjects.includes(newProject.project.name)).toBeFalsy(); + const projectCard = workspaceProjects.cardByName(newProject.project.name); - // await backgroundApiWorkspace.deleteWorkspace(destinationWorkspace.id); + await expect(projectCard.pwLocator).toBeVisible(); + }); }); }); diff --git a/webui/react/src/pages/WorkspaceDetails/WorkspaceProjects.tsx b/webui/react/src/pages/WorkspaceDetails/WorkspaceProjects.tsx index fa9477c5ed1..5e66d13790e 100644 --- a/webui/react/src/pages/WorkspaceDetails/WorkspaceProjects.tsx +++ b/webui/react/src/pages/WorkspaceDetails/WorkspaceProjects.tsx @@ -306,7 +306,7 @@ const WorkspaceProjects: React.FC = ({ workspace, id, pageRef }) => { defaultWidth: DEFAULT_COLUMN_WIDTHS['action'], fixed: 'right', key: 'action', - onCell: onRightClickableCell, + onCell: () => ({ ...onRightClickableCell(), 'data-testid': 'actionMenu' }), render: actionRenderer, title: '', }, From 27c7b41a379cdebb3859a58b3f11dff099a8ae97 Mon Sep 17 00:00:00 2001 From: Thiago Dallacqua Date: Wed, 9 Oct 2024 17:57:22 -0300 Subject: [PATCH 5/5] test(e2e): Refactor project list test for clarity and accuracy. --- webui/react/src/e2e/tests/projects.spec.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/webui/react/src/e2e/tests/projects.spec.ts b/webui/react/src/e2e/tests/projects.spec.ts index ba86a7cbd6a..3dab56dd30c 100644 --- a/webui/react/src/e2e/tests/projects.spec.ts +++ b/webui/react/src/e2e/tests/projects.spec.ts @@ -276,7 +276,7 @@ test.describe('Project List', () => { const projectsTable = workspaceProjects.table.table; - const newProjectRow = ( + let newProjectRow = ( await projectsTable.filterRows( async (row) => (await row.name.pwLocator.textContent()) === newProject.project.name, ) @@ -297,11 +297,13 @@ test.describe('Project List', () => { await workspaceProjects.moveModal.pwLocator.waitFor({ state: 'hidden' }); await newProjectRow.pwLocator.waitFor({ state: 'hidden' }); - const projectsWithoutNewItem = await projectsTable.filterRows( - async (row) => (await row.name.pwLocator.textContent()) === newProject.project.name, - ); + newProjectRow = ( + await projectsTable.filterRows( + async (row) => (await row.name.pwLocator.textContent()) === newProject.project.name, + ) + )[0]; - await expect(projectsWithoutNewItem.length).toBe(0); + await expect(newProjectRow).toBeUndefined(); await workspaceDetails.gotoWorkspace(destinationWorkspace.id);