From 3c0f7900a4e05fa19693af87645a61e572bb153e Mon Sep 17 00:00:00 2001 From: Rohit Date: Wed, 29 Jan 2025 17:41:59 +0530 Subject: [PATCH 01/18] fix: register type actions correctly --- .../src/workflow-management/classes/Generator.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 6e36f287..a148e53b 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -22,7 +22,7 @@ import { getBestSelectorForAction } from "../utils"; import { browserPool } from "../../server"; import { uuid } from "uuidv4"; import { capture } from "../../utils/analytics" -import { encrypt } from "../../utils/auth"; +import { decrypt, encrypt } from "../../utils/auth"; interface PersistedGeneratedData { lastUsedSelector: string; @@ -1062,14 +1062,18 @@ export class WorkflowGenerator { if (condition.args && condition.args[1]) { if (!input.selector) { input.selector = condition.args[0]; + input.type = condition.args[2] } + if (input.selector === condition.args[0]) { input.actionCounter++; - if (condition.args[1].length === 1) { - input.value = input.value + condition.args[1]; - } else if (condition.args[1] === 'Backspace') { + const decryptedKey = decrypt(condition.args[1]); + + if (decryptedKey.length === 1) { + input.value += decryptedKey; + } else if (decryptedKey === 'Backspace') { input.value = input.value.slice(0, -1); - } else if (condition.args[1] !== 'Shift') { + } else if (decryptedKey !== 'Shift') { pushTheOptimizedAction(pair, index); pair.what.splice(index + 1, 0, { action: 'waitForLoadState', @@ -1081,7 +1085,7 @@ export class WorkflowGenerator { pushTheOptimizedAction(pair, index); input = { selector: condition.args[0], - value: condition.args[1], + value: decrypt(condition.args[1]), type: condition.args[2], actionCounter: 1, }; From 80356b5600b9900aff71e839b67d13dbd34655d6 Mon Sep 17 00:00:00 2001 From: Rohit Date: Mon, 3 Feb 2025 17:29:47 +0530 Subject: [PATCH 02/18] feat: add cursor position on click action --- .../workflow-management/classes/Generator.ts | 176 +++++++++++++----- 1 file changed, 128 insertions(+), 48 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index a148e53b..edb19b2a 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -429,25 +429,38 @@ export class WorkflowGenerator { if ((elementInfo?.tagName === 'INPUT' || elementInfo?.tagName === 'TEXTAREA') && selector) { // Calculate the exact position within the element - const elementPos = await page.evaluate((selector) => { + const positionAndCursor = await page.evaluate((selector) => { const element = document.querySelector(selector); if (!element) return null; + + // Get element position for the relative click coordinates const rect = element.getBoundingClientRect(); + + // Get cursor position index + let cursorIndex = 0; + if ('selectionStart' in element) { + // selectionStart gives us the exact index where the cursor is + cursorIndex = (element as HTMLInputElement | HTMLTextAreaElement).selectionStart || 0; + } + return { + rect: { x: rect.left, y: rect.top + }, + cursorIndex }; }, selector); - if (elementPos) { - const relativeX = coordinates.x - elementPos.x; - const relativeY = coordinates.y - elementPos.y; + if (positionAndCursor) { + const relativeX = coordinates.x - positionAndCursor.rect.x; + const relativeY = coordinates.y - positionAndCursor.rect.y; const pair: WhereWhatPair = { where, what: [{ action: 'click', - args: [selector, { position: { x: relativeX, y: relativeY } }] + args: [selector, { position: { x: relativeX, y: relativeY } }, { cursorIndex: positionAndCursor.cursorIndex }], }] }; @@ -1027,24 +1040,24 @@ export class WorkflowGenerator { * @param workflow The workflow to be optimized. */ private optimizeWorkflow = (workflow: WorkflowFile) => { - - // replace a sequence of press actions by a single fill action + // Enhanced input state to include cursor position let input = { selector: '', value: '', type: '', actionCounter: 0, + cursorPosition: -1 // Track cursor position, -1 means end of text }; - + const pushTheOptimizedAction = (pair: WhereWhatPair, index: number) => { if (input.value.length === 1) { - // when only one press action is present, keep it and add a waitForLoadState action + // Single character - keep as is with waitForLoadState pair.what.splice(index + 1, 0, { action: 'waitForLoadState', args: ['networkidle'], - }) + }); } else { - // when more than one press action is present, add a type action + // Multiple characters - optimize to type action pair.what.splice(index - input.actionCounter, input.actionCounter, { action: 'type', args: [input.selector, encrypt(input.value), input.type], @@ -1053,55 +1066,122 @@ export class WorkflowGenerator { args: ['networkidle'], }); } - } - - + }; + for (const pair of workflow.workflow) { - pair.what.forEach((condition, index) => { - if (condition.action === 'press') { - if (condition.args && condition.args[1]) { - if (!input.selector) { - input.selector = condition.args[0]; - input.type = condition.args[2] + for (let i = 0; i < pair.what.length; i++) { + const condition = pair.what[i]; + + // Handle click actions that set cursor position + if (condition.action === 'click' && condition.args?.[1]) { + const cursorIndex = condition.args[1].cursorIndex; + + // If we have pending input, commit it before processing the click + if (input.value.length > 0) { + pushTheOptimizedAction(pair, i); + input = { + selector: '', + value: '', + type: '', + actionCounter: 0, + cursorPosition: -1 + }; + } + + // Update cursor position for next operations + input.cursorPosition = cursorIndex; + continue; + } + + // Handle text input and editing + if (condition.action === 'press' && condition.args?.[1]) { + const [selector, encryptedKey, type] = condition.args; + const key = decrypt(encryptedKey); + + // Initialize new input state if selector changes + if (!input.selector || input.selector !== selector) { + if (input.value.length > 0) { + pushTheOptimizedAction(pair, i); } - - if (input.selector === condition.args[0]) { - input.actionCounter++; - const decryptedKey = decrypt(condition.args[1]); - - if (decryptedKey.length === 1) { - input.value += decryptedKey; - } else if (decryptedKey === 'Backspace') { - input.value = input.value.slice(0, -1); - } else if (decryptedKey !== 'Shift') { - pushTheOptimizedAction(pair, index); - pair.what.splice(index + 1, 0, { - action: 'waitForLoadState', - args: ['networkidle'], - }) - input = { selector: '', value: '', type: '', actionCounter: 0 }; - } + input = { + selector, + value: '', + type: type || 'text', + actionCounter: 0, + cursorPosition: -1 + }; + } + + input.actionCounter++; + + // Handle different key types with cursor awareness + if (key.length === 1) { + // Insert character at cursor position or append if no cursor set + if (input.cursorPosition === -1) { + // No cursor position set, append to end + input.value += key; } else { - pushTheOptimizedAction(pair, index); + // Insert at cursor position + input.value = + input.value.slice(0, input.cursorPosition) + + key + + input.value.slice(input.cursorPosition); + input.cursorPosition++; + } + } else if (key === 'Backspace') { + if (input.cursorPosition > 0) { + // Delete character before cursor + input.value = + input.value.slice(0, input.cursorPosition - 1) + + input.value.slice(input.cursorPosition); + input.cursorPosition--; + } else if (input.cursorPosition === -1 && input.value.length > 0) { + // No cursor position set, delete from end + input.value = input.value.slice(0, -1); + } + } else if (key !== 'Shift') { + // Handle other special keys + if (input.value.length > 0) { + pushTheOptimizedAction(pair, i); input = { - selector: condition.args[0], - value: decrypt(condition.args[1]), - type: condition.args[2], - actionCounter: 1, + selector: '', + value: '', + type: '', + actionCounter: 0, + cursorPosition: -1 }; } } } else { - if (input.value.length !== 0) { - pushTheOptimizedAction(pair, index); - // clear the input - input = { selector: '', value: '', type: '', actionCounter: 0 }; + // Handle non-text actions + if (input.value.length > 0) { + pushTheOptimizedAction(pair, i); + input = { + selector: '', + value: '', + type: '', + actionCounter: 0, + cursorPosition: -1 + }; } } - }); + } + + // Clean up any remaining input state + if (input.value.length > 0) { + pushTheOptimizedAction(pair, pair.what.length); + input = { + selector: '', + value: '', + type: '', + actionCounter: 0, + cursorPosition: -1 + }; + } } + return workflow; - } + }; /** * Returns workflow params from the stored metadata. From 9ff7813ad96d5527a442729fa8482c8b280db367 Mon Sep 17 00:00:00 2001 From: Rohit Date: Mon, 3 Feb 2025 18:13:45 +0530 Subject: [PATCH 03/18] feat: modify logic to gen cursor index --- .../workflow-management/classes/Generator.ts | 68 +++++++++++++------ 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index edb19b2a..5980628b 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -429,28 +429,54 @@ export class WorkflowGenerator { if ((elementInfo?.tagName === 'INPUT' || elementInfo?.tagName === 'TEXTAREA') && selector) { // Calculate the exact position within the element - const positionAndCursor = await page.evaluate((selector) => { - const element = document.querySelector(selector); - if (!element) return null; + const positionAndCursor = await page.evaluate( + ({ selector, coords }) => { + const element = document.querySelector(selector); + if (!element) return null; + + const getCursorPosition = (inputElement: HTMLInputElement | HTMLTextAreaElement, clickCoords: Coordinates) => { + const rect = inputElement.getBoundingClientRect(); + const clickX = clickCoords.x - rect.left; + + // Get the input's text content + const text = inputElement.value; + + // Create a temporary element to measure text + const measurer = document.createElement('span'); + measurer.style.font = window.getComputedStyle(inputElement).font; + measurer.style.position = 'absolute'; + measurer.style.whiteSpace = 'pre'; + measurer.style.visibility = 'hidden'; + document.body.appendChild(measurer); + + // Find the position where the click occurred + let position = 0; + for (let i = 0; i <= text.length; i++) { + measurer.textContent = text.slice(0, i); + const width = measurer.getBoundingClientRect().width; + if (width >= clickX) { + position = i; + break; + } + } + + document.body.removeChild(measurer); + return position; + }; - // Get element position for the relative click coordinates - const rect = element.getBoundingClientRect(); - - // Get cursor position index - let cursorIndex = 0; - if ('selectionStart' in element) { - // selectionStart gives us the exact index where the cursor is - cursorIndex = (element as HTMLInputElement | HTMLTextAreaElement).selectionStart || 0; - } - - return { - rect: { - x: rect.left, - y: rect.top - }, - cursorIndex - }; - }, selector); + const rect = element.getBoundingClientRect(); + const cursorIndex = getCursorPosition(element as HTMLInputElement | HTMLTextAreaElement, coords); + + return { + rect: { + x: rect.left, + y: rect.top + }, + cursorIndex + }; + }, + { selector, coords: coordinates } + ); if (positionAndCursor) { const relativeX = coordinates.x - positionAndCursor.rect.x; From f63ff72d4a8a1f393eab69cac86765d50bc564ae Mon Sep 17 00:00:00 2001 From: Rohit Date: Fri, 7 Feb 2025 17:32:00 +0530 Subject: [PATCH 04/18] feat: add selection start property to get cursor index --- .../workflow-management/classes/Generator.ts | 62 ++++++++++++------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 5980628b..38237312 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -435,32 +435,33 @@ export class WorkflowGenerator { if (!element) return null; const getCursorPosition = (inputElement: HTMLInputElement | HTMLTextAreaElement, clickCoords: Coordinates) => { - const rect = inputElement.getBoundingClientRect(); - const clickX = clickCoords.x - rect.left; + // const rect = inputElement.getBoundingClientRect(); + // const clickX = clickCoords.x - rect.left; - // Get the input's text content - const text = inputElement.value; + // // Get the input's text content + // const text = inputElement.value; - // Create a temporary element to measure text - const measurer = document.createElement('span'); - measurer.style.font = window.getComputedStyle(inputElement).font; - measurer.style.position = 'absolute'; - measurer.style.whiteSpace = 'pre'; - measurer.style.visibility = 'hidden'; - document.body.appendChild(measurer); + // // Create a temporary element to measure text + // const measurer = document.createElement('span'); + // measurer.style.font = window.getComputedStyle(inputElement).font; + // measurer.style.position = 'absolute'; + // measurer.style.whiteSpace = 'pre'; + // measurer.style.visibility = 'hidden'; + // document.body.appendChild(measurer); - // Find the position where the click occurred - let position = 0; - for (let i = 0; i <= text.length; i++) { - measurer.textContent = text.slice(0, i); - const width = measurer.getBoundingClientRect().width; - if (width >= clickX) { - position = i; - break; - } - } + // // Find the position where the click occurred + // let position = 0; + // for (let i = 0; i <= text.length; i++) { + // measurer.textContent = text.slice(0, i); + // const width = measurer.getBoundingClientRect().width; + // if (width >= clickX) { + // position = i; + // break; + // } + // } - document.body.removeChild(measurer); + // document.body.removeChild(measurer); + const position = inputElement.selectionStart || 0; return position; }; @@ -740,6 +741,23 @@ export class WorkflowGenerator { * @returns {Promise} */ public saveNewWorkflow = async (fileName: string, userId: number, isLogin: boolean) => { + for (const pair of this.workflowRecord.workflow) { + for (let i = 0; i < pair.what.length; i++) { + const condition = pair.what[i]; + + if (condition.action === 'press' && condition.args) { + const [selector, encryptedKey, type] = condition.args; + const key = decrypt(encryptedKey); + + console.log(`Selector: ${selector}, Key: ${key}`); + } + + if (condition.action === 'click' && condition.args) { + console.log("Click args: ", condition.args); + } + } + } + const recording = this.optimizeWorkflow(this.workflowRecord); try { this.recordingMeta = { From 2199149827625be50a20c63428e37dc26db26fcc Mon Sep 17 00:00:00 2001 From: Rohit Date: Fri, 7 Feb 2025 17:33:12 +0530 Subject: [PATCH 05/18] feat: rm previous cursor logic --- .../workflow-management/classes/Generator.ts | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index 38237312..ff36bed9 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -435,32 +435,6 @@ export class WorkflowGenerator { if (!element) return null; const getCursorPosition = (inputElement: HTMLInputElement | HTMLTextAreaElement, clickCoords: Coordinates) => { - // const rect = inputElement.getBoundingClientRect(); - // const clickX = clickCoords.x - rect.left; - - // // Get the input's text content - // const text = inputElement.value; - - // // Create a temporary element to measure text - // const measurer = document.createElement('span'); - // measurer.style.font = window.getComputedStyle(inputElement).font; - // measurer.style.position = 'absolute'; - // measurer.style.whiteSpace = 'pre'; - // measurer.style.visibility = 'hidden'; - // document.body.appendChild(measurer); - - // // Find the position where the click occurred - // let position = 0; - // for (let i = 0; i <= text.length; i++) { - // measurer.textContent = text.slice(0, i); - // const width = measurer.getBoundingClientRect().width; - // if (width >= clickX) { - // position = i; - // break; - // } - // } - - // document.body.removeChild(measurer); const position = inputElement.selectionStart || 0; return position; }; From 206d760dd13c2561441326b9a7902e9237bdc209 Mon Sep 17 00:00:00 2001 From: Rohit Date: Wed, 12 Feb 2025 22:00:51 +0530 Subject: [PATCH 06/18] feat: accurately get cursor index value for input field --- .../workflow-management/classes/Generator.ts | 94 ++++++++++++++++--- 1 file changed, 81 insertions(+), 13 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index ff36bed9..bd2233ba 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -431,27 +431,95 @@ export class WorkflowGenerator { // Calculate the exact position within the element const positionAndCursor = await page.evaluate( ({ selector, coords }) => { - const element = document.querySelector(selector); - if (!element) return null; + const getCursorPosition = (element: any, clickX: any) => { + // Get the input's text content + const text = element.value; - const getCursorPosition = (inputElement: HTMLInputElement | HTMLTextAreaElement, clickCoords: Coordinates) => { - const position = inputElement.selectionStart || 0; - return position; - }; + // Create a temporary hidden div to measure text + const mirror = document.createElement('div'); + + // Copy ALL relevant styles that could affect text measurement + const style = window.getComputedStyle(element); + mirror.style.cssText = ` + font: ${style.font}; + line-height: ${style.lineHeight}; + padding: ${style.padding}; + border: ${style.border}; + box-sizing: ${style.boxSizing}; + white-space: ${style.whiteSpace}; + overflow-wrap: ${style.overflowWrap}; + position: absolute; + top: -9999px; + left: -9999px; + width: ${element.offsetWidth}px; + `; + + document.body.appendChild(mirror); + + // Get the element's padding and border widths + const paddingLeft = parseFloat(style.paddingLeft); + const borderLeft = parseFloat(style.borderLeftWidth); + + // Adjust clickX to account for padding and border + const adjustedClickX = clickX - (paddingLeft + borderLeft); + + let bestIndex = 0; + let bestDiff = Infinity; + + // Try each possible cursor position + for (let i = 0; i <= text.length; i++) { + // Create a span for the text before cursor + const textBeforeCursor = text.substring(0, i); + const span = document.createElement('span'); + span.textContent = textBeforeCursor; + mirror.innerHTML = ''; + mirror.appendChild(span); + + // Get the x-position where this character would end + const textWidth = span.getBoundingClientRect().width; + + // Calculate distance from adjusted click to this position + const diff = Math.abs(adjustedClickX - textWidth); + + // If this position is closer to the click, update bestIndex + if (diff < bestDiff) { + bestIndex = i; + bestDiff = diff; + } + } + + // Clean up + document.body.removeChild(mirror); + + // Add debug logging + console.log({ + text, + clickX, + adjustedClickX, + bestIndex, + value: text.substring(0, bestIndex), + nextChar: text[bestIndex] || 'EOL' + }); + + return bestIndex; + }; + + const element = document.querySelector(selector) as HTMLInputElement | HTMLTextAreaElement; + if (!element) return null; const rect = element.getBoundingClientRect(); - const cursorIndex = getCursorPosition(element as HTMLInputElement | HTMLTextAreaElement, coords); + const relativeX = coords.x - rect.left; return { - rect: { - x: rect.left, - y: rect.top - }, - cursorIndex + rect: { + x: rect.left, + y: rect.top + }, + cursorIndex: getCursorPosition(element, relativeX) }; }, { selector, coords: coordinates } - ); + ); if (positionAndCursor) { const relativeX = coordinates.x - positionAndCursor.rect.x; From 945f1200c4d02a168e6996e794f74d65a5d04783 Mon Sep 17 00:00:00 2001 From: Rohit Date: Wed, 12 Feb 2025 22:37:40 +0530 Subject: [PATCH 07/18] feat: optimize press actions to type actions --- .../workflow-management/classes/Generator.ts | 201 ++++++++---------- 1 file changed, 86 insertions(+), 115 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index bd2233ba..ff556359 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -42,6 +42,13 @@ interface MetaData { isLogin?: boolean; } +interface InputState { + selector: string; + value: string; + type: string; + cursorPosition: number; +} + /** * Workflow generator is used to transform the user's interactions into an automatically * generated correct workflows, using the ability of internal state persistence and @@ -1126,56 +1133,33 @@ export class WorkflowGenerator { * @param workflow The workflow to be optimized. */ private optimizeWorkflow = (workflow: WorkflowFile) => { - // Enhanced input state to include cursor position - let input = { - selector: '', - value: '', - type: '', - actionCounter: 0, - cursorPosition: -1 // Track cursor position, -1 means end of text - }; - - const pushTheOptimizedAction = (pair: WhereWhatPair, index: number) => { - if (input.value.length === 1) { - // Single character - keep as is with waitForLoadState - pair.what.splice(index + 1, 0, { - action: 'waitForLoadState', - args: ['networkidle'], - }); - } else { - // Multiple characters - optimize to type action - pair.what.splice(index - input.actionCounter, input.actionCounter, { - action: 'type', - args: [input.selector, encrypt(input.value), input.type], - }, { - action: 'waitForLoadState', - args: ['networkidle'], - }); - } - }; + // Track state for each input field + const inputStates = new Map(); + // First pass: Process all actions and build final states for (const pair of workflow.workflow) { - for (let i = 0; i < pair.what.length; i++) { - const condition = pair.what[i]; + let currentIndex = 0; + + while (currentIndex < pair.what.length) { + const condition = pair.what[currentIndex]; - // Handle click actions that set cursor position - if (condition.action === 'click' && condition.args?.[1]) { - const cursorIndex = condition.args[1].cursorIndex; + // Handle click actions with cursor positioning + if (condition.action === 'click' && condition.args?.[2]?.cursorIndex !== undefined) { + const selector = condition.args[0]; + const cursorIndex = condition.args[2].cursorIndex; - // If we have pending input, commit it before processing the click - if (input.value.length > 0) { - pushTheOptimizedAction(pair, i); - input = { - selector: '', - value: '', - type: '', - actionCounter: 0, - cursorPosition: -1 - }; - } + let state = inputStates.get(selector) || { + selector, + value: '', + type: 'text', + cursorPosition: -1 + }; - // Update cursor position for next operations - input.cursorPosition = cursorIndex; + state.cursorPosition = cursorIndex; + inputStates.set(selector, state); + + // Remove the click action + pair.what.splice(currentIndex, 1); continue; } @@ -1183,86 +1167,73 @@ export class WorkflowGenerator { if (condition.action === 'press' && condition.args?.[1]) { const [selector, encryptedKey, type] = condition.args; const key = decrypt(encryptedKey); + + let state = inputStates.get(selector) || { + selector, + value: '', + type: type || 'text', + cursorPosition: -1 + }; - // Initialize new input state if selector changes - if (!input.selector || input.selector !== selector) { - if (input.value.length > 0) { - pushTheOptimizedAction(pair, i); - } - input = { - selector, - value: '', - type: type || 'text', - actionCounter: 0, - cursorPosition: -1 - }; - } - - input.actionCounter++; - - // Handle different key types with cursor awareness if (key.length === 1) { - // Insert character at cursor position or append if no cursor set - if (input.cursorPosition === -1) { - // No cursor position set, append to end - input.value += key; + if (state.cursorPosition === -1) { + state.value += key; } else { - // Insert at cursor position - input.value = - input.value.slice(0, input.cursorPosition) + + state.value = + state.value.slice(0, state.cursorPosition) + key + - input.value.slice(input.cursorPosition); - input.cursorPosition++; + state.value.slice(state.cursorPosition); + state.cursorPosition++; } } else if (key === 'Backspace') { - if (input.cursorPosition > 0) { - // Delete character before cursor - input.value = - input.value.slice(0, input.cursorPosition - 1) + - input.value.slice(input.cursorPosition); - input.cursorPosition--; - } else if (input.cursorPosition === -1 && input.value.length > 0) { - // No cursor position set, delete from end - input.value = input.value.slice(0, -1); + if (state.cursorPosition > 0) { + state.value = + state.value.slice(0, state.cursorPosition - 1) + + state.value.slice(state.cursorPosition); + state.cursorPosition--; + } else if (state.cursorPosition === -1 && state.value.length > 0) { + state.value = state.value.slice(0, -1); } - } else if (key !== 'Shift') { - // Handle other special keys - if (input.value.length > 0) { - pushTheOptimizedAction(pair, i); - input = { - selector: '', - value: '', - type: '', - actionCounter: 0, - cursorPosition: -1 - }; + } else if (key === 'Delete') { + if (state.cursorPosition >= 0 && state.cursorPosition < state.value.length) { + state.value = + state.value.slice(0, state.cursorPosition) + + state.value.slice(state.cursorPosition + 1); + } else if (state.cursorPosition === -1 && state.value.length > 0) { + // If no cursor position set, delete at the end + state.value = state.value.slice(0, -1); } } - } else { - // Handle non-text actions - if (input.value.length > 0) { - pushTheOptimizedAction(pair, i); - input = { - selector: '', - value: '', - type: '', - actionCounter: 0, - cursorPosition: -1 - }; - } - } - } - // Clean up any remaining input state - if (input.value.length > 0) { - pushTheOptimizedAction(pair, pair.what.length); - input = { - selector: '', - value: '', - type: '', - actionCounter: 0, - cursorPosition: -1 - }; + inputStates.set(selector, state); + + // Remove the press action + pair.what.splice(currentIndex, 1); + continue; + } + + currentIndex++; + } + } + + // Second pass: Add one type action per selector in the last pair where that selector was used + for (const [selector, state] of inputStates.entries()) { + if (state.value) { + // Find the last pair that used this selector + for (let i = workflow.workflow.length - 1; i >= 0; i--) { + const pair = workflow.workflow[i]; + + // Add type action to the end of the pair + pair.what.push({ + action: 'type', + args: [selector, encrypt(state.value), state.type] + }, { + action: 'waitForLoadState', + args: ['networkidle'] + }); + + break; // Stop after adding to the first pair we find + } } } From afe7c2fced6ae75f2d52792b37ebee4af41508fc Mon Sep 17 00:00:00 2001 From: Rohit Date: Wed, 12 Feb 2025 23:03:49 +0530 Subject: [PATCH 08/18] feat: maintain input type for fields --- .../workflow-management/classes/Generator.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index ff556359..fc732e60 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -1168,12 +1168,18 @@ export class WorkflowGenerator { const [selector, encryptedKey, type] = condition.args; const key = decrypt(encryptedKey); - let state = inputStates.get(selector) || { - selector, - value: '', - type: type || 'text', - cursorPosition: -1 - }; + let state = inputStates.get(selector); + if (!state) { + state = { + selector, + value: '', + type: type || 'text', // Use the type from the press action + cursorPosition: -1 + }; + } else { + // Update type from the press action if it exists + state.type = type || state.type; + } if (key.length === 1) { if (state.cursorPosition === -1) { From 6a989ad0e431804aa42d5d9e1bc25f44890820c4 Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 13 Feb 2025 11:14:38 +0530 Subject: [PATCH 09/18] feat: extract type action creds --- src/components/robot/RobotEdit.tsx | 64 ++++++------------------------ 1 file changed, 12 insertions(+), 52 deletions(-) diff --git a/src/components/robot/RobotEdit.tsx b/src/components/robot/RobotEdit.tsx index f1f79b77..12fd32bd 100644 --- a/src/components/robot/RobotEdit.tsx +++ b/src/components/robot/RobotEdit.tsx @@ -118,6 +118,7 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin useEffect(() => { if (robot?.recording?.workflow) { const extractedCredentials = extractInitialCredentials(robot.recording.workflow); + console.log(extractedCredentials); setCredentials(extractedCredentials); setCredentialGroups(groupCredentialsByType(extractedCredentials)); } @@ -126,74 +127,32 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin const extractInitialCredentials = (workflow: any[]): Credentials => { const credentials: Credentials = {}; - // Helper function to check if a character is printable - const isPrintableCharacter = (char: string): boolean => { - return char.length === 1 && !!char.match(/^[\x20-\x7E]$/); - }; - // Process each step in the workflow workflow.forEach(step => { if (!step.what) return; - // Keep track of the current input field being processed - let currentSelector = ''; - let currentValue = ''; - let currentType = ''; - // Process actions in sequence to maintain correct text state step.what.forEach((action: any) => { if ( - (action.action === 'type' || action.action === 'press') && + action.action === 'type' && action.args?.length >= 2 && typeof action.args[1] === 'string' ) { const selector: string = action.args[0]; - const character: string = action.args[1]; - const inputType: string = action.args[2] || ''; + const value: string = action.args[1]; + const type: string = action.args[2]; - // Detect `input[type="password"]` - if (!currentType && inputType.toLowerCase() === 'password') { - currentType = 'password'; + if (!credentials[selector]) { + credentials[selector] = { + value: '', + type: '' + }; } - // If we're dealing with a new selector, store the previous one - if (currentSelector && selector !== currentSelector) { - if (!credentials[currentSelector]) { - credentials[currentSelector] = { - value: currentValue, - type: currentType - }; - } else { - credentials[currentSelector].value = currentValue; - } - } - - // Update current tracking variables - if (selector !== currentSelector) { - currentSelector = selector; - currentValue = credentials[selector]?.value || ''; - currentType = inputType || credentials[selector]?.type || ''; - } - - // Handle different types of key actions - if (character === 'Backspace') { - // Remove the last character when backspace is pressed - currentValue = currentValue.slice(0, -1); - } else if (isPrintableCharacter(character)) { - // Add the character to the current value - currentValue += character; - } - // Note: We ignore other special keys like 'Shift', 'Enter', etc. + credentials[selector].value = value; + credentials[selector].type = type; } }); - - // Store the final state of the last processed selector - if (currentSelector) { - credentials[currentSelector] = { - value: currentValue, - type: currentType - }; - } }); return credentials; @@ -224,6 +183,7 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin const getRobot = async () => { if (recordingId) { const robot = await getStoredRecording(recordingId); + console.log("ROBOT:", robot); setRobot(robot); } else { notify('error', t('robot_edit.notifications.update_failed')); From 4c21e2794a4e87e8b6e91dd97f827064c62ca526 Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 13 Feb 2025 11:15:51 +0530 Subject: [PATCH 10/18] feat: console log cleanup --- src/components/robot/RobotEdit.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/robot/RobotEdit.tsx b/src/components/robot/RobotEdit.tsx index 12fd32bd..b4b271bc 100644 --- a/src/components/robot/RobotEdit.tsx +++ b/src/components/robot/RobotEdit.tsx @@ -183,7 +183,6 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin const getRobot = async () => { if (recordingId) { const robot = await getStoredRecording(recordingId); - console.log("ROBOT:", robot); setRobot(robot); } else { notify('error', t('robot_edit.notifications.update_failed')); From b7ce40c8af0b8f7f942b02b4c05f6c214b9a08aa Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 13 Feb 2025 11:58:02 +0530 Subject: [PATCH 11/18] feat: update workflow type actions, update robot --- server/src/routes/storage.ts | 69 +++++++++++++++--------------------- 1 file changed, 29 insertions(+), 40 deletions(-) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index 01f1ca6b..aa63798f 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -167,51 +167,26 @@ function updateTypeActionsInWorkflow(workflow: any[], credentials: Credentials) return workflow.map(step => { if (!step.what) return step; - const indicesToRemove = new Set(); - step.what.forEach((action: any, index: number) => { - if (!action.action || !action.args?.[0]) return; - - if ((action.action === 'type' || action.action === 'press') && credentials[action.args[0]]) { - indicesToRemove.add(index); - - if (step.what[index + 1]?.action === 'waitForLoadState') { - indicesToRemove.add(index + 1); - } - } - }); - - const filteredWhat = step.what.filter((_: any, index: number) => !indicesToRemove.has(index)); - - Object.entries(credentials).forEach(([selector, credentialInfo]) => { - const clickIndex = filteredWhat.findIndex((action: any) => - action.action === 'click' && action.args?.[0] === selector - ); - - if (clickIndex !== -1) { - const chars = credentialInfo.value.split(''); + step.what = step.what.map((action: any) => { + if (action.action === 'type' && action.args?.length >= 2) { + const selector = action.args[0]; - chars.forEach((char, i) => { - filteredWhat.splice(clickIndex + 1 + (i * 2), 0, { - action: 'type', + if (credentials[selector]) { + return { + ...action, args: [ selector, - encrypt(char), - credentialInfo.type + encrypt(credentials[selector].value), + credentials[selector].type ] - }); - - filteredWhat.splice(clickIndex + 2 + (i * 2), 0, { - action: 'waitForLoadState', - args: ['networkidle'] - }); - }); + }; + } } + + return action; }); - return { - ...step, - what: filteredWhat - }; + return step; }); } @@ -281,9 +256,23 @@ router.put('/recordings/:id', requireSignIn, async (req: AuthenticatedRequest, r } } - robot.set('recording', { ...robot.recording, workflow }); + const updates: any = { + recording: { + ...robot.recording, + workflow + } + }; - await robot.save(); + if (name) { + updates.recording_meta = { + ...robot.recording_meta, + name + }; + } + + await Robot.update(updates, { + where: { 'recording_meta.id': id } + }); const updatedRobot = await Robot.findOne({ where: { 'recording_meta.id': id } }); From 81b917b3976545c3cafe4143eb76ac8b29db4a1a Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 13 Feb 2025 12:05:02 +0530 Subject: [PATCH 12/18] feat: console log cleanup --- src/components/robot/RobotEdit.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/robot/RobotEdit.tsx b/src/components/robot/RobotEdit.tsx index b4b271bc..86fc5974 100644 --- a/src/components/robot/RobotEdit.tsx +++ b/src/components/robot/RobotEdit.tsx @@ -118,7 +118,6 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin useEffect(() => { if (robot?.recording?.workflow) { const extractedCredentials = extractInitialCredentials(robot.recording.workflow); - console.log(extractedCredentials); setCredentials(extractedCredentials); setCredentialGroups(groupCredentialsByType(extractedCredentials)); } From 8369714a152d8cb989669cd1f8fd00de3fe53cd5 Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 13 Feb 2025 12:08:33 +0530 Subject: [PATCH 13/18] feat: comments cleanup --- .../workflow-management/classes/Generator.ts | 37 +------------------ 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index fc732e60..a5d92d4e 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -435,17 +435,13 @@ export class WorkflowGenerator { } if ((elementInfo?.tagName === 'INPUT' || elementInfo?.tagName === 'TEXTAREA') && selector) { - // Calculate the exact position within the element const positionAndCursor = await page.evaluate( ({ selector, coords }) => { const getCursorPosition = (element: any, clickX: any) => { - // Get the input's text content const text = element.value; - // Create a temporary hidden div to measure text const mirror = document.createElement('div'); - // Copy ALL relevant styles that could affect text measurement const style = window.getComputedStyle(element); mirror.style.cssText = ` font: ${style.font}; @@ -463,51 +459,33 @@ export class WorkflowGenerator { document.body.appendChild(mirror); - // Get the element's padding and border widths const paddingLeft = parseFloat(style.paddingLeft); const borderLeft = parseFloat(style.borderLeftWidth); - // Adjust clickX to account for padding and border const adjustedClickX = clickX - (paddingLeft + borderLeft); let bestIndex = 0; let bestDiff = Infinity; - // Try each possible cursor position for (let i = 0; i <= text.length; i++) { - // Create a span for the text before cursor const textBeforeCursor = text.substring(0, i); const span = document.createElement('span'); span.textContent = textBeforeCursor; mirror.innerHTML = ''; mirror.appendChild(span); - // Get the x-position where this character would end const textWidth = span.getBoundingClientRect().width; - // Calculate distance from adjusted click to this position const diff = Math.abs(adjustedClickX - textWidth); - // If this position is closer to the click, update bestIndex if (diff < bestDiff) { bestIndex = i; bestDiff = diff; } } - // Clean up document.body.removeChild(mirror); - // Add debug logging - console.log({ - text, - clickX, - adjustedClickX, - bestIndex, - value: text.substring(0, bestIndex), - nextChar: text[bestIndex] || 'EOL' - }); - return bestIndex; }; @@ -1133,17 +1111,14 @@ export class WorkflowGenerator { * @param workflow The workflow to be optimized. */ private optimizeWorkflow = (workflow: WorkflowFile) => { - // Track state for each input field const inputStates = new Map(); - // First pass: Process all actions and build final states for (const pair of workflow.workflow) { let currentIndex = 0; while (currentIndex < pair.what.length) { const condition = pair.what[currentIndex]; - // Handle click actions with cursor positioning if (condition.action === 'click' && condition.args?.[2]?.cursorIndex !== undefined) { const selector = condition.args[0]; const cursorIndex = condition.args[2].cursorIndex; @@ -1158,12 +1133,10 @@ export class WorkflowGenerator { state.cursorPosition = cursorIndex; inputStates.set(selector, state); - // Remove the click action pair.what.splice(currentIndex, 1); continue; } - // Handle text input and editing if (condition.action === 'press' && condition.args?.[1]) { const [selector, encryptedKey, type] = condition.args; const key = decrypt(encryptedKey); @@ -1173,11 +1146,10 @@ export class WorkflowGenerator { state = { selector, value: '', - type: type || 'text', // Use the type from the press action + type: type || 'text', cursorPosition: -1 }; } else { - // Update type from the press action if it exists state.type = type || state.type; } @@ -1206,14 +1178,12 @@ export class WorkflowGenerator { state.value.slice(0, state.cursorPosition) + state.value.slice(state.cursorPosition + 1); } else if (state.cursorPosition === -1 && state.value.length > 0) { - // If no cursor position set, delete at the end state.value = state.value.slice(0, -1); } } inputStates.set(selector, state); - // Remove the press action pair.what.splice(currentIndex, 1); continue; } @@ -1222,14 +1192,11 @@ export class WorkflowGenerator { } } - // Second pass: Add one type action per selector in the last pair where that selector was used for (const [selector, state] of inputStates.entries()) { if (state.value) { - // Find the last pair that used this selector for (let i = workflow.workflow.length - 1; i >= 0; i--) { const pair = workflow.workflow[i]; - // Add type action to the end of the pair pair.what.push({ action: 'type', args: [selector, encrypt(state.value), state.type] @@ -1238,7 +1205,7 @@ export class WorkflowGenerator { args: ['networkidle'] }); - break; // Stop after adding to the first pair we find + break; } } } From 5207d7da6f90b0d67130825dafedc0fec6c3764f Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 13 Feb 2025 12:11:04 +0530 Subject: [PATCH 14/18] feat: default to text if type not specified --- src/components/robot/RobotEdit.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/robot/RobotEdit.tsx b/src/components/robot/RobotEdit.tsx index 86fc5974..1f76c725 100644 --- a/src/components/robot/RobotEdit.tsx +++ b/src/components/robot/RobotEdit.tsx @@ -139,7 +139,7 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin ) { const selector: string = action.args[0]; const value: string = action.args[1]; - const type: string = action.args[2]; + const type: string = action.args[2] || 'text'; if (!credentials[selector]) { credentials[selector] = { From cd574194b0e4a5ad7b0667262abb57e007bf5151 Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 13 Feb 2025 12:23:58 +0530 Subject: [PATCH 15/18] feat: console log cleanup --- .../workflow-management/classes/Generator.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/server/src/workflow-management/classes/Generator.ts b/server/src/workflow-management/classes/Generator.ts index a5d92d4e..28b8b361 100644 --- a/server/src/workflow-management/classes/Generator.ts +++ b/server/src/workflow-management/classes/Generator.ts @@ -768,23 +768,6 @@ export class WorkflowGenerator { * @returns {Promise} */ public saveNewWorkflow = async (fileName: string, userId: number, isLogin: boolean) => { - for (const pair of this.workflowRecord.workflow) { - for (let i = 0; i < pair.what.length; i++) { - const condition = pair.what[i]; - - if (condition.action === 'press' && condition.args) { - const [selector, encryptedKey, type] = condition.args; - const key = decrypt(encryptedKey); - - console.log(`Selector: ${selector}, Key: ${key}`); - } - - if (condition.action === 'click' && condition.args) { - console.log("Click args: ", condition.args); - } - } - } - const recording = this.optimizeWorkflow(this.workflowRecord); try { this.recordingMeta = { From c6f5479358bd7d47192761ec66105312967133a3 Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 13 Feb 2025 15:50:13 +0530 Subject: [PATCH 16/18] feat: add support for full and partial word type actions --- src/components/robot/RobotEdit.tsx | 120 +++++++++++++++++++++++------ 1 file changed, 97 insertions(+), 23 deletions(-) diff --git a/src/components/robot/RobotEdit.tsx b/src/components/robot/RobotEdit.tsx index 1f76c725..6e8868de 100644 --- a/src/components/robot/RobotEdit.tsx +++ b/src/components/robot/RobotEdit.tsx @@ -123,39 +123,113 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin } }, [robot]); - const extractInitialCredentials = (workflow: any[]): Credentials => { + function extractInitialCredentials(workflow: any[]): Credentials { const credentials: Credentials = {}; - - // Process each step in the workflow + + const isPrintableCharacter = (char: string): boolean => { + return char.length === 1 && !!char.match(/^[\x20-\x7E]$/); + }; + workflow.forEach(step => { if (!step.what) return; - - // Process actions in sequence to maintain correct text state - step.what.forEach((action: any) => { - if ( - action.action === 'type' && - action.args?.length >= 2 && - typeof action.args[1] === 'string' - ) { - const selector: string = action.args[0]; - const value: string = action.args[1]; - const type: string = action.args[2] || 'text'; - + + let currentSelector = ''; + let currentValue = ''; + let currentType = ''; + let i = 0; + + while (i < step.what.length) { + const action = step.what[i]; + + if (!action.action || !action.args?.[0]) { + i++; + continue; + } + + const selector = action.args[0]; + + // Handle full word type actions first + if (action.action === 'type' && + action.args?.length >= 2 && + typeof action.args[1] === 'string' && + action.args[1].length > 1) { + if (!credentials[selector]) { credentials[selector] = { - value: '', - type: '' + value: action.args[1], + type: action.args[2] || 'text' }; } - - credentials[selector].value = value; - credentials[selector].type = type; + i++; + continue; } - }); + + // Handle character-by-character sequences (both type and press) + if ((action.action === 'type' || action.action === 'press') && + action.args?.length >= 2 && + typeof action.args[1] === 'string') { + + if (selector !== currentSelector) { + if (currentSelector && currentValue) { + credentials[currentSelector] = { + value: currentValue, + type: currentType || 'text' + }; + } + currentSelector = selector; + currentValue = credentials[selector]?.value || ''; + currentType = action.args[2] || credentials[selector]?.type || 'text'; + } + + const character = action.args[1]; + + if (isPrintableCharacter(character)) { + currentValue += character; + } else if (character === 'Backspace') { + currentValue = currentValue.slice(0, -1); + } + + if (!currentType && action.args[2]?.toLowerCase() === 'password') { + currentType = 'password'; + } + + let j = i + 1; + while (j < step.what.length) { + const nextAction = step.what[j]; + if (!nextAction.action || !nextAction.args?.[0] || + nextAction.args[0] !== selector || + (nextAction.action !== 'type' && nextAction.action !== 'press')) { + break; + } + if (nextAction.args[1] === 'Backspace') { + currentValue = currentValue.slice(0, -1); + } else if (isPrintableCharacter(nextAction.args[1])) { + currentValue += nextAction.args[1]; + } + j++; + } + + credentials[currentSelector] = { + value: currentValue, + type: currentType + }; + + i = j; + } else { + i++; + } + } + + if (currentSelector && currentValue) { + credentials[currentSelector] = { + value: currentValue, + type: currentType || 'text' + }; + } }); - + return credentials; - }; + } const groupCredentialsByType = (credentials: Credentials): GroupedCredentials => { return Object.entries(credentials).reduce((acc: GroupedCredentials, [selector, info]) => { From ff21f5355b41d4c2f8c0c21b16db499f93f22926 Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 13 Feb 2025 16:13:42 +0530 Subject: [PATCH 17/18] feat: fallback to update type actions --- server/src/routes/storage.ts | 99 +++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 18 deletions(-) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index aa63798f..7e5b3638 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -163,30 +163,93 @@ interface Credentials { [key: string]: CredentialInfo; } -function updateTypeActionsInWorkflow(workflow: any[], credentials: Credentials) { +function handleWorkflowActions(workflow: any[], credentials: Credentials) { + // Process one step at a time to avoid memory issues return workflow.map(step => { if (!step.what) return step; - step.what = step.what.map((action: any) => { - if (action.action === 'type' && action.args?.length >= 2) { - const selector = action.args[0]; + // Use a more memory-efficient approach + const newWhat: any[] = []; + const processedSelectors = new Set(); + + for (let i = 0; i < step.what.length; i++) { + const action = step.what[i]; + + // Skip invalid actions + if (!action?.action || !action?.args?.[0]) { + newWhat.push(action); + continue; + } + + const selector = action.args[0]; + const credential = credentials[selector]; + + // Handle non-credential actions + if (!credential) { + newWhat.push(action); + continue; + } + + // Handle credential-related actions + if (action.action === 'click') { + newWhat.push(action); - if (credentials[selector]) { - return { - ...action, - args: [ - selector, - encrypt(credentials[selector].value), - credentials[selector].type - ] - }; + // If we haven't processed this selector and there's a following type/press action + if (!processedSelectors.has(selector) && + i + 1 < step.what.length && + (step.what[i + 1].action === 'type' || step.what[i + 1].action === 'press')) { + + // Add type action + newWhat.push({ + action: 'type', + args: [selector, encrypt(credential.value), credential.type] + }); + + // Add single waitForLoadState + newWhat.push({ + action: 'waitForLoadState', + args: ['networkidle'] + }); + + processedSelectors.add(selector); + + // Skip subsequent type/press/waitForLoadState actions for this selector + while (i + 1 < step.what.length && + (step.what[i + 1].action === 'type' || + step.what[i + 1].action === 'press' || + step.what[i + 1].action === 'waitForLoadState')) { + i++; + } + } + } else if ((action.action === 'type' || action.action === 'press') && + !processedSelectors.has(selector)) { + // Handle standalone type/press action + newWhat.push({ + action: 'type', + args: [selector, encrypt(credential.value), credential.type] + }); + + newWhat.push({ + action: 'waitForLoadState', + args: ['networkidle'] + }); + + processedSelectors.add(selector); + + // Skip subsequent type/press/waitForLoadState actions for this selector + while (i + 1 < step.what.length && + (step.what[i + 1].action === 'type' || + step.what[i + 1].action === 'press' || + step.what[i + 1].action === 'waitForLoadState')) { + i++; } } - - return action; - }); + } - return step; + return { + ...step, + what: newWhat + }; }); } @@ -218,7 +281,7 @@ router.put('/recordings/:id', requireSignIn, async (req: AuthenticatedRequest, r let workflow = [...robot.recording.workflow]; // Create a copy of the workflow if (credentials) { - workflow = updateTypeActionsInWorkflow(workflow, credentials); + workflow = handleWorkflowActions(workflow, credentials); } // Update the limit From 99696aaba2aabef1421a362930c1afd704427eb9 Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 13 Feb 2025 16:15:27 +0530 Subject: [PATCH 18/18] feat: comment cleanup --- server/src/routes/storage.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index 7e5b3638..ea1fce38 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -164,18 +164,15 @@ interface Credentials { } function handleWorkflowActions(workflow: any[], credentials: Credentials) { - // Process one step at a time to avoid memory issues return workflow.map(step => { if (!step.what) return step; - // Use a more memory-efficient approach const newWhat: any[] = []; const processedSelectors = new Set(); for (let i = 0; i < step.what.length; i++) { const action = step.what[i]; - // Skip invalid actions if (!action?.action || !action?.args?.[0]) { newWhat.push(action); continue; @@ -184,28 +181,23 @@ function handleWorkflowActions(workflow: any[], credentials: Credentials) { const selector = action.args[0]; const credential = credentials[selector]; - // Handle non-credential actions if (!credential) { newWhat.push(action); continue; } - // Handle credential-related actions if (action.action === 'click') { newWhat.push(action); - // If we haven't processed this selector and there's a following type/press action if (!processedSelectors.has(selector) && i + 1 < step.what.length && (step.what[i + 1].action === 'type' || step.what[i + 1].action === 'press')) { - // Add type action newWhat.push({ action: 'type', args: [selector, encrypt(credential.value), credential.type] }); - // Add single waitForLoadState newWhat.push({ action: 'waitForLoadState', args: ['networkidle'] @@ -213,7 +205,6 @@ function handleWorkflowActions(workflow: any[], credentials: Credentials) { processedSelectors.add(selector); - // Skip subsequent type/press/waitForLoadState actions for this selector while (i + 1 < step.what.length && (step.what[i + 1].action === 'type' || step.what[i + 1].action === 'press' || @@ -223,7 +214,6 @@ function handleWorkflowActions(workflow: any[], credentials: Credentials) { } } else if ((action.action === 'type' || action.action === 'press') && !processedSelectors.has(selector)) { - // Handle standalone type/press action newWhat.push({ action: 'type', args: [selector, encrypt(credential.value), credential.type]