Skip to content

Commit

Permalink
Merge pull request #1732 from jdi-testing/issue_1284-get-css-locators…
Browse files Browse the repository at this point in the history
…-from-backend

Issue 1284: get css locators from backend
  • Loading branch information
Iogsotot authored May 13, 2024
2 parents 214d77a + 10702f5 commit eed609a
Show file tree
Hide file tree
Showing 49 changed files with 639 additions and 371 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@ export const selectMockedLocators = (pageObject: PageObject) => [
elemText: 'EPAM framework Wishes…',
elemAriaLabel: null,
locatorValue: {
taskStatus: 'SUCCESS',
cssSelectorStatus: 'SUCCESS',
xPathStatus: 'SUCCESS',
xPath: "//*[@class='main-content']",
Expand All @@ -180,7 +179,6 @@ export const selectMockedLocators = (pageObject: PageObject) => [
elemAriaLabel: null,
locatorType: pageObject.locatorType || LocatorType.xPath,
locatorValue: {
taskStatus: 'PENDING',
xPathStatus: 'SUCCESS',
cssSelectorStatus: 'PENDING',
xPath: "//*[@class='uui-navigation nav navbar-nav m-l8 any']",
Expand Down Expand Up @@ -212,7 +210,6 @@ export const selectMockedLocators = (pageObject: PageObject) => [
elemText: 'HTML 5',
elemAriaLabel: null,
locatorValue: {
taskStatus: 'FAILURE',
xPathStatus: 'SUCCESS',
cssSelectorStatus: 'FAILURE',
xPath: "//*[@index='5']/ul",
Expand Down
2 changes: 1 addition & 1 deletion src/app/reducers/defineServer.thunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const defineServer = createAsyncThunk('main/defineServer', async () => {
request.then((response) => {
const [major, minor, build] = response.data.split('.').map(toInteger);
if (compatibleMajorVer === major && compatibleMinorVer === minor && compatibleBuildVer <= build) {
return JSON.parse(JSON.stringify(response));
return JSON.parse(JSON.stringify(response)); // Deep copy
} else if (isRemote) {
throw new Error(BackendStatus.IncompatibleVersionRemote);
} else if (major < compatibleMajorVer || minor < compatibleMinorVer || build < compatibleBuildVer) {
Expand Down
2 changes: 2 additions & 0 deletions src/common/types/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export enum LocatorType {
dataAttributes = 'dataAttributes',
}

export type GeneralLocatorType = LocatorType.cssSelector | LocatorType.xPath;

export const locatorTypes: { [key in LocatorType]: string } = {
[LocatorType.cssSelector]: 'CSS Selector',
[LocatorType.xPath]: 'xPath',
Expand Down
26 changes: 15 additions & 11 deletions src/common/utils/getFullDocumentWithStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ export const getFullDocumentWithStyles = async () => {
await waitForAllStylesToLoad();

const documentResult = await connector.attachContentScript(() => {
const minifyHTML = (outerHTML: string) => {
return outerHTML
.replace(/\s{2,}/g, ' ') // replace multiple spaces with one
.replace(/\s*(<[^>]+>)\s*/g, '$1') // remove spaces around tags
.replace(/>\s+</g, '><') // remove spaces between tags
.replace(/\n/g, ''); // remove line breaks
};

const removeScriptTags = (html: string): string => {
return html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
};

const fetchCSS = () => {
let allStyles = '';
for (let i = 0; i < document.styleSheets.length; i++) {
Expand All @@ -45,18 +57,10 @@ export const getFullDocumentWithStyles = async () => {
outerHTML = outerHTML.replace(/<link rel="stylesheet"[^>]+>/g, '');
outerHTML = outerHTML.replace('</head>', `<style>\n${stylesString}</style></head>`);

const scripts = [...document.scripts]
.map((script: HTMLScriptElement) => {
return script.src ? `<script src="${script.src}"></script>` : `<script>${script.innerHTML}</script>`;
})
.join('\n');

outerHTML = outerHTML.replace('</body>', `${scripts}</body>`);

return JSON.stringify({ outerHTML });
return removeScriptTags(minifyHTML(outerHTML));
});

const result = await documentResult[0].result;
const { outerHTML } = JSON.parse(result);
return JSON.stringify(outerHTML);

return result;
};
13 changes: 13 additions & 0 deletions src/common/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ export const getElementFullXpath = async (foundHash: string): Promise<string> =>
return fullXpath;
};

export const getElementOriginalCssSelector = async (foundHash: string): Promise<string> => {
let originalCssSelector = '';

await sendMessage
.getElementOriginalCssSelector(foundHash)
.then((res: string) => {
if (res) originalCssSelector = res;
})
.catch((err: Error) => err);

return originalCssSelector;
};

export const isFilteredSelect = (input: string, option: any) =>
(option?.value?.toString() ?? '').toLowerCase().includes(input.toLowerCase());

Expand Down
3 changes: 0 additions & 3 deletions src/common/utils/helpersTS.ts

This file was deleted.

3 changes: 1 addition & 2 deletions src/common/utils/removeNodesByAttribute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,5 @@ export const removeNodesByAttribute = (htmlString: string, attributeName: string
}
});

const res = document.documentElement.outerHTML.replace(/\\&quot;/g, '');
return JSON.stringify(res);
return document.documentElement.outerHTML.replace(/\\&quot;/g, '');
};
24 changes: 14 additions & 10 deletions src/common/utils/throttler.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,46 @@
import { Middleware } from '@reduxjs/toolkit';
import { type Middleware } from '@reduxjs/toolkit';
import { selectAreInProgress } from '../../features/locators/selectors/locatorsByPO.selectors';
import { CssSelectorsGenerationPayload, XpathMultipleGenerationPayload } from '../../services/webSoket.types';

class Throttler {
accumulatedArgs: any[] = [];
accumulatedArgs: (CssSelectorsGenerationPayload | XpathMultipleGenerationPayload)[] = [];

interval: NodeJS.Timeout | null = null;

constructor() {
this.accumulateAndThrottle = this.accumulateAndThrottle.bind(this);
}

accumulateAndThrottle(fn: (arg: any[]) => any) {
const debouncedFn = (args: any[]) => {
this.accumulatedArgs.push(...args);
accumulateAndThrottle(fn: (arg: CssSelectorsGenerationPayload | XpathMultipleGenerationPayload) => any) {
const debouncedFn = (args: CssSelectorsGenerationPayload | XpathMultipleGenerationPayload) => {
this.accumulatedArgs.push(args);
};

const throttledFn = () => {
if (this.accumulatedArgs.length === 0) return;

try {
fn(this.accumulatedArgs);
this.accumulatedArgs.forEach((arg) => fn(arg));
this.accumulatedArgs = [];
} catch (error) {
this.quitThrottler();
if (__DEV_ENVIRONMENT__) console.warn("Can't invoke throttled function:", error);
}
this.accumulatedArgs = [];
};

if (!this.interval) this.interval = setInterval(throttledFn, 500);

return (args: any) => {
return (args: CssSelectorsGenerationPayload | XpathMultipleGenerationPayload) => {
debouncedFn(args);
};
}

quitThrottler() {
if (this.interval) clearInterval(this.interval);
this.interval = null;
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
this.accumulatedArgs = [];
}
}
}

Expand Down
25 changes: 17 additions & 8 deletions src/features/locators/Locator.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
// ToDo fix naming according to naming-convention
/* eslint-disable @typescript-eslint/naming-convention */
import React, { useEffect, useRef, useState, useMemo, FC, useLayoutEffect } from 'react';
import { Checkbox, Button } from 'antd';
import React, { FC, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { Button, Checkbox } from 'antd';
import { DotsThree } from '@phosphor-icons/react';
import Text from 'antd/lib/typography/Text';
import { useDispatch, useSelector } from 'react-redux';

import { areChildrenChecked, isLocatorIndeterminate } from './selectors/locators.selectors';
import { isMacPlatform } from '../../common/utils/helpers';
import {
elementGroupUnsetActive,
elementSetActive,
elementUnsetActive,
elementGroupUnsetActive,
setActiveSingle,
toggleLocatorIsChecked,
setChildrenGeneration,
setChildrenIsChecked,
toggleElementGeneration,
setChildrenGeneration,
toggleLocatorIsChecked,
} from './locators.slice';

import { size } from 'lodash';
import { PageType } from '../../app/types/mainSlice.types';
import { RootState } from '../../app/store/store';
import { ILocator } from './types/locator.types';
import { ILocator, LocatorTaskStatus } from './types/locator.types';
import { SearchState } from './components/LocatorsTree';
import { LocatorEditDialog } from './components/LocatorEditDialog';
import { LocatorCopyButton } from './components/LocatorCopyButton';
Expand All @@ -41,7 +41,7 @@ import { OnboardingStep } from '../onboarding/constants';
import { useOnboardingContext } from '../onboarding/OnboardingProvider';

interface Props {
element: ILocator;
element: ILocator & { locatorTaskStatus: LocatorTaskStatus | null };
currentPage: PageType;
disabled?: boolean;
searchState?: SearchState;
Expand All @@ -67,6 +67,7 @@ export const Locator: FC<Props> = ({ element, currentPage, searchState, depth, s
isChecked,
annotationType: elementAnnotationType,
locatorType: elementLocatorType,
locatorTaskStatus,
} = element;

const currentPageObject = useSelector(selectCurrentPageObject);
Expand Down Expand Up @@ -211,7 +212,15 @@ export const Locator: FC<Props> = ({ element, currentPage, searchState, depth, s
searchState === SearchState.Hidden ? ' jdn__locator--disabled' : ''
}`}
>
<LocatorIcon {...{ message: elementMessage, locatorValue, deleted, isCustomLocator }} />
<LocatorIcon
{...{
message: elementMessage,
locatorErrorMessage: locatorValue.errorMessage,
locatorTaskStatus,
deleted,
isCustomLocator,
}}
/>
{renderColorizedString()}
</Text>
{searchState !== SearchState.Hidden ? (
Expand Down
6 changes: 0 additions & 6 deletions src/features/locators/LocatorsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { Filter } from '../filter/Filter';
import { useCalculateHeaderSize } from './utils/useCalculateHeaderSize';
import { RootState } from '../../app/store/store';
import { IdentificationStatus } from './types/locator.types';
import { LocatorTreeSpinner } from './components/LocatorTreeSpinner';
import { removeAll as removeAllFilters, setFilter } from '../filter/filter.slice';
import { selectClassFilterByPO, selectIfUnselectedAll } from '../filter/filter.selectors';

Expand Down Expand Up @@ -53,9 +52,6 @@ const { confirm } = Modal;

export const LocatorsPage = () => {
const dispatch = useDispatch();
const showSpinner = useSelector(
(state: RootState) => state.locators.present.status === IdentificationStatus.preparing,
);
const locators = useSelector(selectFilteredLocators);
const areUnselectedAll = useSelector(selectIfUnselectedAll);
const locatorIds = useSelector(getLocatorsIdsByPO);
Expand Down Expand Up @@ -240,8 +236,6 @@ export const LocatorsPage = () => {
>
{locators.length || areUnselectedAll ? (
<LocatorsTree {...{ viewProps, locatorIds }} />
) : showSpinner ? (
<LocatorTreeSpinner />
) : (
<>
{isNoPageLocators && (
Expand Down
13 changes: 6 additions & 7 deletions src/features/locators/components/LocatorIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,25 @@ import { Spin, Tooltip } from 'antd';
import Icon from '@ant-design/icons';
import WarningEditedSvg from '../assets/warning-edited.svg';
import { PauseCircle, Trash, WarningCircle } from '@phosphor-icons/react';
import { LocatorValidationErrorType, LocatorValue, ValidationStatus, LocatorTaskStatus } from '../types/locator.types';
import { LocatorTaskStatus, LocatorValidationErrorType, ValidationStatus } from '../types/locator.types';
import { getLocatorValidationStatus } from '../utils/utils';

interface Props {
message: LocatorValidationErrorType;
locatorValue: LocatorValue;
locatorErrorMessage?: string;
locatorTaskStatus: LocatorTaskStatus | null;
deleted?: boolean;
isCustomLocator?: boolean;
}

export const LocatorIcon: React.FC<Props> = ({ message, locatorValue, deleted }) => {
export const LocatorIcon: React.FC<Props> = ({ message, locatorErrorMessage, locatorTaskStatus, deleted }) => {
const getTooltipText = () => message || 'Edited';

const startedIcon = <Spin size="small" />;
const revokedIcon = <PauseCircle size={14} color="#d81515" className="jdn__locator-icon_status" />;
const deletedIcon = <Trash size={14} color="#9a9da9" className="jdn__locator-icon_status" />;

const failureIcon = (
<Tooltip title={locatorValue.errorMessage ?? 'Locator generation was failed'}>
<Tooltip title={locatorErrorMessage ?? 'Locator generation was failed'}>
<WarningCircle size={14} color="#d81515" className="jdn__locator-icon_status" />
</Tooltip>
);
Expand All @@ -34,8 +34,7 @@ export const LocatorIcon: React.FC<Props> = ({ message, locatorValue, deleted })

const renderIcon = () => {
if (deleted) return deletedIcon;

switch (locatorValue.taskStatus) {
switch (locatorTaskStatus) {
case LocatorTaskStatus.SUCCESS: {
const validationStatus = getLocatorValidationStatus(message);
return validationStatus === ValidationStatus.WARNING || validationStatus === ValidationStatus.ERROR
Expand Down
7 changes: 6 additions & 1 deletion src/features/locators/components/LocatorsTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { fullEscapeLocatorString, checkForEscaped } from '../utils/escapeLocator
import { LocatorType } from '../../../common/types/common';
import type RcTree from 'rc-tree';
import cn from 'classnames';
import { getTaskStatus } from '../utils/utils';

export enum SearchState {
None = 'none',
Expand Down Expand Up @@ -119,6 +120,10 @@ export const LocatorsTree: React.FC<LocatorTreeProps> = ({ locatorIds, viewProps
_data.forEach((element, index) => {
const { element_id, children, parent_id, jdnHash, searchState, depth } = element;
const locator = locatorsMap[element_id];
const locatorTaskStatus = getTaskStatus(
locator.locatorValue.xPathStatus,
locator.locatorValue.cssSelectorStatus,
);

if (locator.locatorType === LocatorType.linkText && !checkForEscaped(locator.locatorValue.output)) {
locator.locatorValue.output = fullEscapeLocatorString(locator.locatorValue.output);
Expand All @@ -135,7 +140,7 @@ export const LocatorsTree: React.FC<LocatorTreeProps> = ({ locatorIds, viewProps
title: (
<Locator
{...{
element: locator,
element: { ...locator, locatorTaskStatus },
currentPage,
library,
depth,
Expand Down
Loading

0 comments on commit eed609a

Please sign in to comment.