diff --git a/webui/react/src/components/ExperimentMoveModal.tsx b/webui/react/src/components/ExperimentMoveModal.tsx index 7fbe5868c92..fac37ead413 100644 --- a/webui/react/src/components/ExperimentMoveModal.tsx +++ b/webui/react/src/components/ExperimentMoveModal.tsx @@ -179,6 +179,7 @@ const ExperimentMoveModalComponent: React.FC = ({ name="workspaceId" rules={[{ message: 'Workspace is required', required: true }]}> (option?.title?.toString() ?? '').toLowerCase().includes(input.toLowerCase()) } diff --git a/webui/react/src/e2e/models/components/ExperimentActionDropdown.ts b/webui/react/src/e2e/models/components/ExperimentActionDropdown.ts index f95d12a3450..4c0cb3d2bd0 100644 --- a/webui/react/src/e2e/models/components/ExperimentActionDropdown.ts +++ b/webui/react/src/e2e/models/components/ExperimentActionDropdown.ts @@ -1,6 +1,7 @@ import { DropdownMenu } from 'e2e/models/common/hew/Dropdown'; import ExperimentEditModal from './ExperimentEditModal'; +import ExperimentMoveModal from './ExperimentMoveModal'; /** * Represents the ExperimentActionDropdown component in src/components/ExperimentActionDropdown.tsx @@ -14,4 +15,7 @@ export class ExperimentActionDropdown extends DropdownMenu { readonly editModal = new ExperimentEditModal({ root: this.root, }); + readonly moveModal = new ExperimentMoveModal({ + root: this.root, + }); } diff --git a/webui/react/src/e2e/models/components/ExperimentMoveModal.ts b/webui/react/src/e2e/models/components/ExperimentMoveModal.ts new file mode 100644 index 00000000000..cd90cd80334 --- /dev/null +++ b/webui/react/src/e2e/models/components/ExperimentMoveModal.ts @@ -0,0 +1,16 @@ +import { Modal } from 'e2e/models/common/hew/Modal'; +import { Select } from 'e2e/models/common/hew/Select'; + +/** + * Represents the ExperimentMoveModal component in src/components/ExperimentMoveModal.tsx + */ +export default class ExperimentMoveModal extends Modal { + readonly destinationWorkspace = new Select({ + parent: this, + selector: '[data-test="workspace"]', + }); + readonly destinationProject = new Select({ + parent: this, + selector: '[data-test="project"]', + }); +} diff --git a/webui/react/src/e2e/tests/experimentList.spec.ts b/webui/react/src/e2e/tests/experimentList.spec.ts index d06a959dddc..8ae7f66d01a 100644 --- a/webui/react/src/e2e/tests/experimentList.spec.ts +++ b/webui/react/src/e2e/tests/experimentList.spec.ts @@ -7,6 +7,7 @@ import { ProjectDetails } from 'e2e/models/pages/ProjectDetails'; import { detExecSync, fullPath } from 'e2e/utils/detCLI'; import { safeName } from 'e2e/utils/naming'; import { repeatWithFallback } from 'e2e/utils/polling'; +import { V1Project } from 'services/api-ts-sdk'; import { ExperimentBase } from 'types'; dayjs.extend(utcPlugin); @@ -595,4 +596,86 @@ test.describe('Experiment List', () => { { column: 'Searcher Metric', direction: 'desc' }, ]); }); + + test.describe('Row Actions', () => { + let destinationProject: V1Project; + let experimentId: number; + + // create a new project, workspace and experiment + test.beforeAll( + async ({ + backgroundApiProject, + newProject: { + response: { project }, + }, + }) => { + destinationProject = ( + await backgroundApiProject.createProject( + project.workspaceId, + backgroundApiProject.new({ projectProps: { workspaceId: project.workspaceId } }), + ) + ).project; + + const expId = Number( + detExecSync( + `experiment create ${fullPath('examples/tutorials/mnist_pytorch/adaptive.yaml')} --paused --project_id ${project.id}`, + ).split(' ')[2], + ); // returns in the format "Created experiment " + + if (Number.isNaN(expId)) throw new Error('No experiment ID was found'); + + experimentId = expId; + }, + ); + + // cleanup + test.afterAll(async ({ backgroundApiProject }) => { + if (experimentId !== undefined) { + detExecSync(`experiment kill ${experimentId}`); + detExecSync(`experiment delete ${experimentId} --y`); + } + + await backgroundApiProject.deleteProject(destinationProject.id); + }); + + test('move experiment', async ({ + newWorkspace: { + response: { workspace }, + }, + }) => { + if (experimentId === undefined) throw new Error('No experiment ID was found'); + + const newExperimentRow = + await projectDetailsPage.f_experimentList.dataGrid.getRowByColumnValue( + 'ID', + experimentId.toString(), + ); + + const experimentActionDropdown = await newExperimentRow.experimentActionDropdown.open(); + + await experimentActionDropdown.menuItem('Move').pwLocator.click(); + await experimentActionDropdown.moveModal.destinationWorkspace.selectMenuOption( + workspace.name, + ); + await experimentActionDropdown.moveModal.destinationProject.pwLocator.waitFor({ + state: 'visible', + }); + await experimentActionDropdown.moveModal.destinationProject.selectMenuOption( + destinationProject.name, + ); + await experimentActionDropdown.moveModal.footer.submit.pwLocator.click(); + await experimentActionDropdown.moveModal.pwLocator.waitFor({ state: 'hidden' }); + + await newExperimentRow.pwLocator.waitFor({ state: 'hidden' }); + + await projectDetailsPage.gotoProject(destinationProject.id); + const grid = projectDetailsPage.f_experimentList.dataGrid; + await grid.setColumnHeight(); + await grid.headRow.setColumnDefs(); + const newProjectRows = await projectDetailsPage.f_experimentList.dataGrid.filterRows(() => + Promise.resolve(true), + ); + await expect(newProjectRows.length).toBe(1); + }); + }); });