Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 58 additions & 28 deletions packages/injected/src/recorder/recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ class InspectTool implements RecorderTool {
void this._recorder.recordAction({
name: 'assertVisible',
selector,
signals: [],
});
this._recorder.setMode('recording');
this._recorder.overlay?.flashToolSucceeded('assertingVisibility');
Expand Down Expand Up @@ -262,27 +261,31 @@ class RecordActionTool implements RecorderTool {
return;
}

if (event.detail === 1) {
// A new click starts here, so the stalled one is not a double click after all.
this._commitPendingClickAction();
} else {
// This click continues a multi-click, which is reported by 'dblclick' instead.
this._cancelPendingClickAction();
}

const checkbox = asCheckbox(this._recorder.deepEventTarget(event));
if (checkbox && event.detail === 1) {
// Interestingly, inputElement.checked is reversed inside this event handler.
this._performAction({
name: checkbox.checked ? 'check' : 'uncheck',
selector: this._hoveredModel!.selector,
signals: [],
});
return;
}

this._cancelPendingClickAction();

// Stall click in case we are observing double-click.
if (event.detail === 1) {
this._pendingClickAction = {
action: {
name: 'click',
selector: this._hoveredModel!.selector,
position: positionForEvent(event),
signals: [],
button: buttonForEvent(event),
modifiers: modifiersForEvent(event),
clickCount: event.detail
Expand Down Expand Up @@ -311,7 +314,6 @@ class RecordActionTool implements RecorderTool {
name: 'click',
selector: this._hoveredModel!.selector,
position: positionForEvent(event),
signals: [],
button: buttonForEvent(event),
modifiers: modifiersForEvent(event),
clickCount: event.detail
Expand Down Expand Up @@ -421,7 +423,6 @@ class RecordActionTool implements RecorderTool {
this._recordAction({
name: 'setInputFiles',
selector,
signals: [],
files: [...((target as HTMLInputElement).files || [])].map(file => file.name),
});
return;
Expand All @@ -432,7 +433,6 @@ class RecordActionTool implements RecorderTool {
name: 'fill',
// must use hoveredModel instead of activeModel for it to work in webkit
selector: this._hoveredModel!.selector,
signals: [],
text: target.value,
});
return;
Expand All @@ -450,7 +450,6 @@ class RecordActionTool implements RecorderTool {
this._recordAction({
name: 'fill',
selector: this._activeModel!.selector,
signals: [],
text: target.isContentEditable ? target.innerText : (target as HTMLInputElement).value,
});
}
Expand All @@ -461,7 +460,6 @@ class RecordActionTool implements RecorderTool {
name: 'select',
selector: this._activeModel!.selector,
options: [...selectElement.selectedOptions].map(option => option.value),
signals: []
});
}
}
Expand All @@ -484,7 +482,6 @@ class RecordActionTool implements RecorderTool {
this._performAction({
name: checkbox.checked ? 'uncheck' : 'check',
selector: this._activeModel!.selector,
signals: [],
});
return;
}
Expand All @@ -493,7 +490,6 @@ class RecordActionTool implements RecorderTool {
this._performAction({
name: 'press',
selector: this._activeModel!.selector,
signals: [],
key: event.key,
modifiers: modifiersForEvent(event),
});
Expand Down Expand Up @@ -529,7 +525,6 @@ class RecordActionTool implements RecorderTool {
name: 'click',
selector: model.selector,
position: actionPosition,
signals: [],
button: 'left',
modifiers: 0,
clickCount: 0,
Expand All @@ -541,7 +536,6 @@ class RecordActionTool implements RecorderTool {
name: 'click',
selector: model.selector,
position: actionPosition,
signals: [],
button: 'right',
modifiers: 0,
clickCount: 0,
Expand All @@ -553,7 +547,6 @@ class RecordActionTool implements RecorderTool {
name: 'click',
selector: model.selector,
position: actionPosition,
signals: [],
button: 'left',
modifiers: 0,
clickCount: 2,
Expand All @@ -565,7 +558,6 @@ class RecordActionTool implements RecorderTool {
name: 'hover',
selector: model.selector,
position: actionPosition,
signals: [],
}),
},
{
Expand Down Expand Up @@ -741,6 +733,7 @@ class RecordActionTool implements RecorderTool {

class JsonRecordActionTool implements RecorderTool {
private _recorder: Recorder;
private _pendingClickAction: { action: actions.ClickAction, timeout: number } | undefined;

constructor(recorder: Recorder) {
this._recorder = recorder;
Expand All @@ -752,6 +745,7 @@ class JsonRecordActionTool implements RecorderTool {
}

uninstall() {
this._cancelPendingClickAction();
this._recorder.highlight.install();
}

Expand All @@ -767,6 +761,14 @@ class JsonRecordActionTool implements RecorderTool {
if (this._shouldIgnoreMouseEvent(event))
return;

if (event.detail === 1) {
// A new click starts here, so the stalled one is not a double click after all.
this._commitPendingClickAction();
} else {
// This click continues a multi-click, which is reported by 'dblclick' instead.
this._cancelPendingClickAction();
}

const checkbox = asCheckbox(element);
const { ariaSnapshot, selector, ref } = this._ariaSnapshot(element);
if (checkbox && event.detail === 1) {
Expand All @@ -775,25 +777,63 @@ class JsonRecordActionTool implements RecorderTool {
name: checkbox.checked ? 'check' : 'uncheck',
selector,
ref,
signals: [],
ariaSnapshot,
});
return;
}

// Stall click in case we are observing double-click.
if (event.detail === 1) {
this._pendingClickAction = {
action: {
name: 'click',
selector,
ref,
ariaSnapshot,
position: positionForEvent(event),
button: buttonForEvent(event),
modifiers: modifiersForEvent(event),
clickCount: event.detail,
},
timeout: this._recorder.injectedScript.utils.builtins.setTimeout(() => this._commitPendingClickAction(), 200)
};
}
}

onDblClick(event: MouseEvent) {
const element = this._recorder.deepEventTarget(event);
if (isRangeInput(element))
return;
if (this._shouldIgnoreMouseEvent(event))
return;

this._cancelPendingClickAction();

const { ariaSnapshot, selector, ref } = this._ariaSnapshot(element);
void this._recorder.recordAction({
name: 'click',
selector,
ref,
ariaSnapshot,
position: positionForEvent(event),
signals: [],
button: buttonForEvent(event),
modifiers: modifiersForEvent(event),
clickCount: event.detail,
});
}

private _commitPendingClickAction() {
if (this._pendingClickAction)
void this._recorder.recordAction(this._pendingClickAction.action);
this._cancelPendingClickAction();
}

private _cancelPendingClickAction() {
if (this._pendingClickAction)
this._recorder.injectedScript.utils.builtins.clearTimeout(this._pendingClickAction.timeout);
this._pendingClickAction = undefined;
}

onContextMenu(event: MouseEvent): void {
const element = this._recorder.deepEventTarget(event);
const { ariaSnapshot, selector, ref } = this._ariaSnapshot(element);
Expand All @@ -803,7 +843,6 @@ class JsonRecordActionTool implements RecorderTool {
ref,
ariaSnapshot,
position: positionForEvent(event),
signals: [],
button: 'right',
modifiers: modifiersForEvent(event),
clickCount: 1,
Expand All @@ -820,7 +859,6 @@ class JsonRecordActionTool implements RecorderTool {
selector,
ref,
ariaSnapshot,
signals: [],
text: element.value,
});
return;
Expand All @@ -837,7 +875,6 @@ class JsonRecordActionTool implements RecorderTool {
ref,
selector,
ariaSnapshot,
signals: [],
text: element.isContentEditable ? element.innerText : (element as HTMLInputElement).value,
});
return;
Expand All @@ -851,7 +888,6 @@ class JsonRecordActionTool implements RecorderTool {
ref,
ariaSnapshot,
options: [...selectElement.selectedOptions].map(option => option.value),
signals: []
});
return;
}
Expand All @@ -873,7 +909,6 @@ class JsonRecordActionTool implements RecorderTool {
selector,
ref,
ariaSnapshot,
signals: [],
});
return;
}
Expand All @@ -884,7 +919,6 @@ class JsonRecordActionTool implements RecorderTool {
selector,
ref,
ariaSnapshot,
signals: [],
key: event.key,
modifiers: modifiersForEvent(event),
});
Expand Down Expand Up @@ -1036,15 +1070,13 @@ class TextAssertionTool implements RecorderTool {
return {
name: 'assertChecked',
selector,
signals: [],
// Interestingly, inputElement.checked is reversed inside this event handler.
checked: !(target as HTMLInputElement).checked,
};
} else {
return {
name: 'assertValue',
selector,
signals: [],
value: (target as (HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement)).value,
};
}
Expand All @@ -1057,7 +1089,6 @@ class TextAssertionTool implements RecorderTool {
return {
name: 'assertSnapshot',
selector: this._hoverHighlight.selector,
signals: [],
ariaSnapshot: this._recorder.injectedScript.ariaSnapshot(target, { mode: 'codegen' }),
};
} else {
Expand All @@ -1069,7 +1100,6 @@ class TextAssertionTool implements RecorderTool {
return {
name: 'assertText',
selector: this._hoverHighlight.selector,
signals: [],
text: this._recorder.injectedScript.utils.elementText(this._textCache, target).normalized,
substring: true,
};
Expand Down
7 changes: 2 additions & 5 deletions packages/isomorphic/codegen/actions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export type ActionName =

export type ActionBase = {
name: ActionName,
signals: Signal[],
ariaSnapshot?: string,
};

Expand Down Expand Up @@ -155,7 +154,7 @@ export type DialogSignal = BaseSignal & {
dialogAlias: string,
};

// An element that appeared since the previous action, asserted before this action runs.
// An element appeared as a result of an action.
export type ExpectSignal = BaseSignal & {
name: 'expect',
selector: string,
Expand All @@ -166,12 +165,10 @@ export type Signal = NavigationSignal | PopupSignal | DownloadSignal | DialogSig
export type ActionInContext = {
pageGuid: string;
action: Action;
startTime: number;
endTime?: number;
signals: Signal[];
};

export type SignalInContext = {
pageGuid: string;
signal: Signal;
timestamp: number;
};
2 changes: 1 addition & 1 deletion packages/isomorphic/codegen/csharp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class CSharpLanguageGenerator implements LanguageGenerator {
}

const subject = pageAlias;
const signals = toSignalMap(action);
const signals = toSignalMap(actionInContext.signals);

if (signals.dialog) {
formatter.add(` void ${pageAlias}_Dialog${signals.dialog.dialogAlias}_EventHandler(object sender, IDialog dialog)
Expand Down
2 changes: 1 addition & 1 deletion packages/isomorphic/codegen/java.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class JavaLanguageGenerator implements LanguageGenerator {
}

const subject = pageAlias;
const signals = toSignalMap(action);
const signals = toSignalMap(actionInContext.signals);

if (signals.dialog) {
formatter.add(` ${pageAlias}.onceDialog(dialog -> {
Expand Down
2 changes: 1 addition & 1 deletion packages/isomorphic/codegen/javascript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator {
}

const subject = pageAlias;
const signals = toSignalMap(action);
const signals = toSignalMap(actionInContext.signals);

if (signals.dialog) {
formatter.add(` ${pageAlias}.once('dialog', dialog => {
Expand Down
3 changes: 2 additions & 1 deletion packages/isomorphic/codegen/jsonl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@ export class JsonlLanguageGenerator implements LanguageGenerator {
const entry = {
...actionInContext.action,
pageGuid: actionInContext.pageGuid,
signals: actionInContext.signals,
locator,
ariaSnapshot: undefined,
};
const lines = [JSON.stringify(entry)];
const expect = toSignalMap(actionInContext.action).expect;
const expect = toSignalMap(actionInContext.signals).expect;
if (options.generateExpectSignal && expect)
lines.push(this.generateAction(expectSignalAction(actionInContext, expect), options));
return lines.join('\n');
Expand Down
Loading
Loading