Skip to content

Commit

Permalink
[ML] Add AD job validation UI tests (#179358)
Browse files Browse the repository at this point in the history
## Summary

- Add job wizard validation suite
- Add job wizard model change annotation recommendation callout test
subject
- Add job wizard model time picker test subject using `popperProps`
- Add test subjects for two steps: Time Range and Job Details
- Add more methods to job wizard common service

---------

Co-authored-by: Dima Arnautov <arnautov.dima@gmail.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Robert Oskamp <traeluki@gmail.com>
  • Loading branch information
4 people committed May 27, 2024
1 parent 57b2555 commit dc46dfc
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const Message: FC<Pick<CalloutMessage, 'text' | 'url'>> = ({ text, url }) => (
export const Callout: FC<CalloutMessage> = ({ heading, status, text, url }) => (
<>
<EuiCallOut
data-test-subj={'mlValidationCallout'}
data-test-subj={`mlValidationCallout ${status}`}
// @ts-ignore
color={statusToEuiColor(status)}
size="s"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ export const TimeRangePicker: FC<Props> = ({ setTimeRange, timeRange }) => {
fullWidth={true}
startDateControl={
<EuiDatePicker
popperProps={{
'data-test-subj': 'mlJobWizardDatePickerRangeStartDate',
}}
selected={startMoment}
onChange={handleChangeStart}
startDate={startMoment}
Expand All @@ -86,6 +89,9 @@ export const TimeRangePicker: FC<Props> = ({ setTimeRange, timeRange }) => {
}
endDateControl={
<EuiDatePicker
popperProps={{
'data-test-subj': 'mlJobWizardDatePickerRangeEndDate',
}}
selected={endMoment}
onChange={handleChangeEnd}
startDate={startMoment}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const AnnotationsSwitch: FC = () => {
</Description>
{showCallOut && (
<EuiCallOut
data-test-subj="mlJobWizardAlsoEnableAnnotationsRecommendationCallout"
title={
<FormattedMessage
id="xpack.ml.newJob.wizard.jobDetailsStep.advancedSection.annotationsSwitchCallout.title"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,30 +42,35 @@ export const WizardHorizontalSteps: FC<Props> = ({
defaultMessage: 'Time range',
}),
...createStepProps(WIZARD_STEPS.TIME_RANGE),
'data-test-subj': 'mlJobWizardTimeRangeStep',
},
{
title: i18n.translate('xpack.ml.newJob.wizard.step.pickFieldsTitle', {
defaultMessage: 'Choose fields',
}),
...createStepProps(WIZARD_STEPS.PICK_FIELDS),
'data-test-subj': 'mlJobWizardPickFieldsStep',
},
{
title: i18n.translate('xpack.ml.newJob.wizard.step.jobDetailsTitle', {
defaultMessage: 'Job details',
}),
...createStepProps(WIZARD_STEPS.JOB_DETAILS),
'data-test-subj': 'mlJobWizardJobDetailsStep',
},
{
title: i18n.translate('xpack.ml.newJob.wizard.step.validationTitle', {
defaultMessage: 'Validation',
}),
...createStepProps(WIZARD_STEPS.VALIDATION),
'data-test-subj': 'mlJobWizardValidationStep',
},
{
title: i18n.translate('xpack.ml.newJob.wizard.step.summaryTitle', {
defaultMessage: 'Summary',
}),
...createStepProps(WIZARD_STEPS.SUMMARY),
'data-test-subj': 'mlJobWizardSummaryStep',
},
];

Expand All @@ -75,6 +80,7 @@ export const WizardHorizontalSteps: FC<Props> = ({
defaultMessage: 'Configure datafeed',
}),
...createStepProps(WIZARD_STEPS.ADVANCED_CONFIGURE_DATAFEED),
'data-test-subj': 'mlJobWizardAdvancedStep',
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,5 +388,90 @@ export default function ({ getService }: FtrProviderContext) {
);
await ml.api.assertNoJobResultsExist(jobIdClone);
});

it('job cloning with too short of a job creation time range results in validation callouts', async () => {
await ml.testExecution.logTestStep('job cloning loads the job management page');
await ml.navigation.navigateToMl();
await ml.navigation.navigateToJobManagement();

await ml.testExecution.logTestStep(`cloning job: [${jobId}]`);
await ml.jobTable.clickCloneJobAction(jobId);

await ml.testExecution.logTestStep('job cloning displays the time range step');
await ml.jobWizardCommon.assertTimeRangeSectionExists();

await ml.testExecution.logTestStep('job cloning sets the time range');
await ml.jobWizardCommon.clickUseFullDataButton(
'Feb 7, 2016 @ 00:00:00.000',
'Feb 11, 2016 @ 23:59:54.000'
);

await ml.jobWizardCommon.goToTimeRangeStep();

const { startDate: origStartDate } = await ml.jobWizardCommon.getSelectedDateRange();

await ml.testExecution.logTestStep('calculate the new end date');
const shortDurationEndDate: string = `${origStartDate?.split(':', 1)[0]}:01:00.000`;

await ml.testExecution.logTestStep('set the new end date');
await ml.jobWizardCommon.setTimeRange({ endTime: shortDurationEndDate });

// assert time is set as expected
await ml.jobWizardCommon.assertDateRangeSelection(
origStartDate as string,
shortDurationEndDate
);

await ml.jobWizardCommon.advanceToPickFieldsSection();
await ml.jobWizardCommon.advanceToJobDetailsSection();
await ml.jobWizardCommon.assertJobIdInputExists();
await ml.jobWizardCommon.setJobId(`${jobIdClone}-again`);
await ml.jobWizardCommon.advanceToValidationSection();
await ml.jobWizardCommon.assertValidationCallouts([
'mlValidationCallout warning',
'mlValidationCallout error',
]);
await ml.jobWizardCommon.assertCalloutText(
'mlValidationCallout warning',
/Time range\s*The selected or available time range might be too short/
);

await ml.jobWizardCommon.goToTimeRangeStep();
await ml.jobWizardCommon.clickUseFullDataButton(
'Feb 7, 2016 @ 00:00:00.000',
'Feb 11, 2016 @ 23:59:54.000'
);
await ml.jobWizardCommon.goToValidationStep();
await ml.jobWizardCommon.assertValidationCallouts(['mlValidationCallout success']);
await ml.jobWizardCommon.assertCalloutText(
'mlValidationCallout success',
/Time range\s*Valid and long enough to model patterns in the data/
);
});

it('job creation and toggling model change annotation triggers enable annotation recommendation callout', async () => {
await ml.jobWizardCommon.goToJobDetailsStep();
await ml.jobWizardCommon.ensureAdvancedSectionOpen();

await ml.commonUI.toggleSwitchIfNeeded('mlJobWizardSwitchAnnotations', false);
await ml.jobWizardCommon.assertAnnotationRecommendationCalloutVisible();

await ml.commonUI.toggleSwitchIfNeeded('mlJobWizardSwitchAnnotations', true);
await ml.jobWizardCommon.assertAnnotationRecommendationCalloutVisible(false);
});

it('job creation memory limit too large results in validation callout', async () => {
await ml.jobWizardCommon.goToJobDetailsStep();

const tooLarge = '100000000MB';
await ml.jobWizardCommon.setModelMemoryLimit(tooLarge);

await ml.jobWizardCommon.advanceToValidationSection();
await ml.jobWizardCommon.assertValidationCallouts(['mlValidationCallout warning']);
await ml.jobWizardCommon.assertCalloutText(
'mlValidationCallout warning',
/Job will not be able to run in the current cluster because model memory limit is higher than/
);
});
});
}
4 changes: 4 additions & 0 deletions x-pack/test/functional/services/ml/common_ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,5 +447,9 @@ export function MachineLearningCommonUIProvider({
await testSubjects.missingOrFail(selector);
}
},

async toggleSwitchIfNeeded(testSubj: string, targetState: boolean) {
await testSubjects.setEuiSwitch(testSubj, targetState ? 'check' : 'uncheck');
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -553,12 +553,12 @@ export function MachineLearningDataFrameAnalyticsCreationProvider(

async assertValidationCalloutsExists() {
await retry.tryForTime(4000, async () => {
await testSubjects.existOrFail('mlValidationCallout');
await testSubjects.existOrFail('~mlValidationCallout');
});
},

async assertAllValidationCalloutsPresent(expectedNumCallouts: number) {
const validationCallouts = await testSubjects.findAll('mlValidationCallout');
const validationCallouts = await testSubjects.findAll('~mlValidationCallout');
expect(validationCallouts.length).to.eql(expectedNumCallouts);
},

Expand Down
74 changes: 74 additions & 0 deletions x-pack/test/functional/services/ml/job_wizard_common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export function MachineLearningJobWizardCommonProvider(
const retry = getService('retry');
const testSubjects = getService('testSubjects');
const headerPage = getPageObject('header');
const browser = getService('browser');

function advancedSectionSelector(subSelector?: string) {
const subj = 'mlJobWizardAdvancedSection';
Expand Down Expand Up @@ -628,5 +629,78 @@ export function MachineLearningJobWizardCommonProvider(
await testSubjects.existOrFail(expectedSelector);
});
},

async assertAnnotationRecommendationCalloutVisible(expectVisible: boolean = true) {
const callOutTestSubj = 'mlJobWizardAlsoEnableAnnotationsRecommendationCallout';
if (expectVisible)
await testSubjects.existOrFail(callOutTestSubj, {
timeout: 3_000,
});
else
await testSubjects.missingOrFail(callOutTestSubj, {
timeout: 3_000,
});
},

async goToTimeRangeStep() {
await retry.tryForTime(60_000, async () => {
await testSubjects.existOrFail('mlJobWizardTimeRangeStep');
await testSubjects.click('mlJobWizardTimeRangeStep');
await this.assertTimeRangeSectionExists();
});
},

async goToValidationStep() {
await retry.tryForTime(60_000, async () => {
await testSubjects.existOrFail('mlJobWizardValidationStep');
await testSubjects.click('mlJobWizardValidationStep');
await this.assertValidationSectionExists();
});
},

async setTimeRange({ startTime, endTime }: { startTime?: string; endTime?: string }) {
const opts = {
clearWithKeyboard: true,
typeCharByChar: true,
};

if (startTime)
await testSubjects.setValue('mlJobWizardDatePickerRangeStartDate', startTime, opts);
if (endTime) await testSubjects.setValue('mlJobWizardDatePickerRangeEndDate', endTime, opts);

// escape popover
await browser.pressKeys(browser.keys.ESCAPE);
},

async goToJobDetailsStep() {
await testSubjects.existOrFail('mlJobWizardJobDetailsStep', {
timeout: 3_000,
});
await testSubjects.click('mlJobWizardJobDetailsStep');
await this.assertJobDetailsSectionExists();
},

async assertValidationCallouts(expectedCallOutSelectors: string[]) {
for await (const sel of expectedCallOutSelectors)
await testSubjects.existOrFail(sel, {
timeout: 3_000,
});
},

async assertCalloutText(calloutStatusTestSubj: string, expectedText: RegExp) {
const allCalloutStatusTexts = await testSubjects.getVisibleTextAll(calloutStatusTestSubj);

const oneCalloutMatches = allCalloutStatusTexts.some(
(visibleText) => !!visibleText.match(expectedText)
);
expect(oneCalloutMatches).to.eql(
true,
`Expect one of the callouts [${calloutStatusTestSubj}] to match [${expectedText}], instead found ${JSON.stringify(
allCalloutStatusTexts,
null,
2
)}`
);
},
};
}

0 comments on commit dc46dfc

Please sign in to comment.