Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 1714: fix validation for locator name #1740

Merged
merged 7 commits into from
May 28, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: add custom validation for visible and invisible (notShownElemen…
…tIds) elements & fix code style, names
Iogsotot committed May 28, 2024
commit 94133475878fbc2d237ba3b10551149fb84ab7d4
4 changes: 4 additions & 0 deletions src/app/main.selectors.ts
Original file line number Diff line number Diff line change
@@ -9,6 +9,10 @@ export const selectCurrentPage = (state: RootState) => {
return last(state.main.pageHistory) || ({ page: PageType.PageObject } as Page);
};

const selectMain = (state: RootState) => state.main;

export const selectServerLocation = createSelector([selectMain], (main) => main.baseUrl ?? '');

export const selectIsDefaultState = createSelector(
selectCurrentPage,
selectPageObjects,
26 changes: 15 additions & 11 deletions src/features/locators/components/LocatorEditDialog.tsx
Original file line number Diff line number Diff line change
@@ -18,21 +18,21 @@ import {
LocatorValidationErrorType,
LocatorValidationWarnings,
} from '../types/locator.types';

import { changeLocatorAttributes } from '../locators.slice';
import { createNewName, getLocatorValidationStatus, getLocatorValueOnTypeSwitch } from '../utils/utils';
import { createLocatorValidationRules } from '../utils/locatorValidationRules';
import { createNameValidationRules } from '../utils/nameValidationRules';
import { AnnotationType, FrameworkType, LocatorType, SelectOption } from '../../../common/types/common';
import { isFilteredSelect } from '../../../common/utils/helpers';
import { CALCULATING, newLocatorStub } from '../utils/constants';
import { changeLocatorElement } from '../reducers/changeLocatorElement.thunk';
import { addCustomLocator } from '../reducers/addCustomLocator.thunk';
import { selectPresentLocatorsByPO } from '../selectors/locatorsByPO.selectors';
import { LocatorMessageForDuplicate } from './LocatorMessageForDuplicate';
import { createLocatorTypeOptions, ILocatorTypeOptions } from '../utils/createLocatorTypeOptions';
import { validateLocator } from '../utils/locatorValidation';
import { annotationTypeOptions } from './utils';
import { changeLocatorElement } from '../reducers/changeLocatorElement.thunk';
import { changeLocatorAttributes } from '../locators.slice';
import { selectNotShownElementIds } from '../../../services/pageDocument/pageDocument.selectors';

interface Props extends ILocator {
isModalOpen: boolean;
@@ -52,7 +52,7 @@ export const LocatorEditDialog: React.FC<Props> = ({
isModalOpen,
setIsModalOpen,
isCreatingForm = false,
element_id,
elementId,
isCustomName = true,
name,
type,
@@ -95,14 +95,16 @@ export const LocatorEditDialog: React.FC<Props> = ({
const [isOkButtonDisabled, setIsOkButtonDisabled] = useState<boolean>(true);
// ToDo: fix legacy:
// eslint-disable-next-line @typescript-eslint/naming-convention
const _isNameUnique = (value: string) => !isNameUnique(locators, element_id, value);
const _isNameUnique = (value: string) => !isNameUnique(locators, elementId, value);

const nameValidationRules: Rule[] = createNameValidationRules(_isNameUnique);

const closeDialog = () => {
form.resetFields();
setIsModalOpen(false);
};

const notShownElementIds = useSelector(selectNotShownElementIds);
const getLocatorValidationRules: () => Rule[] = () =>
createLocatorValidationRules(
isCreatingForm,
@@ -111,15 +113,16 @@ export const LocatorEditDialog: React.FC<Props> = ({
setValidationErrorOptions,
locators,
jdnHash,
element_id,
elementId,
notShownElementIds,
);
const [locatorValidationRules, setLocatorValidationRules] = useState<Rule[]>(getLocatorValidationRules());

const handleTypeChange = (value: string) => {
if (isEditedName) return;

const newName = createNewName(
{ element_id, isCustomName, type, name, elemId, elemName, elemText } as ILocator,
{ elementId, isCustomName, type, name, elemId, elemName, elemText } as ILocator,
value,
library,
locators,
@@ -177,7 +180,7 @@ export const LocatorEditDialog: React.FC<Props> = ({
locatorValue,
annotationType,
locatorType,
element_id,
elementId,
library,
message: validationMessage,
isCustomName: isEditedName,
@@ -229,7 +232,7 @@ export const LocatorEditDialog: React.FC<Props> = ({
const newLocatorValue = await getLocatorValueOnTypeSwitch(
newLocatorType,
validationMessage,
element_id,
elementId,
jdnHash,
locatorValue,
form,
@@ -276,7 +279,7 @@ export const LocatorEditDialog: React.FC<Props> = ({
} else {
setLocatorTypeOptions(staticLocatorTypeOptions);
}
}, [locatorValue, locators, element_id]);
}, [locatorValue, locators, elementId]);

const handleLocatorDropdownOnChange = async () => {
setValidationMessage('');
@@ -290,7 +293,8 @@ export const LocatorEditDialog: React.FC<Props> = ({
locatorTypeFromForm,
jdnHash,
locators,
element_id,
elementId,
notShownElementIds,
isCreatingForm,
);

14 changes: 9 additions & 5 deletions src/features/locators/reducers/checkLocatorValidity.thunk.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
import { ActionReducerMapBuilder, createAsyncThunk } from '@reduxjs/toolkit';
import { type ActionReducerMapBuilder, createAsyncThunk } from '@reduxjs/toolkit';
import { RootState } from '../../../app/store/store';
import { locatorsAdapter } from '../selectors/locators.selectors';
import { ILocator, LocatorsState, LocatorValidationErrorType } from '../types/locator.types';
import { validateLocator } from '../utils/locatorValidation';
import { LocatorType } from '../../../common/types/common';
import { selectLocatorsByPageObject } from '../selectors/locatorsByPO.selectors';
import { useSelector } from 'react-redux';
import { selectNotShownElementIds } from '../../../services/pageDocument/pageDocument.selectors';

export const checkLocatorsValidity = createAsyncThunk('locators/checkLocatorsValidity', async (payload, thunkAPI) => {
const state = thunkAPI.getState();

const locators: ILocator[] = selectLocatorsByPageObject(state as RootState);

const invalidLocators: Partial<ILocator>[] = [];
const notShownElementIds = useSelector(selectNotShownElementIds);

for (const locator of locators) {
const { jdnHash, element_id, locatorValue, locatorType } = locator;
const { jdnHash, elementId, locatorValue, locatorType } = locator;
try {
const validation = await validateLocator(
locatorValue.output ?? '',
locatorType || LocatorType.xPath,
jdnHash,
locators,
element_id,
elementId,
notShownElementIds,
);
if (validation.length)
invalidLocators.push({ element_id, message: validation as LocatorValidationErrorType, jdnHash });
invalidLocators.push({ elementId, message: validation as LocatorValidationErrorType, jdnHash });
} catch (error) {
invalidLocators.push({ element_id, message: error.message as LocatorValidationErrorType, jdnHash });
invalidLocators.push({ elementId, message: error.message as LocatorValidationErrorType, jdnHash });
}
}

14 changes: 10 additions & 4 deletions src/features/locators/utils/locatorValidation.ts
Original file line number Diff line number Diff line change
@@ -16,7 +16,8 @@ export const validateLocator = async (
locatorType: LocatorType,
jdnHash: JDNHash,
locators: ILocator[],
element_id: ElementId,
elementId: ElementId,
notShownElementIds: string[],
isCreatingForm?: boolean,
): Promise<LocatorValidationErrorType> => {
let length;
@@ -25,19 +26,24 @@ export const validateLocator = async (
let validatedJdnHash;
let validationMessage: LocatorValidationErrorType = '';

const locatorValue = await evaluateLocator(locatorString, locatorType, element_id, jdnHash);
const locatorValue = await evaluateLocator(locatorString, locatorType, elementId, jdnHash);
if (locatorValue === LocatorValidationWarnings.StartsWithDigit) {
validationMessage = LocatorValidationWarnings.StartsWithDigit;
} else if (locatorValue === LocatorValidationWarnings.NotFound || !locatorValue) {
validationMessage = LocatorValidationWarnings.NotFound; //validationStatus: WARNING
} else {
({ length, foundHash } = JSON.parse(locatorValue));
validatedElementId = JSON.parse(locatorValue).element_id || element_id;
validatedElementId = JSON.parse(locatorValue).elementId || elementId;
validatedJdnHash = JSON.parse(locatorValue).originJdnHash || jdnHash;

// TODO: remove hardcode, when for Selenium framework support is added to the project (issue/585)
const isSelenium = false; // hardcode
const duplicateErrorCondition = isSelenium ? length > 1 : length > 1 && !notShownElementIds.includes(foundHash);

if (length === 0) {
validationMessage = LocatorValidationWarnings.NotFound; //validationStatus: WARNING
} else if (length > 1) {
} else if (duplicateErrorCondition) {
console.log('throw duplicate error');
const err = `${length} ${LocatorValidationErrors.MultipleElements}` as LocatorValidationErrorType; //validationStatus: ERROR;
throw new Error(err);
} else if (length === 1 && validatedJdnHash !== foundHash) {
8 changes: 5 additions & 3 deletions src/features/locators/utils/locatorValidationRules.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react';
import { Rule, RuleObject } from 'antd/lib/form';
import { validateLocator } from './locatorValidation';
import { ElementId, ILocator, LocatorValidationErrorType, LocatorValidationWarnings } from '../types/locator.types';
@@ -10,7 +11,8 @@ export const createLocatorValidationRules = (
setValidationErrorOptions: React.Dispatch<React.SetStateAction<any>>,
locators: ILocator[],
jdnHash: string,
element_id: ElementId,
elementId: ElementId,
notShownElementIds: string[],
): Rule[] => {
return [
{
@@ -19,14 +21,14 @@ export const createLocatorValidationRules = (
setValidationMessage(LocatorValidationWarnings.EmptyValue); // validationStatus: WARNING
return Promise.resolve();
}

try {
const validationMessage = await validateLocator(
locatorValue,
locatorType,
jdnHash,
locators,
element_id,
elementId,
notShownElementIds,
isCreatingForm,
);
setValidationMessage(validationMessage as LocatorValidationErrorType);
Original file line number Diff line number Diff line change
@@ -54,7 +54,6 @@ export const selectAutoGeneratingLocatorTypes = createSelector(selectCurrentPage
generateCssSelector: !pageObj.hideUnadded && pageObj.locatorType === LocatorType.cssSelector,
generateXpath: !pageObj.hideUnadded && pageObj.locatorType === LocatorType.xPath,
};
// console.log('isAutogenerated: ', isAutogenerated); // LOG

return isAutogenerated;
});
8 changes: 4 additions & 4 deletions src/pageServices/contentScripts/generationData.js
Original file line number Diff line number Diff line change
@@ -91,20 +91,20 @@ export const getGenerationAttributes = () => {
};

const generateSelectorGroupByHash = (elements) => {
return elements.map(({ element_id, jdnHash }) => {
return elements.map(({ elementId, jdnHash }) => {
const element = document.querySelector(`[jdn-hash='${jdnHash}']`);
return {
element_id,
elementId,
locatorValue: {
cssSelector: element ? getUniqueCssSelector(element) : null,
},
};
});
};

const generateSelectorByHash = ({ element_id, jdnHash }) => {
const generateSelectorByHash = ({ elementId, jdnHash }) => {
const element = document.querySelector(`[jdn-hash='${jdnHash}']`);
return element ? { element_id, cssSelector: getUniqueCssSelector(element) } : null;
return element ? { elementId, cssSelector: getUniqueCssSelector(element) } : null;
};

/*
7 changes: 6 additions & 1 deletion src/services/pageDocument/pageDocument.reducers.ts
Original file line number Diff line number Diff line change
@@ -10,12 +10,17 @@ export const pageDocumentReducers = {

const notShownElementIds = action.payload
.filter((el: PredictedEntity) => !el.is_shown)
.map((el: PredictedEntity) => el.element_id);
.map((el: PredictedEntity) => el.elementId);

state.notShownElementIds = notShownElementIds;

/* set cleaned Html String into pageDocumentForRobula: */
state.pageDocumentForRobula = removeNodesByAttribute(documentContent, 'jdn-hash', notShownElementIds);
} else {
console.error('Document content is not available.');
}
},
setNotShownElementsIds(state: PageDocumentState, action: PayloadAction<string[]>) {
state.notShownElementIds = action.payload;
},
};
1 change: 1 addition & 0 deletions src/services/pageDocument/pageDocument.selectors.ts
Original file line number Diff line number Diff line change
@@ -8,3 +8,4 @@ export const selectPageDocumentForRobula = createSelector(
selectPageDocumentState,
(state) => state.pageDocumentForRobula,
);
export const selectNotShownElementIds = createSelector(selectPageDocumentState, (state) => state.notShownElementIds);
5 changes: 3 additions & 2 deletions src/services/pageDocument/pageDocument.slice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createSlice } from '@reduxjs/toolkit';

import { pageDocumentReducers } from './pageDocument.reducers';
import { pageDocumentExtraReducers } from './fetchPageDocument.thunk';

@@ -10,11 +9,13 @@ export interface PageDocumentState {
error?: string;
};
pageDocumentForRobula: null | string;
notShownElementIds: string[];
}

export const initialState: PageDocumentState = {
pageDocument: { content: null, isLoading: false },
pageDocumentForRobula: null,
notShownElementIds: [],
};

const pageDocumentSlice = createSlice({
@@ -24,6 +25,6 @@ const pageDocumentSlice = createSlice({
extraReducers: pageDocumentExtraReducers,
});

export const { createDocumentForRobula } = pageDocumentSlice.actions;
export const { createDocumentForRobula, setNotShownElementsIds } = pageDocumentSlice.actions;

export default pageDocumentSlice.reducer;