[ML] Functional tests for 3rd party models deployment (#138242) (#138389)

* test start model deployment

* start and stop tests

* wait for refresh

* wait for refresh

* update assertLastToastHeader with retry

(cherry picked from commit b08914c0db)

Co-authored-by: Dima Arnautov <dmitrii.arnautov@elastic.co>
This commit is contained in:
Kibana Machine 2022-08-09 10:35:48 -04:00 committed by GitHub
parent 6c6a872ebd
commit d8f57738ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 134 additions and 11 deletions

View file

@ -345,6 +345,7 @@ export const ModelsList: FC<Props> = ({
description: i18n.translate('xpack.ml.inference.modelsList.startModelDeploymentActionLabel', {
defaultMessage: 'Start deployment',
}),
'data-test-subj': 'mlModelsTableRowStartDeploymentAction',
icon: 'play',
type: 'icon',
isPrimary: true,
@ -399,6 +400,7 @@ export const ModelsList: FC<Props> = ({
description: i18n.translate('xpack.ml.inference.modelsList.stopModelDeploymentActionLabel', {
defaultMessage: 'Stop deployment',
}),
'data-test-subj': 'mlModelsTableRowStopDeploymentAction',
icon: 'stop',
type: 'icon',
isPrimary: true,
@ -497,6 +499,7 @@ export const ModelsList: FC<Props> = ({
description: i18n.translate('xpack.ml.inference.modelsList.testModelActionLabel', {
defaultMessage: 'Test model',
}),
'data-test-subj': 'mlModelsTableRowTestAction',
icon: 'inputOutput',
type: 'icon',
isPrimary: true,

View file

@ -111,6 +111,7 @@ export const StartDeploymentSetup: FC<StartDeploymentSetup> = ({ config, onConfi
onChange={(event) => {
onConfigChange({ ...config, numOfAllocations: Number(event.target.value) });
}}
data-test-subj={'mlModelsStartDeploymentModalNumOfAllocations'}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
@ -156,6 +157,7 @@ export const StartDeploymentSetup: FC<StartDeploymentSetup> = ({ config, onConfi
onConfigChange({ ...config, threadsPerAllocations: value });
}}
options={threadsPerAllocationsOptions}
data-test-subj={'mlModelsStartDeploymentModalThreadsPerAllocation'}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
@ -195,7 +197,12 @@ export const StartDeploymentModal: FC<StartDeploymentModalProps> = ({
const errors = numOfAllocationsValidator(config.numOfAllocations);
return (
<EuiModal onClose={onClose} initialFocus="[name=numOfAllocations]" maxWidth={false}>
<EuiModal
onClose={onClose}
initialFocus="[name=numOfAllocations]"
maxWidth={false}
data-test-subj="mlModelsStartDeploymentModal"
>
<EuiModalHeader>
<EuiModalHeaderTitle>
<EuiFlexGroup justifyContent={'spaceBetween'}>
@ -288,6 +295,7 @@ export const StartDeploymentModal: FC<StartDeploymentModalProps> = ({
onClick={onConfigChange.bind(null, config)}
fill
disabled={!!errors}
data-test-subj={'mlModelsStartDeploymentModalStartButton'}
>
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.startDeployment.startButton"

View file

@ -91,16 +91,6 @@ export default function ({ getService }: FtrProviderContext) {
await ml.trainedModelsTable.assertPipelinesTabContent(false);
});
for (const model of trainedModels) {
it(`renders expanded row content correctly for imported tiny model ${model.id} without pipelines`, async () => {
await ml.trainedModelsTable.ensureRowIsExpanded(model.id);
await ml.trainedModelsTable.assertDetailsTabContent();
await ml.trainedModelsTable.assertInferenceConfigTabContent();
await ml.trainedModelsTable.assertStatsTabContent();
await ml.trainedModelsTable.assertPipelinesTabContent(false);
});
}
it('displays the built-in model and no actions are enabled', async () => {
await ml.testExecution.logTestStep('should display the model in the table');
await ml.trainedModelsTable.filterWithSearchString(builtInModelData.modelId, 1);
@ -186,6 +176,29 @@ export default function ({ getService }: FtrProviderContext) {
false
);
});
describe('with imported models', function () {
for (const model of trainedModels) {
it(`renders expanded row content correctly for imported tiny model ${model.id} without pipelines`, async () => {
await ml.trainedModelsTable.ensureRowIsExpanded(model.id);
await ml.trainedModelsTable.assertDetailsTabContent();
await ml.trainedModelsTable.assertInferenceConfigTabContent();
await ml.trainedModelsTable.assertStatsTabContent();
await ml.trainedModelsTable.assertPipelinesTabContent(false);
});
it(`starts deployment of the imported model ${model.id}`, async () => {
await ml.trainedModelsTable.startDeploymentWithParams(model.id, {
numOfAllocations: 1,
threadsPerAllocation: 2,
});
});
it(`stops deployment of the imported model ${model.id}`, async () => {
await ml.trainedModelsTable.stopDeployment(model.id);
});
}
});
});
describe('for ML user with read-only access', () => {

View file

@ -32,6 +32,7 @@ export function MachineLearningCommonUIProvider({
const testSubjects = getService('testSubjects');
const find = getService('find');
const browser = getService('browser');
const toasts = getService('toasts');
return {
async setValueWithChecks(
@ -356,5 +357,36 @@ export function MachineLearningCommonUIProvider({
);
});
},
async selectButtonGroupValue(inputTestSubj: string, value: string) {
await retry.tryForTime(5000, async () => {
// The input element can not be clicked directly.
// Instead, we need to click the corresponding label
const fieldSetElement = await testSubjects.find(inputTestSubj);
const labelElement = await fieldSetElement.findByCssSelector(`label[title="${value}"]`);
await labelElement.click();
const labelClasses = await labelElement.getAttribute('class');
expect(labelClasses).to.contain(
'euiButtonGroupButton-isSelected',
`Label for '${inputTestSubj}' should be selected`
);
});
},
async assertLastToastHeader(expectedHeader: string, timeout: number = 5000) {
await retry.tryForTime(timeout, async () => {
const resultToast = await toasts.getToastElement(1);
const titleElement = await testSubjects.findDescendant('euiToastHeader', resultToast);
const title: string = await titleElement.getVisibleText();
expect(title).to.eql(
expectedHeader,
`Expected the toast header to equal "${expectedHeader}" (got "${title}")`
);
});
await toasts.dismissAllToasts();
},
};
}

View file

@ -197,6 +197,14 @@ export function TrainedModelsTableProvider(
await testSubjects.existOrFail('mlModelsDeleteModal', { timeout: 60 * 1000 });
}
public async assertStartDeploymentModalExists(expectExist = true) {
if (expectExist) {
await testSubjects.existOrFail('mlModelsStartDeploymentModal', { timeout: 60 * 1000 });
} else {
await testSubjects.missingOrFail('mlModelsStartDeploymentModal', { timeout: 60 * 1000 });
}
}
public async assertDeleteModalNotExists() {
await testSubjects.missingOrFail('mlModelsDeleteModal', { timeout: 60 * 1000 });
}
@ -214,6 +222,65 @@ export function TrainedModelsTableProvider(
await this.assertDeleteModalExists();
}
async assertNumOfAllocations(expectedValue: number) {
const actualValue = await testSubjects.getAttribute(
'mlModelsStartDeploymentModalNumOfAllocations',
'value'
);
expect(actualValue).to.eql(
expectedValue,
`Expected number of allocations to equal ${expectedValue}, got ${actualValue}`
);
}
public async setNumOfAllocations(value: number) {
await testSubjects.setValue('mlModelsStartDeploymentModalNumOfAllocations', value.toString());
await this.assertNumOfAllocations(value);
}
public async setThreadsPerAllocation(value: number) {
await mlCommonUI.selectButtonGroupValue(
'mlModelsStartDeploymentModalThreadsPerAllocation',
value.toString()
);
}
public async startDeploymentWithParams(
modelId: string,
params: { numOfAllocations: number; threadsPerAllocation: number }
) {
await this.openStartDeploymentModal(modelId);
await this.setNumOfAllocations(params.numOfAllocations);
await this.setThreadsPerAllocation(params.threadsPerAllocation);
await testSubjects.click('mlModelsStartDeploymentModalStartButton');
await this.assertStartDeploymentModalExists(false);
await mlCommonUI.waitForRefreshButtonEnabled();
await mlCommonUI.assertLastToastHeader(
`Deployment for "${modelId}" has been started successfully.`
);
}
public async stopDeployment(modelId: string) {
await this.clickStopDeploymentAction(modelId);
await mlCommonUI.waitForRefreshButtonEnabled();
await mlCommonUI.assertLastToastHeader(
`Deployment for "${modelId}" has been stopped successfully.`
);
}
public async openStartDeploymentModal(modelId: string) {
await testSubjects.click(this.rowSelector(modelId, 'mlModelsTableRowStartDeploymentAction'));
await this.assertStartDeploymentModalExists(true);
}
public async clickStopDeploymentAction(modelId: string) {
await testSubjects.click(this.rowSelector(modelId, 'mlModelsTableRowStopDeploymentAction'));
}
public async ensureRowIsExpanded(modelId: string) {
await this.filterWithSearchString(modelId);
await retry.tryForTime(10 * 1000, async () => {