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 1590: fix bug with fake unique name attribut #1607

Merged
merged 2 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "JDN",
"description": "JDN – helps Test Automation Engineer to create Page Objects in the test automation framework and speed up test development",
"devtools_page": "index.html",
"version": "3.14.35",
"version": "3.14.36",
"icons": {
"128": "icon128.png"
},
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jdn-ai-chrome-extension",
"version": "3.14.35",
"version": "3.14.36",
"description": "jdn-ai chrome extension",
"scripts": {
"start": "webpack --watch --env devenv",
Expand Down
18 changes: 15 additions & 3 deletions src/features/locators/components/LocatorEditDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { Form, Input, Select } from 'antd';
import Icon from '@ant-design/icons';
import WarningFilled from '../assets/warning-filled.svg';
Expand Down Expand Up @@ -31,7 +31,7 @@ import { changeLocatorElement } from '../reducers/changeLocatorElement.thunk';
import { addCustomLocator } from '../reducers/addCustomLocator.thunk';
import { selectPresentLocatorsByPO } from '../selectors/locatorsByPO.selectors';
import { LocatorMessageForDuplicate } from './LocatorMessageForDuplicate';
import { createLocatorTypeOptions } from '../utils/createLocatorTypeOptions';
import { ILocatorTypeOptions, createLocatorTypeOptions } from '../utils/createLocatorTypeOptions';
import { validateLocator } from '../utils/locatorValidation';

interface Props extends ILocator {
Expand Down Expand Up @@ -258,7 +258,19 @@ export const LocatorEditDialog: React.FC<Props> = ({

const isLocatorDisabled = form.getFieldValue('locator') === CALCULATING;

const locatorTypeOptions = createLocatorTypeOptions(locatorValue, locators, element_id);
const [locatorTypeOptions, setLocatorTypeOptions] = useState<ILocatorTypeOptions[]>([]);
useEffect(() => {
const fetchLocatorTypeOptions = async () => {
try {
const options = await createLocatorTypeOptions(locatorValue);
setLocatorTypeOptions(options);
} catch (error) {
console.error('Error: can`t get options for locator:', error);
}
};

void fetchLocatorTypeOptions();
}, [locatorValue, locators, element_id]);

const handleLocatorDropdownOnChange = async () => {
setValidationMessage('');
Expand Down
108 changes: 34 additions & 74 deletions src/features/locators/utils/createLocatorTypeOptions.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
import React from 'react';

import { ILocator, LocatorValue } from '../types/locator.types';
import { LocatorValue } from '../types/locator.types';
import {
ElementAttributes,
ExtendedElementAttributes,
LocatorType,
locatorAttributesInitialState,
} from '../../../common/types/common';
import { mergeObjects } from './mergeObjects';
import { Tooltip } from 'antd';
import { evaluateLocator } from './utils';

interface IOptionsWithLabel {
label: React.JSX.Element;
value: string;
disabled?: boolean;
}

interface ILocatorTypeOptions {
export interface ILocatorTypeOptions {
label: string;
options: IOptionsWithLabel[];
}

const generateOptionsWithLabel = (attributes: ElementAttributes): IOptionsWithLabel[] => {
const generateLabel = (locatorType: string, attribute: string | null) => {
if (attribute === null || attribute === '') {
return <Tooltip title="Disabled because no data">{locatorType}</Tooltip>;
return (
<Tooltip title="Disabled because no data">
<span style={{ color: 'rgba(0, 0, 0, 0.45)' }}>{locatorType}</span>
</Tooltip>
);
}

return (
Expand Down Expand Up @@ -96,104 +102,58 @@ const getLocatorTypeOptions = (
];
};

const createHashMap = (locators: LocatorValue[]): Map<string, Set<string | null>> => {
const hashMap = new Map<string, Set<string | null>>();

const updateHashMap = (key: string, value: string | null) => {
if (!hashMap.has(key)) {
hashMap.set(key, new Set<string | null>());
}

const valueSet = hashMap.get(key);
if (value !== null && value !== undefined) {
valueSet?.add(value);
}
};

locators.forEach((locator) => {
const attributes = locator.attributes;

if (attributes) {
Object.keys(attributes).forEach((key) => {
if (key === 'dataAttributes' && attributes.dataAttributes) {
const dataAttributes = attributes.dataAttributes;
Object.keys(dataAttributes).forEach((dataKey) => {
const value = dataAttributes[dataKey];
updateHashMap(dataKey, value);
});
} else {
const value = attributes[key as keyof Omit<ElementAttributes, 'dataAttributes'>];
if (value) updateHashMap(key, value);
else {
throw new Error(`The key value of the ${key} value is undefined!`);
}
}
});
}
});

return hashMap;
};

const splitUniqueAndNonUniqueAttributes = (
attributes: ElementAttributes,
attributesHashMap: Map<string, Set<string | null>>,
): ElementAttributes[] => {
const splitUniqueAndNonUniqueAttributes = async (attributes: ElementAttributes): Promise<ElementAttributes[]> => {
const uniqueAttributes: ElementAttributes = {};
const nonUniqueAttributes: ElementAttributes = {};

Object.entries(attributes).forEach(([key, value]) => {
for (const [key, value] of Object.entries(attributes)) {
const keyType = key as keyof ElementAttributes;
const isValueEmpty = value === '' || value === null || value === undefined;

if (keyType === 'dataAttributes' && value) {
// Check and unpacked data-attributes:
const dataAttributes: { [key: string]: string | null } = value;
const uniqueDataAttributes: { [key: string]: string | null } = {};
const nonUniqueDataAttributes: { [key: string]: string | null } = {};

Object.entries(dataAttributes).forEach(([dataKey, dataValue]) => {
const isDataValueEmpty = dataValue === '' || dataValue === null || dataValue === undefined;
const isNonUnique =
isDataValueEmpty || !attributesHashMap.has(dataKey) || !attributesHashMap.get(dataKey)?.has(dataValue);
if (isNonUnique) {
for (const [dataKey, dataValue] of Object.entries(dataAttributes)) {
if (dataValue === '' || dataValue === null || dataValue === undefined) {
nonUniqueDataAttributes[dataKey] = dataValue;
} else {
uniqueDataAttributes[dataKey] = dataValue;
const res = JSON.parse(await evaluateLocator(dataValue, dataKey as LocatorType));
const isDataAttributeUnique = res.length === 1;

if (isDataAttributeUnique) {
uniqueDataAttributes[dataKey] = dataValue;
} else {
nonUniqueDataAttributes[dataKey] = dataValue;
}
}
});
}

if (Object.keys(uniqueDataAttributes).length > 0) {
mergeObjects(uniqueAttributes, uniqueDataAttributes);
}

if (Object.keys(nonUniqueDataAttributes).length > 0) {
mergeObjects(nonUniqueAttributes, nonUniqueDataAttributes);
}
} else {
const isNonUnique = isValueEmpty || !attributesHashMap.has(key) || !attributesHashMap.get(key)?.has(value);
if (isNonUnique) {
nonUniqueAttributes[keyType] = value;
} else {
} else if (!isValueEmpty) {
const res = JSON.parse(await evaluateLocator(value, key as LocatorType));
const isAttributeUnique = res.length === 1;

if (isAttributeUnique) {
uniqueAttributes[keyType] = value;
} else {
nonUniqueAttributes[keyType] = value;
}
} else {
nonUniqueAttributes[keyType] = value;
}
});
}

return [uniqueAttributes, nonUniqueAttributes];
};

export const createLocatorTypeOptions = (
locatorValue: LocatorValue,
locators: ILocator[],
currentElementId: string,
) => {
const preparedData: LocatorValue[] = locators
.filter((element) => element.element_id !== currentElementId)
.map((element) => element.locatorValue);

const hashMap: Map<string, Set<string | null>> = createHashMap(preparedData);
const optionsData = splitUniqueAndNonUniqueAttributes(locatorValue.attributes, hashMap);

export const createLocatorTypeOptions = async (locatorValue: LocatorValue) => {
const optionsData = await splitUniqueAndNonUniqueAttributes(locatorValue.attributes);
return getLocatorTypeOptions(optionsData, locatorValue.cssSelector, locatorValue.xPath);
};
2 changes: 1 addition & 1 deletion src/features/locators/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const prepareLocatorStringForEvaluation = (type: LocatorType, string: string): s
export const evaluateLocator = async (
locatorString: string,
locatorType: LocatorType,
elementId: ElementId,
elementId?: ElementId,
jdnHash?: string,
) => {
if (locatorType === LocatorType.xPath) return evaluateXpath(locatorString, elementId, jdnHash);
Expand Down
Loading