Skip to content

Commit

Permalink
Record enter key presses in input fields
Browse files Browse the repository at this point in the history
Signed-off-by: Simon Bennetts <[email protected]>
  • Loading branch information
psiinon committed Nov 28, 2024
1 parent cc15f7f commit c3e5583
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 6 deletions.
110 changes: 104 additions & 6 deletions source/ContentScript/recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
ZestStatement,
ZestStatementElementClick,
ZestStatementElementSendKeys,
ZestStatementElementSubmit,
ZestStatementLaunchBrowser,
ZestStatementSwitchToFrame,
} from '../types/zestScript/ZestStatement';
Expand All @@ -44,28 +45,47 @@ class Recorder {

isNotificationRaised = false;

async sendZestScriptToZAP(zestStatement: ZestStatement): Promise<number> {
// Enter keydown events often occur before the input change events, so we reorder them if needed
cachedSubmit?: ZestStatementElementSubmit;

async sendZestScriptToZAP(
zestStatement: ZestStatement,
sendCache: boolean
): Promise<number> {
if (sendCache) {
this.handleCachedSubmit();
}
this.notify(zestStatement);
return Browser.runtime.sendMessage({
type: ZEST_SCRIPT,
data: zestStatement.toJSON(),
});
}

handleCachedSubmit(): void {
if (this.cachedSubmit) {
this.sendZestScriptToZAP(this.cachedSubmit, false);
delete this.cachedSubmit;
}
}

handleFrameSwitches(level: number, frameIndex: number): void {
if (this.curLevel === level && this.curFrame === frameIndex) {
return;
}
if (this.curLevel > level) {
while (this.curLevel > level) {
this.sendZestScriptToZAP(new ZestStatementSwitchToFrame(-1));
this.sendZestScriptToZAP(new ZestStatementSwitchToFrame(-1), true);
this.curLevel -= 1;
}
this.curFrame = frameIndex;
} else {
this.curLevel += 1;
this.curFrame = frameIndex;
this.sendZestScriptToZAP(new ZestStatementSwitchToFrame(frameIndex));
this.sendZestScriptToZAP(
new ZestStatementSwitchToFrame(frameIndex),
true
);
}
if (this.curLevel !== level) {
console.log('Error in switching frames');
Expand All @@ -81,7 +101,10 @@ class Recorder {
this.handleFrameSwitches(level, frame);
console.log(event, 'clicked');
const elementLocator = getPath(event.target as HTMLElement, element);
this.sendZestScriptToZAP(new ZestStatementElementClick(elementLocator));
this.sendZestScriptToZAP(
new ZestStatementElementClick(elementLocator),
true
);
// click on target element
}

Expand Down Expand Up @@ -118,12 +141,38 @@ class Recorder {
this.handleFrameSwitches(level, frame);
console.log(event, 'change', (event.target as HTMLInputElement).value);
const elementLocator = getPath(event.target as HTMLElement, element);
// Send the keys before a cached submit statement on the same element
if (
this.cachedSubmit &&
this.cachedSubmit.elementLocator.element !== elementLocator.element
) {
// The cached submit was not on the same element, so send it
this.handleCachedSubmit();
}
this.sendZestScriptToZAP(
new ZestStatementElementSendKeys(
elementLocator,
(event.target as HTMLInputElement).value
)
),
false
);
// Now send the cached submit, if there still is one
this.handleCachedSubmit();
}

handleKeypress(
params: {level: number; frame: number; element: Document},
event: KeyboardEvent
): void {
if (!this.shouldRecord(event.target as HTMLElement)) return;
const {element} = params;
if (event.key === 'Enter') {
this.handleCachedSubmit();
const elementLocator = getPath(event.target as HTMLElement, element);
console.log('Enter key pressed', elementLocator);
// Cache the statement as it often occurs before the change event occurs
this.cachedSubmit = new ZestStatementElementSubmit(elementLocator);
}
}

handleResize(): void {
Expand All @@ -145,6 +194,9 @@ class Recorder {
level: number,
frame: number
): void {
// A list of all of the text elements that we have added event listeners to
const textElements = new Set();

element.addEventListener(
'click',
this.handleClick.bind(this, {level, frame, element})
Expand Down Expand Up @@ -173,6 +225,45 @@ class Recorder {
i += 1;
}
});

// Add listeners to all of the text fields
element.querySelectorAll('input').forEach((input) => {
if (!textElements.has(input)) {
textElements.add(input);
input.addEventListener(
'keydown',
this.handleKeypress.bind(this, {level, frame, element})
);
}
});
// Observer callback function to handle DOM mutations to detect added text fields
const domMutated: MutationCallback = (mutationsList: MutationRecord[]) => {
mutationsList.forEach((mutation) => {
if (mutation.type === 'childList') {
// Look for added input elements
if (mutation.target instanceof Element) {
const inputs = mutation.target.getElementsByTagName('input');
for (let j = 0; j < inputs.length; j += 1) {
const input = inputs[j];
if (!textElements.has(input)) {
textElements.add(input);
input.addEventListener(
'keydown',
this.handleKeypress.bind(this, {level, frame, element})
);
}
}
}
}
});
};

const observer = new MutationObserver(domMutated);
observer.observe(document, {
attributes: false,
childList: true,
subtree: true,
});
}

shouldRecord(element: HTMLElement): boolean {
Expand All @@ -196,7 +287,10 @@ class Recorder {
// send window resize event to ensure same size
const browserType = this.getBrowserName();
const url = window.location.href;
this.sendZestScriptToZAP(new ZestStatementLaunchBrowser(browserType, url));
this.sendZestScriptToZAP(
new ZestStatementLaunchBrowser(browserType, url),
true
);
this.handleResize();
}

Expand All @@ -221,6 +315,7 @@ class Recorder {

stopRecordingUserInteractions(): void {
console.log('Stopping Recording User Interactions ...');
this.handleCachedSubmit();
Browser.storage.sync.set({zaprecordingactive: false});
this.active = false;
const floatingDiv = document.getElementById('ZapfloatingDiv');
Expand Down Expand Up @@ -367,6 +462,9 @@ class Recorder {
} else if (stmt instanceof ZestStatementElementSendKeys) {
notifyMessage.title = 'Send Keys';
notifyMessage.message = `${stmt.elementLocator.element}: ${stmt.keys}`;
} else if (stmt instanceof ZestStatementElementSubmit) {
notifyMessage.title = 'Submit';
notifyMessage.message = `${stmt.elementLocator.element}`;
} else if (stmt instanceof ZestStatementLaunchBrowser) {
notifyMessage.title = 'Launch Browser';
notifyMessage.message = stmt.browserType;
Expand Down
47 changes: 47 additions & 0 deletions source/types/zestScript/ZestStatement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import {
ZEST_CLIENT_ELEMENT_CLICK,
ZEST_CLIENT_ELEMENT_MOUSE_OVER,
ZEST_CLIENT_ELEMENT_SEND_KEYS,
ZEST_CLIENT_ELEMENT_SUBMIT,
ZEST_CLIENT_LAUNCH,
ZEST_CLIENT_SWITCH_TO_FRAME,
ZEST_CLIENT_WINDOW_CLOSE,
ZEST_COMMENT,
} from '../../utils/constants';

class ElementLocator {
Expand Down Expand Up @@ -91,6 +93,26 @@ class ZestStatementLaunchBrowser extends ZestStatement {
}
}

class ZestComment extends ZestStatement {
comment: string;

windowHandle: string;

constructor(comment: string) {
super(ZEST_COMMENT);
this.comment = comment;
}

toJSON(): string {
return JSON.stringify({
index: this.index,
enabled: true,
elementType: this.elementType,
comment: this.comment,
});
}
}

abstract class ZestStatementElement extends ZestStatement {
elementLocator: ElementLocator;

Expand Down Expand Up @@ -147,6 +169,29 @@ class ZestStatementElementSendKeys extends ZestStatementElement {
}
}

class ZestStatementElementSubmit extends ZestStatementElement {
keys: string;

constructor(
elementLocator: ElementLocator,
windowHandle = DEFAULT_WINDOW_HANDLE
) {
super(ZEST_CLIENT_ELEMENT_SUBMIT, elementLocator);
this.windowHandle = windowHandle;
}

toJSON(): string {
return JSON.stringify({
value: this.keys,
windowHandle: this.windowHandle,
...this.elementLocator.toJSON(),
index: this.index,
enabled: true,
elementType: this.elementType,
});
}
}

class ZestStatementElementClear extends ZestStatementElement {
constructor(
elementLocator: ElementLocator,
Expand Down Expand Up @@ -234,12 +279,14 @@ class ZestStatementElementMouseOver extends ZestStatementElement {

export {
ElementLocator,
ZestComment,
ZestStatement,
ZestStatementLaunchBrowser,
ZestStatementElementMouseOver,
ZestStatementElementClick,
ZestStatementSwitchToFrame,
ZestStatementElementSendKeys,
ZestStatementElementSubmit,
ZestStatementElementClear,
ZestStatementWindowClose,
};
2 changes: 2 additions & 0 deletions source/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
export const ZEST_CLIENT_SWITCH_TO_FRAME = 'ZestClientSwitchToFrame';
export const ZEST_CLIENT_ELEMENT_CLICK = 'ZestClientElementClick';
export const ZEST_CLIENT_ELEMENT_SEND_KEYS = 'ZestClientElementSendKeys';
export const ZEST_CLIENT_ELEMENT_SUBMIT = 'ZestClientElementSubmit';
export const ZEST_CLIENT_LAUNCH = 'ZestClientLaunch';
export const ZEST_CLIENT_ELEMENT_CLEAR = 'ZestClientElementClear';
export const ZEST_CLIENT_WINDOW_CLOSE = 'ZestClientWindowClose';
export const ZEST_CLIENT_ELEMENT_MOUSE_OVER = 'ZestClientMouseOverElement';
export const ZEST_COMMENT = 'ZestComment';
export const DEFAULT_WINDOW_HANDLE = 'windowHandle1';

export const ZAP_STOP_RECORDING = 'zapStopRecording';
Expand Down

0 comments on commit c3e5583

Please sign in to comment.