diff --git a/src/__tests__/locators/utils/__mocks__/locatorEscaped.mock.ts b/src/__tests__/locators/utils/__mocks__/locatorEscaped.mock.ts new file mode 100644 index 00000000..4e274c1d --- /dev/null +++ b/src/__tests__/locators/utils/__mocks__/locatorEscaped.mock.ts @@ -0,0 +1,65 @@ +export const locatorMocks = [ + { + input: + '"//*[@data-nav-tracking-menu-navigate-down=\'[{"method":"submitEvent","dataContainer":{"nav_MenuAction":"down","nav_MenuRank":2,"nav_MenuLevel":1,"nav_MenuTitle":"Damen-Mode","nav_MenuType":"curated","nav_GlobalNavigation":"damen-mode"}}]\']"', + output: + '"//*[@data-nav-tracking-menu-navigate-down=\\\'[{\\\\"method\\\\":\\\\"submitEvent\\\\",\\\\"dataContainer\\\\":{\\\\"nav_MenuAction\\\\":\\\\"down\\\\",\\\\"nav_MenuRank\\\\":2,\\\\"nav_MenuLevel\\\\":1,\\\\"nav_MenuTitle\\\\":\\\\"Damen-Mode\\\\",\\\\"nav_MenuType\\\\":\\\\"curated\\\\",\\\\"nav_GlobalNavigation\\\\":\\\\"damen-mode\\\\"}}]\\\']"', + }, + { + input: + '"[data-nav-tracking-menu-navigate-down=\'[{"method":"submitEvent","dataContainer":{"nav_MenuAction":"down","nav_MenuRank":2,"nav_MenuLevel":1,"nav_MenuTitle":"Damen-Mode","nav_MenuType":"curated","nav_GlobalNavigation":"damen-mode"}}]\']"', + output: + '"[data-nav-tracking-menu-navigate-down=\\\'[{\\\\"method\\\\":\\\\"submitEvent\\\\",\\\\"dataContainer\\\\":{\\\\"nav_MenuAction\\\\":\\\\"down\\\\",\\\\"nav_MenuRank\\\\":2,\\\\"nav_MenuLevel\\\\":1,\\\\"nav_MenuTitle\\\\":\\\\"Damen-Mode\\\\",\\\\"nav_MenuType\\\\":\\\\"curated\\\\",\\\\"nav_GlobalNavigation\\\\":\\\\"damen-mode\\\\"}}]\\\']"', + }, + { + input: + '@FindBy(xpath = "//*[@data-nav-tracking-menu-navigate-down=\'[{"method":"submitEvent","dataContainer":{"nav_MenuAction":"down","nav_MenuRank":2,"nav_MenuLevel":1,"nav_MenuTitle":"Damen-Mode","nav_MenuType":"curated","nav_GlobalNavigation":"damen-mode"}}]\']")', + output: + '@FindBy(xpath = "//*[@data-nav-tracking-menu-navigate-down=\\\'[{\\\\"method\\\\":\\\\"submitEvent\\\\",\\\\"dataContainer\\\\":{\\\\"nav_MenuAction\\\\":\\\\"down\\\\",\\\\"nav_MenuRank\\\\":2,\\\\"nav_MenuLevel\\\\":1,\\\\"nav_MenuTitle\\\\":\\\\"Damen-Mode\\\\",\\\\"nav_MenuType\\\\":\\\\"curated\\\\",\\\\"nav_GlobalNavigation\\\\":\\\\"damen-mode\\\\"}}]\\\']")', + }, + { + input: + '@FindBy(css = "[data-nav-tracking-menu-navigate-down=\'[{"method":"submitEvent","dataContainer":{"nav_MenuAction":"down","nav_MenuRank":2,"nav_MenuLevel":1,"nav_MenuTitle":"Damen-Mode","nav_MenuType":"curated","nav_GlobalNavigation":"damen-mode"}}]\']")', + output: + '@FindBy(css = "[data-nav-tracking-menu-navigate-down=\\\'[{\\\\"method\\\\":\\\\"submitEvent\\\\",\\\\"dataContainer\\\\":{\\\\"nav_MenuAction\\\\":\\\\"down\\\\",\\\\"nav_MenuRank\\\\":2,\\\\"nav_MenuLevel\\\\":1,\\\\"nav_MenuTitle\\\\":\\\\"Damen-Mode\\\\",\\\\"nav_MenuType\\\\":\\\\"curated\\\\",\\\\"nav_GlobalNavigation\\\\":\\\\"damen-mode\\\\"}}]\\\']")', + }, + { + input: + '@UI("//*[@data-nav-tracking-menu-navigate-down=\'[{"method":"submitEvent","dataContainer":{"nav_MenuAction":"down","nav_MenuRank":2,"nav_MenuLevel":1,"nav_MenuTitle":"Damen-Mode","nav_MenuType":"curated","nav_GlobalNavigation":"damen-mode"}}]\']")', + output: + '@UI("//*[@data-nav-tracking-menu-navigate-down=\\\'[{\\\\"method\\\\":\\\\"submitEvent\\\\",\\\\"dataContainer\\\\":{\\\\"nav_MenuAction\\\\":\\\\"down\\\\",\\\\"nav_MenuRank\\\\":2,\\\\"nav_MenuLevel\\\\":1,\\\\"nav_MenuTitle\\\\":\\\\"Damen-Mode\\\\",\\\\"nav_MenuType\\\\":\\\\"curated\\\\",\\\\"nav_GlobalNavigation\\\\":\\\\"damen-mode\\\\"}}]\\\']")', + }, + { + input: + '@UI("[data-nav-tracking-menu-navigate-down=\'[{"method":"submitEvent","dataContainer":{"nav_MenuAction":"down","nav_MenuRank":2,"nav_MenuLevel":1,"nav_MenuTitle":"Damen-Mode","nav_MenuType":"curated","nav_GlobalNavigation":"damen-mode"}}]\']")', + output: + '@UI("[data-nav-tracking-menu-navigate-down=\\\'[{\\\\"method\\\\":\\\\"submitEvent\\\\",\\\\"dataContainer\\\\":{\\\\"nav_MenuAction\\\\":\\\\"down\\\\",\\\\"nav_MenuRank\\\\":2,\\\\"nav_MenuLevel\\\\":1,\\\\"nav_MenuTitle\\\\":\\\\"Damen-Mode\\\\",\\\\"nav_MenuType\\\\":\\\\"curated\\\\",\\\\"nav_GlobalNavigation\\\\":\\\\"damen-mode\\\\"}}]\\\']")', + }, + + { + input: "\"//*[contains(text(), 'Jetzt: GenialCard + 25€-Gutschein!')]\"", + output: "\"//*[contains(text(), \\'Jetzt: GenialCard + 25€-Gutschein!\\')]\"", + }, + { + input: '"#\\35 8776894140b02029425a3e2 [style="display\\:\\ inline\\;"]"', + output: '"#\\\\\\\\35 8776894140b02029425a3e2 [style=\\\\"display\\\\\\\\:\\\\\\\\ inline\\\\\\\\;\\\\"]"', + }, + { + input: '"[jsname="NNJLud"]:nth-child(3) [role="menuitem"]"', + output: '"[jsname=\\\\"NNJLud\\\\"]:nth-child(3) [role=\\\\"menuitem\\\\"]"', + }, + { + input: "\"//*[@data-ved='0ahUKEwjs1sqi4JqAAxUUSkEAHfQZDjgQ4dUDCAk']\"", + output: "\"//*[@data-ved=\\'0ahUKEwjs1sqi4JqAAxUUSkEAHfQZDjgQ4dUDCAk\\']\"", + }, + { + input: '".tm-navigation-filters__option:nth-child(6) > [tabindex="-\\31 "]"', + output: '".tm-navigation-filters__option:nth-child(6) > [tabindex=\\\\"-\\\\\\\\31 \\\\"]"', + }, + { + input: + '@UI("#\\37 49046 .tm-article-snippet__hubs-item:nth-child(2) > .tm-article-snippet__hubs-item-link")\npublic Label cLabel1;', + output: + '@UI("#\\\\\\\\37 49046 .tm-article-snippet__hubs-item:nth-child(2) > .tm-article-snippet__hubs-item-link")\\npublic Label cLabel1;', + }, +]; diff --git a/src/__tests__/locators/utils/escapeLocator.test.ts b/src/__tests__/locators/utils/escapeLocator.test.ts new file mode 100644 index 00000000..a629858a --- /dev/null +++ b/src/__tests__/locators/utils/escapeLocator.test.ts @@ -0,0 +1,8 @@ +import { locatorMocks } from "./__mocks__/locatorEscaped.mock"; +import { escapeLocator } from "../../../common/utils/copyToClipboard"; + +test("escape symbols in locator", () => { + locatorMocks.forEach((locator) => { + expect(escapeLocator(locator.input)).toBe(locator.output); + }); +}); diff --git a/src/common/components/CopyButton.tsx b/src/common/components/CopyButton.tsx index 2711bf57..b72b4c1a 100644 --- a/src/common/components/CopyButton.tsx +++ b/src/common/components/CopyButton.tsx @@ -1,7 +1,7 @@ import { Button, Tooltip } from "antd"; import { Copy } from "phosphor-react"; import React, { useState } from "react"; -import { copyToClipboard } from "../utils/helpers"; +import { copyToClipboard } from "../utils/copyToClipboard"; import { CopyTitle } from "../types/common"; interface Props { diff --git a/src/common/utils/copyToClipboard.ts b/src/common/utils/copyToClipboard.ts new file mode 100644 index 00000000..1539ba58 --- /dev/null +++ b/src/common/utils/copyToClipboard.ts @@ -0,0 +1,38 @@ +export const escapeLocator = (locator: string) => { + let transformedText = locator.replace(/[\\'\n]/g, (match: string) => { + switch (match) { + case "\\": + return "\\\\\\\\"; + case "'": + return "\\'"; + case "\n": + return "\\n"; + default: + return match; + } + }); + const lastDoubleQuote = transformedText.lastIndexOf('"'); + const firstDoubleQuote = transformedText.indexOf('"'); + const beforeFirstDoubleQuote = transformedText.slice(0, firstDoubleQuote + 1); + const afterLastDoubleQuote = transformedText.slice(lastDoubleQuote); + let insideOfDoubleQuotes = transformedText.slice(firstDoubleQuote + 1, lastDoubleQuote); + + if (insideOfDoubleQuotes.includes('"')) { + insideOfDoubleQuotes = insideOfDoubleQuotes.replace(/"/g, '\\\\"'); + transformedText = beforeFirstDoubleQuote + insideOfDoubleQuotes + afterLastDoubleQuote; + } + + return transformedText; +}; + +export const copyToClipboard = (value: string | string[]) => { + let transformedText; + + if (typeof value === "string") { + transformedText = escapeLocator(value); + } else { + transformedText = value.map((el: string) => escapeLocator(el)).join("\\n\\n"); + } + + chrome.devtools.inspectedWindow.eval(`copy('${transformedText}')`); +}; diff --git a/src/common/utils/helpers.ts b/src/common/utils/helpers.ts index a8332a79..0ffb3c47 100644 --- a/src/common/utils/helpers.ts +++ b/src/common/utils/helpers.ts @@ -7,18 +7,6 @@ export const floatToPercent = (value: number) => { return Math.trunc(value * 100); }; -export const copyToClipboard = (text: string) => { - // "\\\\3" - needed to get "\3" in 'eval()' - const transformedText = text - .replace(/'/g, "\\'") - .replace(/\n/g, "\\n") - .replace(/#\\3/g, "#\\\\3") - .replace(/=\\'\\3/g, "=\\'\\\\3") // two different cases for \\3 to avoid affecting something else... - .replace(/\\/g, "\\\\") - .replace(/"/g, '\\"'); - chrome.devtools.inspectedWindow.eval(`copy('${transformedText}')`); -}; - export const getLocatorString = (locator: LocatorValue, type: ElementLibrary | ElementClass, name: string): string => `@UI("${locator.output}")\npublic ${type} ${name};`; diff --git a/src/features/locators/components/LocatorCopyButton.jsx b/src/features/locators/components/LocatorCopyButton.jsx index 5c32c94e..72a115e5 100644 --- a/src/features/locators/components/LocatorCopyButton.jsx +++ b/src/features/locators/components/LocatorCopyButton.jsx @@ -1,7 +1,7 @@ import { Button, Tooltip } from "antd"; import { CopySimple } from "phosphor-react"; import React, { useState } from "react"; -import { copyToClipboard, getLocatorString } from "../../../common/utils/helpers"; +import { copyToClipboard, getLocatorString } from "../../../common/utils/copyToClipboard"; import { CopyTitle } from "../../../common/types/common"; export const LocatorCopyButton = ({ element }) => { diff --git a/src/features/locators/utils/utils.ts b/src/features/locators/utils/utils.ts index 617c1e67..1fbb0661 100644 --- a/src/features/locators/utils/utils.ts +++ b/src/features/locators/utils/utils.ts @@ -12,11 +12,12 @@ import { ElementId, JDNHash, } from "../types/locator.types"; -import { copyToClipboard, getLocatorString, getElementFullXpath } from "../../../common/utils/helpers"; +import { getLocatorString, getElementFullXpath } from "../../../common/utils/helpers"; import { LocatorOption } from "./constants"; import { LocatorType } from "../../../common/types/common"; import { isStringContainsNumbers } from "../../../common/utils/helpers"; import { FormInstance } from "antd/es/form/Form"; +import { copyToClipboard } from "../../../common/utils/copyToClipboard"; export const getLocatorWithJDIAnnotation = (locator: string): string => `@UI("${locator}")`; diff --git a/src/features/pageObjects/components/PageObjCopyButton.tsx b/src/features/pageObjects/components/PageObjCopyButton.tsx index 2b282f62..c8af465d 100644 --- a/src/features/pageObjects/components/PageObjCopyButton.tsx +++ b/src/features/pageObjects/components/PageObjCopyButton.tsx @@ -1,10 +1,11 @@ import React, { FC, MouseEvent, useState } from "react"; import { Button, Tooltip } from "antd"; -import { copyToClipboard, getLocatorString } from "../../../common/utils/helpers"; +import { getLocatorString } from "../../../common/utils/helpers"; import { CopySimple } from "phosphor-react"; import { Locator } from "../../locators/types/locator.types"; import { CopyTitle } from "../../../common/types/common"; +import { copyToClipboard } from "../../../common/utils/copyToClipboard"; interface Props { elements: Locator[]; @@ -14,7 +15,7 @@ export const PageObjCopyButton: FC = ({ elements }) => { const [copyTooltipTitle, setTooltipTitle] = useState(CopyTitle.Copy); const getPageObjectForCopying = (locators: Locator[]) => { - return locators.map(({ locator, type, name }) => getLocatorString(locator, type, name)).join("\n\n"); + return locators.map(({ locator, type, name }) => getLocatorString(locator, type, name)); }; const handleCopy = (e: MouseEvent) => { diff --git a/src/pageServices/contentScripts/generationData.js b/src/pageServices/contentScripts/generationData.js index e9ceb248..7e3c27da 100644 --- a/src/pageServices/contentScripts/generationData.js +++ b/src/pageServices/contentScripts/generationData.js @@ -107,15 +107,14 @@ export const getGenerationAttributes = () => { const isSelectorByGeneratorString = typeof selectorByGenerator === "string"; const isSelectorByFinderString = typeof selectorByFinder === "string"; - const transformSelector = (selector) => selector.replace(/"/g, "'"); if (isSelectorByGeneratorString && isSelectorByFinderString) { const selector = selectorByGenerator.length < selectorByFinder.length ? selectorByGenerator : selectorByFinder; - return transformSelector(selector); + return selector; } else if (!isSelectorByFinderString && isSelectorByGeneratorString) { - return transformSelector(selectorByGenerator); + return selectorByGenerator; } else if (!isSelectorByGeneratorString && isSelectorByFinderString) { - return transformSelector(selectorByFinder); + return selectorByFinder; } else { return "CSS selector generation was failed"; }