feat: optimize press actions to type actions

This commit is contained in:
Rohit
2025-02-12 22:37:40 +05:30
parent 206d760dd1
commit 945f1200c4

View File

@@ -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<string, InputState>();
// 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
}
}
}