Skip to content

Commit

Permalink
visible-automation + click automation (#6829)
Browse files Browse the repository at this point in the history
* visible-automation + click automation

* fix client test

* fix test client

* fix test client

* fix ie client tests

* 1

* fix tests

* fix client

* fix legacy tests

* coalse

* lint
  • Loading branch information
AlexKamaev authored Feb 8, 2022
1 parent e43cdd7 commit f9a6786
Show file tree
Hide file tree
Showing 60 changed files with 1,418 additions and 789 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { MouseEventArgs } from '../../../../../../../../shared/actions/automations/visible-element-automation';
import { MouseClickStrategyBase } from '../../../../../../../../shared/actions/automations/click/mouse-click-strategy-base';
import * as clientsManager from '../../clients-manager';

const BUTTON = 'left';
const CLICK_COUNT = 1;

class CDPMouseClickStategy<E> extends MouseClickStrategyBase<E> {
public constructor () {
super();
}

public async mousedown (options: MouseEventArgs<E>): Promise<void> {
const { Input } = clientsManager.getClient();

await Input.dispatchMouseEvent({
type: 'mousePressed',
x: options.point?.x ?? -1,
y: options.point?.y ?? -1,
button: BUTTON,
clickCount: CLICK_COUNT,
});
}

public async mouseup (element: E, options: MouseEventArgs<E>): Promise<MouseEventArgs<E>> {
const { Input } = clientsManager.getClient();

await Input.dispatchMouseEvent({
type: 'mouseReleased',
x: options.point?.x ?? -1,
y: options.point?.y ?? -1,
button: BUTTON,
clickCount: CLICK_COUNT,
});

return options;
}
}

export default function createMouseClickStrategy<E> (): MouseClickStrategyBase<E> {
return new CDPMouseClickStategy<E>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as positionUtils from './utils/position-utils';
import * as styleUtils from './utils/style-utils';
import * as eventUtils from './utils/event-utils';
import createEventSequence from './utils/create-event-sequence';
import createMouseClickStrategy from './automations/click/create-mouse-click-strategy';


initializeAdapter({
Expand Down Expand Up @@ -70,6 +71,12 @@ initializeAdapter({
isTouchDevice: false,
},

utils: {
extend (target: Record<string, any>, ...args): Record<string, any> {
return Object.assign(target, ...args);
},
},

createEventSequence,

sendRequestToFrame: () => { },
Expand All @@ -79,4 +86,15 @@ initializeAdapter({
position: positionUtils,
style: styleUtils,
event: eventUtils,

ensureMouseEventAfterScroll: () => Promise.resolve(),

automations: {
click: {
createMouseClickStrategy,
},

_ensureWindowAndCursorForLegacyTests () {
},
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ export async function findIframeByWindow (context: ExecutionContext): Promise<Se
return null;
}

export function getTagName (node: ServerNode): string {
return node.nodeName.toLowerCase();
}

function hasTagName (node: ServerNode, tagName: string): boolean {
return node.nodeName.toLowerCase() === tagName.toLowerCase();
}
Expand All @@ -85,6 +89,11 @@ export function isBodyElement (node: ServerNode): boolean {
return hasTagName(node, 'body');
}

export function isImgElement (node: ServerNode): boolean {
return hasTagName(node, 'img');
}


export async function getScrollingElement (node?: ServerNode): Promise<ServerNode> {
const client = clientsManager.getClient();

Expand Down Expand Up @@ -125,7 +134,7 @@ export async function getDocumentElement (win: ExecutionContext): Promise<Server
if (exceptionDetails)
throw exceptionDetails;

return describeNode(DOM, resultObj.value.objectId);
return describeNode(DOM, resultObj.objectId as string);
}

export async function isDocumentElement (el: ServerNode): Promise<boolean> {
Expand All @@ -137,3 +146,105 @@ export async function isDocumentElement (el: ServerNode): Promise<boolean> {
export async function isIframeWindow (): Promise<boolean> {
return false;
}

export async function closest (el: ServerNode, selector: string): Promise<ServerNode | null> {
const { Runtime, DOM } = clientsManager.getClient();

const { exceptionDetails, result: resultObj } = await Runtime.callFunctionOn({
arguments: [{ objectId: el.objectId }, { value: selector }],
functionDeclaration: `function (el, selector) {
debugger;
return window["%proxyless%"].nativeMethods.closest.call(el, selector);
}`,
executionContextId: ExecutionContext.getCurrentContextId(),
});

if (exceptionDetails)
throw exceptionDetails;

return resultObj.value ? describeNode(DOM, resultObj.value.objectId) : null;
}

export async function getNodeText (el: ServerNode): Promise<string> {
const { Runtime } = clientsManager.getClient();

const { exceptionDetails, result: resultObj } = await Runtime.callFunctionOn({
arguments: [{ objectId: el.objectId }],
functionDeclaration: `function (el) {
return window["%proxyless%"].nativeMethods.nodeTextContentGetter.call(el);
}`,
executionContextId: ExecutionContext.getCurrentContextId(),
});

if (exceptionDetails)
throw exceptionDetails;

return resultObj.value;
}

export async function containsElement (el1: ServerNode, el2: ServerNode): Promise<boolean> {
const { Runtime } = clientsManager.getClient();

const { exceptionDetails, result: resultObj } = await Runtime.callFunctionOn({
arguments: [{ objectId: el1.objectId }, { objectId: el2.objectId }],
functionDeclaration: `function (el1, el2) {
do {
if (el2.parentNode === el1)
return true;
}
while(el2 = el2.parentNode);
return false;
}`,
executionContextId: ExecutionContext.getCurrentContextId(),
});

if (exceptionDetails)
throw exceptionDetails;

return resultObj.value;
}

export function getImgMapName (el: ServerNode): string {
if (!el.attributes)
return '';

const useMapIndex = el.attributes.indexOf('usemap');

if (useMapIndex === -1)
return '';

return el.attributes[useMapIndex + 1].substring(1);
}

export async function getParentNode ({ objectId }: ServerNode): Promise<ServerNode | null> {
const { Runtime, DOM } = clientsManager.getClient();

const parent = await Runtime.callFunctionOn({
functionDeclaration: `function () {
const el = this.assignedSlot || this;
return this.parentNode || el.host;
}`,
objectId,
});

if (parent.result.value !== null && parent.result.objectId)
return describeNode(DOM, parent.result.objectId || '');

return null;
}

export async function getParents (el: ServerNode): Promise<ServerNode[]> {
// TODO: check this method
const result = [];

let parent = await getParentNode(el);

while (parent) {
result.push(parent);

parent = await getParentNode(parent);
}

return result;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@ import Protocol from 'devtools-protocol/types/protocol';
import ExecutionContext from '../execution-context';
import AxisValues, { AxisValuesData, LeftTopValues } from '../../../../../../../shared/utils/values/axis-values';
import BoundaryValues, { BoundaryValuesData } from '../../../../../../../shared/utils/values/boundary-values';
import { findIframeByWindow } from './dom-utils';
import { findIframeByWindow, getIframeByElement } from './dom-utils';
import * as clientsManager from '../clients-manager';
import { ServerNode } from '../types';
import { PositionDimensions, ServerNode } from '../types';
import { describeNode } from './';

import {
getBoxModel,
getClientDimensions,
getDocumentScroll,
getElementPadding,
getElementDimensions,
getProperties,
} from './style-utils';

import { ElementRectangle } from '../../../../../../../client/core/utils/shared/types';


export async function getClientPosition (node: ServerNode): Promise<AxisValues<number>> {
const boxModel = await getBoxModel(node);

Expand All @@ -27,7 +31,7 @@ export async function getOffsetPosition (node: ServerNode): Promise<LeftTopValue
return { left: dimensions.left + left, top: dimensions.top + top };
}

export async function containsOffset (node: ServerNode, offsetX: number, offsetY: number): Promise<boolean> {
export async function containsOffset (node: ServerNode, offsetX?: number, offsetY?: number): Promise<boolean> {
const dimensions = await getClientDimensions(node);
const properties = await getProperties(node, 'scrollWidth', 'scrollHeight');

Expand Down Expand Up @@ -68,13 +72,16 @@ export async function getIframePointRelativeToParentFrame (iframePoint: AxisValu
return new AxisValues<number>(left, top);
}

export async function getElementFromPoint (point: AxisValuesData<number>): Promise<Protocol.DOM.ResolveNodeResponse | null> {
export async function getElementFromPoint (point: AxisValuesData<number>): Promise<ServerNode | null> {
const { DOM } = clientsManager.getClient();

try {
const { backendNodeId } = await DOM.getNodeForLocation({ x: point.x, y: point.y });

return DOM.resolveNode({ backendNodeId });
const result = await DOM.resolveNode({ backendNodeId });

if (result?.object.objectId)
return describeNode(DOM, result.object.objectId.toString());
}
catch {
// NOTE: TODO: for some reason this methods throws error for correct `point` values
Expand All @@ -84,6 +91,33 @@ export async function getElementFromPoint (point: AxisValuesData<number>): Promi
return null;
}

export async function getElementRectangle (node: ServerNode): Promise<ElementRectangle> {
const dimensions = await getElementDimensions(node);

return {
height: dimensions.height,
left: dimensions.left,
top: dimensions.top,
width: dimensions.width,
};
}

export async function getClientDimensions (node: ServerNode): Promise<PositionDimensions> {
const elementDimensions = await getElementDimensions(node);
const parentFrame = await getIframeByElement(node);

if (parentFrame) {
const frameBoxModel = await getBoxModel(parentFrame);

elementDimensions.left -= frameBoxModel.content[0];
elementDimensions.top -= frameBoxModel.content[1];
elementDimensions.bottom -= frameBoxModel.content[1];
elementDimensions.right -= frameBoxModel.content[0];
}

return elementDimensions;
}

export async function getWindowPosition (): Promise<AxisValues<number>> {
const { Runtime } = clientsManager.getClient();

Expand All @@ -99,3 +133,8 @@ export async function getWindowPosition (): Promise<AxisValues<number>> {

return result.value;
}

// TODO: implement
export async function offsetToClientCoords (point: AxisValues<number>): Promise<AxisValues<number>> {
return point;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { LeftTopValues } from '../../../../../../../shared/utils/values/axis-val
import BoundaryValues, { BoundaryValuesData } from '../../../../../../../shared/utils/values/boundary-values';
import { Dictionary } from '../../../../../../../configuration/interfaces';
import Protocol from 'devtools-protocol/types/protocol';
import { getScrollingElement, getIframeByElement } from './dom-utils';
import { getScrollingElement } from './dom-utils';
import ExecutionContext from '../execution-context';
import * as clientsManager from '../clients-manager';
import { ServerNode, PositionDimensions } from '../types';
Expand Down Expand Up @@ -61,7 +61,7 @@ export async function getBoxModel (node: ServerNode): Promise<Protocol.DOM.BoxMo
return boxModel.model;
}

async function getElementDimensions (node: ServerNode): Promise<PositionDimensions> {
export async function getElementDimensions (node: ServerNode): Promise<PositionDimensions> {
// NOTE: for some reason this method call is required for CSS.getComputedStyleForNode
// TODO: remove this line after the problem is clear
await clientsManager.getClient().DOM.getDocument({ });
Expand Down Expand Up @@ -106,22 +106,6 @@ async function getElementDimensions (node: ServerNode): Promise<PositionDimensio
};
}

export async function getClientDimensions (node: ServerNode): Promise<PositionDimensions> {
const elementDimensions = await getElementDimensions(node);
const parentFrame = await getIframeByElement(node);

if (parentFrame) {
const frameBoxModel = await getBoxModel(parentFrame);

elementDimensions.left -= frameBoxModel.content[0];
elementDimensions.top -= frameBoxModel.content[1];
elementDimensions.bottom -= frameBoxModel.content[1];
elementDimensions.right -= frameBoxModel.content[0];
}

return elementDimensions;
}

export async function getBordersWidth (node: ServerNode): Promise<BoundaryValuesData> {
const dimensions = await getElementDimensions(node);

Expand All @@ -136,6 +120,12 @@ export async function getElementScroll (node: ServerNode): Promise<LeftTopValues
return getScroll(node);
}

export async function hasScroll (node: ServerNode): Promise<boolean> {
const scroll = await getElementScroll(node);

return scroll.left > 0 || scroll.top > 0;
}

export async function getWindowDimensions (executionContext?: ExecutionContext): Promise<BoundaryValues> {
const { Runtime } = clientsManager.getClient();

Expand Down
5 changes: 3 additions & 2 deletions src/client/automation/get-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ function fromPoint (point/*: AxisValuesData<number>*/, underTopShadowUIElement)
let topElement = null;

return testCafeUI.hide(underTopShadowUIElement)
.then(() => {
topElement = positionUtils.getElementFromPoint(point.x, point.y);
.then(() => positionUtils.getElementFromPoint(point))
.then(el => {
topElement = el;

return testCafeUI.show(underTopShadowUIElement);
})
Expand Down
4 changes: 2 additions & 2 deletions src/client/automation/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import hammerhead from './deps/hammerhead';
import DispatchEventAutomation from './playback/dispatch-event';
import SetScrollAutomation from './playback/set-scroll';
import ScrollIntoViewAutomation from './playback/scroll-into-view';
import ClickAutomation from './playback/click';
import ClickAutomation from '../../shared/actions/automations/click';
import SelectChildClickAutomation from './playback/click/select-child';
import DblClickAutomation from './playback/dblclick';
import DragToOffsetAutomation from './playback/drag/to-offset';
Expand All @@ -23,7 +23,7 @@ import {
TypeOptions,
} from '../../test-run/commands/options';
import AutomationSettings from '../../shared/actions/automations/settings';
import { getOffsetOptions } from './utils/offsets';
import { getOffsetOptions } from '../../shared/actions/utils/offsets';
import { getNextFocusableElement } from './playback/press/utils';
import SHORTCUT_TYPE from './playback/press/shortcut-type';
import { getSelectionCoordinatesByPosition } from './playback/select/utils';
Expand Down
Loading

0 comments on commit f9a6786

Please # to comment.