diff --git a/examples/tutorials/core_api/2_checkpoints.yaml b/examples/tutorials/core_api/2_checkpoints.yaml index 025afedf201..cc5b34e0fa1 100644 --- a/examples/tutorials/core_api/2_checkpoints.yaml +++ b/examples/tutorials/core_api/2_checkpoints.yaml @@ -6,5 +6,9 @@ entrypoint: python3 2_checkpoints.py searcher: name: single metric: x + max_length: 14 + smaller_is_better: true + source_checkpoint_uuid: null + source_trial_id: null max_restarts: 0 diff --git a/examples/tutorials/mnist_pytorch/adaptive.yaml b/examples/tutorials/mnist_pytorch/adaptive.yaml index 5953cdad5d6..e35031f8b5d 100644 --- a/examples/tutorials/mnist_pytorch/adaptive.yaml +++ b/examples/tutorials/mnist_pytorch/adaptive.yaml @@ -25,6 +25,7 @@ searcher: metric: validation_loss smaller_is_better: true max_trials: 16 + max_length: 14 time_metric: batches max_time: 937 # 60,000 training images with batch size 64 entrypoint: python3 train.py --epochs 1 diff --git a/webui/react/src/components/ExperimentStopModal.tsx b/webui/react/src/components/ExperimentStopModal.tsx index 675dff4d3be..19e262f5c13 100644 --- a/webui/react/src/components/ExperimentStopModal.tsx +++ b/webui/react/src/components/ExperimentStopModal.tsx @@ -61,7 +61,7 @@ const ExperimentStopModalComponent: React.FC = ({ experimentId, onClose } }} title="Confirm Stop" onClose={onClose}> -
+
Are you sure you want to {actionCopy} {experimentId}?
diff --git a/webui/react/src/e2e/models/components/ExperimentActionDropdown.ts b/webui/react/src/e2e/models/components/ExperimentActionDropdown.ts index 4c0cb3d2bd0..9bc2efa7938 100644 --- a/webui/react/src/e2e/models/components/ExperimentActionDropdown.ts +++ b/webui/react/src/e2e/models/components/ExperimentActionDropdown.ts @@ -2,6 +2,7 @@ import { DropdownMenu } from 'e2e/models/common/hew/Dropdown'; import ExperimentEditModal from './ExperimentEditModal'; import ExperimentMoveModal from './ExperimentMoveModal'; +import ExperimentStopModal from './ExperimentStopModal'; /** * Represents the ExperimentActionDropdown component in src/components/ExperimentActionDropdown.tsx @@ -18,4 +19,7 @@ export class ExperimentActionDropdown extends DropdownMenu { readonly moveModal = new ExperimentMoveModal({ root: this.root, }); + readonly stopKillModal = new ExperimentStopModal({ + root: this.root, + }); } diff --git a/webui/react/src/e2e/models/components/ExperimentStopModal.ts b/webui/react/src/e2e/models/components/ExperimentStopModal.ts new file mode 100644 index 00000000000..453da9d5f4d --- /dev/null +++ b/webui/react/src/e2e/models/components/ExperimentStopModal.ts @@ -0,0 +1,12 @@ +import { Modal } from 'e2e/models/common/hew/Modal'; +import { Select } from 'e2e/models/common/hew/Select'; + +/** + * Represents the ExperimentStopModal component in src/components/ExperimentStopModal.tsx + */ +export default class ExperimentStopModal extends Modal { + readonly targetExperiment = new Select({ + parent: this, + selector: '[data-test="experiment"]', + }); +} diff --git a/webui/react/src/e2e/tests/experimentList.spec.ts b/webui/react/src/e2e/tests/experimentList.spec.ts index 8ae7f66d01a..a3ab1198ffa 100644 --- a/webui/react/src/e2e/tests/experimentList.spec.ts +++ b/webui/react/src/e2e/tests/experimentList.spec.ts @@ -8,7 +8,7 @@ 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'; +import { ExperimentBase, RunState } from 'types'; dayjs.extend(utcPlugin); @@ -600,6 +600,7 @@ test.describe('Experiment List', () => { test.describe('Row Actions', () => { let destinationProject: V1Project; let experimentId: number; + let killExperiment: number; // create a new project, workspace and experiment test.beforeAll( @@ -616,15 +617,13 @@ test.describe('Experiment List', () => { ) ).project; - const expId = Number( + experimentId = 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; + if (Number.isNaN(experimentId)) throw new Error('No experiment ID was found'); }, ); @@ -634,6 +633,10 @@ test.describe('Experiment List', () => { detExecSync(`experiment kill ${experimentId}`); detExecSync(`experiment delete ${experimentId} --y`); } + if (killExperiment !== undefined) { + detExecSync(`experiment kill ${killExperiment}`); + detExecSync(`experiment delete ${killExperiment} --y`); + } await backgroundApiProject.deleteProject(destinationProject.id); }); @@ -677,5 +680,45 @@ test.describe('Experiment List', () => { ); await expect(newProjectRows.length).toBe(1); }); + + test('kill experiment', async ({ + newProject: { + response: { project }, + }, + newWorkspace: { + response: { workspace }, + }, + }) => { + killExperiment = Number( + detExecSync( + `experiment create ${fullPath('examples/tutorials/core_api/2_checkpoints.yaml')} --paused --project_id ${project.id}`, + ).split(' ')[2], + ); + if (Number.isNaN(killExperiment)) throw new Error('No experiment ID was found'); + + const grid = projectDetailsPage.f_experimentList.dataGrid; + await grid.setColumnHeight(); + await grid.headRow.setColumnDefs(); + const newExperimentRow = + await projectDetailsPage.f_experimentList.dataGrid.getRowByColumnValue( + 'ID', + killExperiment.toString(), + ); + + const experimentActionDropdown = await newExperimentRow.experimentActionDropdown.open(); + + await experimentActionDropdown.menuItem('Kill').pwLocator.click(); + + await experimentActionDropdown.stopKillModal.pwLocator.waitFor({ state: 'visible' }); + await experimentActionDropdown.stopKillModal.footer.pwLocator.getByText('Kill').click(); + await experimentActionDropdown.stopKillModal.pwLocator.waitFor({ state: 'hidden' }); + + const experiments: ExperimentBase[] = JSON.parse( + detExecSync(`project list-experiments --json ${workspace.name} ${project.name}`), + ); + const killedExperiment = experiments.find(({ id }) => id === killExperiment); + await expect(killedExperiment).not.toBeUndefined(); + await expect(killedExperiment?.state).toContain(RunState.Canceled); + }); }); });