diff --git a/server/src/workflow-management/selector.ts b/server/src/workflow-management/selector.ts index 8a9096ec..3f44a01e 100644 --- a/server/src/workflow-management/selector.ts +++ b/server/src/workflow-management/selector.ts @@ -1654,6 +1654,31 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates } } + if (element.parentElement) { + // Look for identical siblings + const siblings = Array.from(element.parentElement.children); + const identicalSiblings = siblings.filter(sibling => { + if (sibling === element) return false; + + let siblingSelector = sibling.tagName.toLowerCase(); + const siblingClassName = typeof sibling.className === 'string' ? sibling.className : ''; + if (siblingClassName) { + const siblingClasses = siblingClassName.split(/\s+/).filter(Boolean); + const validSiblingClasses = siblingClasses.filter(cls => !cls.startsWith('!') && !cls.includes(':')); + if (validSiblingClasses.length > 0) { + siblingSelector += '.' + validSiblingClasses.map(cls => CSS.escape(cls)).join('.'); + } + } + + return siblingSelector === selector; + }); + + if (identicalSiblings.length > 0) { + const position = siblings.indexOf(element) + 1; + selector += `:nth-child(${position})`; + } + } + return selector; } @@ -1894,6 +1919,31 @@ export const getNonUniqueSelectors = async (page: Page, coordinates: Coordinates } } + if (element.parentElement) { + // Look for identical siblings + const siblings = Array.from(element.parentElement.children); + const identicalSiblings = siblings.filter(sibling => { + if (sibling === element) return false; + + let siblingSelector = sibling.tagName.toLowerCase(); + const siblingClassName = typeof sibling.className === 'string' ? sibling.className : ''; + if (siblingClassName) { + const siblingClasses = siblingClassName.split(/\s+/).filter(Boolean); + const validSiblingClasses = siblingClasses.filter(cls => !cls.startsWith('!') && !cls.includes(':')); + if (validSiblingClasses.length > 0) { + siblingSelector += '.' + validSiblingClasses.map(cls => CSS.escape(cls)).join('.'); + } + } + + return siblingSelector === selector; + }); + + if (identicalSiblings.length > 0) { + const position = siblings.indexOf(element) + 1; + selector += `:nth-child(${position})`; + } + } + return selector; } @@ -2025,6 +2075,31 @@ export const getChildSelectors = async (page: Page, parentSelector: string): Pro } } + if (element.parentElement) { + // Look for identical siblings + const siblings = Array.from(element.parentElement.children); + const identicalSiblings = siblings.filter(sibling => { + if (sibling === element) return false; + + let siblingSelector = sibling.tagName.toLowerCase(); + const siblingClassName = typeof sibling.className === 'string' ? sibling.className : ''; + if (siblingClassName) { + const siblingClasses = siblingClassName.split(/\s+/).filter(Boolean); + const validSiblingClasses = siblingClasses.filter(cls => !cls.startsWith('!') && !cls.includes(':')); + if (validSiblingClasses.length > 0) { + siblingSelector += '.' + validSiblingClasses.map(cls => CSS.escape(cls)).join('.'); + } + } + + return siblingSelector === selector; + }); + + if (identicalSiblings.length > 0) { + const position = siblings.indexOf(element) + 1; + selector += `:nth-child(${position})`; + } + } + return selector; } diff --git a/src/components/browser/BrowserWindow.tsx b/src/components/browser/BrowserWindow.tsx index 9f62a22d..ffeb0df5 100644 --- a/src/components/browser/BrowserWindow.tsx +++ b/src/components/browser/BrowserWindow.tsx @@ -263,7 +263,12 @@ export const BrowserWindow = () => { } if (getList === true && !listSelector) { - setListSelector(highlighterData.selector); + let cleanedSelector = highlighterData.selector; + if (cleanedSelector.includes('nth-child')) { + cleanedSelector = cleanedSelector.replace(/:nth-child\(\d+\)/g, ''); + } + + setListSelector(cleanedSelector); notify(`info`, t('browser_window.attribute_modal.notifications.list_select_success')); setCurrentListId(Date.now()); setFields({}); @@ -275,13 +280,25 @@ export const BrowserWindow = () => { // Add fields to the list if (options.length === 1) { const attribute = options[0].value; + let currentSelector = highlighterData.selector; + + if (currentSelector.includes('>')) { + const [firstPart, ...restParts] = currentSelector.split('>').map(p => p.trim()); + const listSelectorRightPart = listSelector.split('>').pop()?.trim().replace(/:nth-child\(\d+\)/g, ''); + + if (firstPart.includes('nth-child') && + firstPart.replace(/:nth-child\(\d+\)/g, '') === listSelectorRightPart) { + currentSelector = `${firstPart.replace(/:nth-child\(\d+\)/g, '')} > ${restParts.join(' > ')}`; + } + } + const newField: TextStep = { id: Date.now(), type: 'text', label: `Label ${Object.keys(fields).length + 1}`, data: data, selectorObj: { - selector: highlighterData.selector, + selector: currentSelector, tag: highlighterData.elementInfo?.tagName, shadow: highlighterData.elementInfo?.isShadowRoot, attribute