Skip to content

Commit

Permalink
feat: debug with selector option added (#8066)
Browse files Browse the repository at this point in the history
<!--
Thank you for your contribution.

Before making a PR, please read our contributing guidelines at

https://github.com/DevExpress/testcafe/blob/master/CONTRIBUTING.md#code-contribution

We recommend creating a *draft* PR, so that you can mark it as 'ready
for review' when you are done.
-->

## Purpose
The purpose of this PR is to allow passing selector to a t.debug call.
Something like this t.debug(Selector). Then the selector will be shown
in debug mode and highlighted.

## Approach
So overall, the main goal was to pass the selector argument from
server-side debug call to client-side selector-input-contaner.
1) src/test-run/index.ts Inside this file we have
_internalExecuteCommand method. There is debug command type handling
block. Inside of it we call _enqueueSetBreakpointCommand to which we
pass command.selector as a second argument. This way we transfer
Selector from debugCommand to breakpointCommand.
2) After the breakpointCommand is added to driverTaskQueue we can work
with it on client-side.
3) On client-side we pass selector to _debugSelector method of
SelectorInputContainer class.
4) In _debugSelector method we:
           -   remove any previously highlighted elements,
- get Selector name and assign it to this.value for
SelectorInputContainer,
- execute Selector to get elements for highlighting and indication,
           -  highlight and indicate elements of the Selector

## References
DevExpress/testcafe-private#163

## Pre-Merge TODO
- [x] Write tests for your proposed changes
- [x] Make sure that existing tests do not fail
  • Loading branch information
Bayheck authored Dec 19, 2023
1 parent 51b54c7 commit 61d6b4b
Show file tree
Hide file tree
Showing 27 changed files with 179 additions and 102 deletions.
2 changes: 1 addition & 1 deletion src/api/skip-js-errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
Dictionary, SkipJsErrorsCallback, SkipJsErrorsCallbackWithOptionsObject, SkipJsErrorsOptionsObject,
} from '../../configuration/interfaces';
import { parseRegExpString } from '../../utils/make-reg-exp';
import { ExecuteClientFunctionCommand } from '../../test-run/commands/observation';
import { ExecuteClientFunctionCommand } from '../../test-run/commands/execute-client-function';
import ClientFunctionBuilder from '../../client-functions/client-function-builder';

const SKIP_JS_ERRORS_OBJECT_FUNCTION = `
Expand Down
4 changes: 2 additions & 2 deletions src/api/test-controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -602,8 +602,8 @@ export default class TestController {
return new Assertion(actual, this, callsite);
}

[delegatedAPI(DebugCommand.methodName)] () {
return this.enqueueCommand(DebugCommand);
[delegatedAPI(DebugCommand.methodName)] (selector) {
return this.enqueueCommand(DebugCommand, { selector });
}

[delegatedAPI(SetTestSpeedCommand.methodName)] (speed) {
Expand Down
2 changes: 1 addition & 1 deletion src/client-functions/client-function-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { isNil as isNullOrUndefined, assign } from 'lodash';
import testRunTracker from '../api/test-run-tracker';
import functionBuilderSymbol from './builder-symbol';
import { createReplicator, FunctionTransform } from './replicator';
import { ExecuteClientFunctionCommand } from '../test-run/commands/observation';
import { ExecuteClientFunctionCommand } from '../test-run/commands/execute-client-function';
import compileClientFunction from '../compiler/compile-client-function';
import { APIError, ClientFunctionAPIError } from '../errors/runtime';
import { assertType, is } from '../errors/runtime/type-assertions';
Expand Down
2 changes: 1 addition & 1 deletion src/client-functions/selectors/selector-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ClientFunctionAPIError } from '../../errors/runtime';
import functionBuilderSymbol from '../builder-symbol';
import { RUNTIME_ERRORS } from '../../errors/types';
import { assertType, is } from '../../errors/runtime/type-assertions';
import { ExecuteSelectorCommand } from '../../test-run/commands/observation';
import { ExecuteSelectorCommand } from '../../test-run/commands/execute-client-function';
import defineLazyProperty from '../../utils/define-lazy-property';
import { addAPI, addCustomMethods } from './add-api';
import createSnapshotMethods from './create-snapshot-methods';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ExecuteSelectorCommand } from '../../../../test-run/commands/observation';
import { ExecuteSelectorCommand } from '../../../../test-run/commands/execute-client-function';
import { ExecuteSelectorFn } from '../../../../shared/types';
import NODE_TYPE_DESCRIPTIONS from '../../node-type-descriptions';
import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import ClientFunctionNodeTransform from './replicator/transforms/client-function
import evalFunction from './eval-function';
import { UncaughtErrorInClientFunctionCode } from '../../../../shared/errors/index';
import Replicator from 'replicator';
import { ExecuteClientFunctionCommand, ExecuteClientFunctionCommandBase } from '../../../../test-run/commands/observation';
import { ExecuteClientFunctionCommand, ExecuteClientFunctionCommandBase } from '../../../../test-run/commands/execute-client-function';
import { Dictionary } from '../../../../configuration/interfaces';
// @ts-ignore
import { Promise } from '../../deps/hammerhead';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import ClientFunctionExecutor from '../client-function-executor';
import createReplicator from '../replicator/index';
import FunctionTransform from '../replicator/transforms/function-transform';
import SelectorNodeTransform from '../replicator/transforms/selector-node-transform';
import { ExecuteSelectorCommand } from '../../../../../test-run/commands/observation';
import { ExecuteSelectorCommand } from '../../../../../test-run/commands/execute-client-function';
import {
SelectorErrorParams,
SelectorDependencies,
Expand Down
4 changes: 2 additions & 2 deletions src/client/driver/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -1527,10 +1527,10 @@ export default class Driver extends serviceUtils.EventEmitter {
});
}

_onSetBreakpointCommand ({ isTestError }) {
_onSetBreakpointCommand ({ isTestError, selector }) {
const showDebuggingStatusPromise = this.statusBar.showDebuggingStatus(isTestError);

this.selectorInspectorPanel.show();
this.selectorInspectorPanel.show(selector);

showDebuggingStatusPromise.then(debug => {
const stopAfterNextAction = debug === STATUS_BAR_DEBUG_ACTION.step;
Expand Down
13 changes: 7 additions & 6 deletions src/client/ui/selector-inspector-panel/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@ export default class SelectorInspectorPanel {
elementPicker = elementPicker;

constructor () {
this.element = createElementFromDescriptor(panel);

this.element = createElementFromDescriptor(panel);
this.selectorInputContainer = new SelectorInputContainer();
const pickButton = new PickButton();
const selectorInputContainer = new SelectorInputContainer();
const copyButton = new CopyButton(selectorInputContainer);
const container = new MainContainer(pickButton.element, selectorInputContainer.element, copyButton.element);
const copyButton = new CopyButton(this.selectorInputContainer);
const container = new MainContainer(pickButton.element, this.selectorInputContainer.element, copyButton.element);
const hideButton = new HideButton(this.element);

this.element.appendChild(container.element);
this.element.appendChild(hideButton.element);
}

show () {
show (selector) {
this.selectorInputContainer.debugSelector(selector);

if (!this.element.parentElement)
uiRoot.insertFirstChildToPanelsContainer(this.element);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import hammerhead from './../deps/hammerhead';
import testCafeCore from './../deps/testcafe-core';

import { createElementFromDescriptor } from './utils/create-element-from-descriptor';
import { getElementsBySelector } from './utils/get-elements-by-selector';
import { getElementsBySelector, executeSelector } from './utils/get-elements-by-selector';
import { castArray } from './utils/cast-array';

import * as descriptors from './descriptors';
import { elementPicker, ELEMENT_PICKED } from './element-picker';
Expand Down Expand Up @@ -159,4 +160,14 @@ export class SelectorInputContainer {
this._indicateMatches(elements);
this._highlightElements(elements);
}

async debugSelector (selector) {
highlighter.stopHighlighting();

this.value = selector.apiFnChain.join('');
const elements = castArray(await executeSelector(selector));

this._indicateMatches(elements);
this._highlightElements(elements);
}
}
7 changes: 7 additions & 0 deletions src/client/ui/selector-inspector-panel/utils/cast-array.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import hammerhead from '../../deps/hammerhead';

const nativeMethods = hammerhead.nativeMethods;

export function castArray (elements) {
return nativeMethods.isArray(elements) ? elements : [elements];
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ async function parseSelector (selector) {
return browser.parseSelector(communicationUrls.parseSelector, createNativeXHR, selector);
}

async function executeSelector (parsedSelector) {
export async function executeSelector (parsedSelector) {
const startTime = nativeMethods.date();
const selectorExecutor = new SelectorExecutor(parsedSelector, GLOBAL_TIMEOUT, startTime, createNotFoundError, createIsInvisibleError);
const elements = await selectorExecutor.getResult();
Expand Down
2 changes: 1 addition & 1 deletion src/reporter/command/command-formatter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isEmpty } from 'lodash';
import { ExecuteSelectorCommand, ExecuteClientFunctionCommand } from '../../test-run/commands/observation';
import { ExecuteSelectorCommand, ExecuteClientFunctionCommand } from '../../test-run/commands/execute-client-function';
import {
NavigateToCommand,
PressKeyCommand,
Expand Down
2 changes: 1 addition & 1 deletion src/shared/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* global globalThis */

import { ExecuteSelectorCommand } from '../test-run/commands/observation';
import { ExecuteSelectorCommand } from '../test-run/commands/execute-client-function';

export interface NativeMethods {
setTimeout: typeof globalThis.setTimeout;
Expand Down
2 changes: 1 addition & 1 deletion src/test-run/bookmark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {

import { CurrentIframeNotFoundError, CurrentIframeIsNotLoadedError } from '../errors/test-run';
import TestRun from './index';
import { ExecuteClientFunctionCommand, ExecuteSelectorCommand } from './commands/observation';
import { ExecuteClientFunctionCommand, ExecuteSelectorCommand } from './commands/execute-client-function';
import Role, { RedirectUrl } from '../role/role';
import { DEFAULT_SPEED_VALUE } from '../configuration/default-values';
import BrowserConsoleMessages from './browser-console-messages';
Expand Down
2 changes: 1 addition & 1 deletion src/test-run/commands/actions.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ActionCommandBase, CommandBase } from './base';
import { ExecuteClientFunctionCommand, ExecuteSelectorCommand } from './observation';
import { ExecuteClientFunctionCommand, ExecuteSelectorCommand } from './execute-client-function';

import {
ActionOptions,
Expand Down
2 changes: 1 addition & 1 deletion src/test-run/commands/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import {
} from './validations/argument';

import { SetNativeDialogHandlerCodeWrongTypeError } from '../../errors/test-run';
import { ExecuteClientFunctionCommand } from './observation';
import { ExecuteClientFunctionCommand } from './execute-client-function';
import { camelCase } from 'lodash';
import {
prepareSkipJsErrorsOptions,
Expand Down
24 changes: 24 additions & 0 deletions src/test-run/commands/execute-client-function.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { CommandBase } from './base';
import TestRun from '../index';

declare class ExecuteClientFunctionCommandBase extends CommandBase {
public constructor(obj: object, testRun: TestRun, type: string);
public instantiationCallsiteName: string;
public fnCode: string;
public args: string[];
public dependencies: string[];
}

export class ExecuteClientFunctionCommand extends ExecuteClientFunctionCommandBase {
public constructor(obj: object, testRun: TestRun);
}

export class ExecuteSelectorCommand extends ExecuteClientFunctionCommandBase {
public constructor(obj: object, testRun: TestRun);
public visibilityCheck: boolean;
public timeout?: number;
public apiFnChain: string[];
public needError: boolean;
public index: number;
public strictError: boolean;
}
44 changes: 44 additions & 0 deletions src/test-run/commands/execute-client-function.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import TYPE from './type';
import { ActionCommandBase } from './base';

export class ExecuteClientFunctionCommandBase extends ActionCommandBase {
constructor (obj, testRun, type) {
super(obj, testRun, type, false);
}

getAssignableProperties () {
return [
{ name: 'instantiationCallsiteName', defaultValue: '' },
{ name: 'fnCode', defaultValue: '' },
{ name: 'args', defaultValue: [] },
{ name: 'dependencies', defaultValue: [] },
];
}
}

export class ExecuteClientFunctionCommand extends ExecuteClientFunctionCommandBase {
static methodName = TYPE.executeClientFunction;

constructor (obj, testRun) {
super(obj, testRun, TYPE.executeClientFunction);
}
}

export class ExecuteSelectorCommand extends ExecuteClientFunctionCommandBase {
static methodName = TYPE.executeSelector;

constructor (obj, testRun) {
super(obj, testRun, TYPE.executeSelector);
}

getAssignableProperties () {
return [
{ name: 'visibilityCheck', defaultValue: false },
{ name: 'timeout', defaultValue: null },
{ name: 'apiFnChain' },
{ name: 'needError' },
{ name: 'index', defaultValue: 0 },
{ name: 'strictError' },
];
}
}
30 changes: 6 additions & 24 deletions src/test-run/commands/observation.d.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,13 @@
import { CommandBase, ActionCommandBase } from './base';
import { ActionCommandBase } from './base';
import { ExecuteClientFunctionCommand } from './execute-client-function';
import TestRun from '../index';

declare class ExecuteClientFunctionCommandBase extends CommandBase {
public constructor(obj: object, testRun: TestRun, type: string);
public instantiationCallsiteName: string;
public fnCode: string;
public args: string[];
public dependencies: string[];
}

export class ExecuteClientFunctionCommand extends ExecuteClientFunctionCommandBase {
public constructor(obj: object, testRun: TestRun);
}

export class ExecuteSelectorCommand extends ExecuteClientFunctionCommandBase {
public constructor(obj: object, testRun: TestRun);
public visibilityCheck: boolean;
public timeout?: number;
public apiFnChain: string[];
public needError: boolean;
public index: number;
public strictError: boolean;
}

export class WaitCommand extends ActionCommandBase {
public constructor(obj: object, testRun: TestRun);
public timeout: number;
}

export class DebugCommand extends ActionCommandBase { }
export class DebugCommand extends ActionCommandBase {
public constructor(obj: object, testRun: TestRun, validateProperties?: boolean);
public selector?: ExecuteClientFunctionCommand;
}
53 changes: 13 additions & 40 deletions src/test-run/commands/observation.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import TYPE from './type';
import { ActionCommandBase } from './base';
import { positiveIntegerArgument } from './validations/argument';
import { camelCase } from 'lodash';
import { initSelector } from './validations/initializers';


// Initializers
function initDebugOptions (name, val, options) {
return initSelector(name, val, Object.assign({}, options,
{ skipVisibilityCheck: true, collectionMode: true }
));
}

// Commands
export class WaitCommand extends ActionCommandBase {
Expand All @@ -18,52 +27,16 @@ export class WaitCommand extends ActionCommandBase {
}
}

export class ExecuteClientFunctionCommandBase extends ActionCommandBase {
constructor (obj, testRun, type) {
super(obj, testRun, type, false);
}

getAssignableProperties () {
return [
{ name: 'instantiationCallsiteName', defaultValue: '' },
{ name: 'fnCode', defaultValue: '' },
{ name: 'args', defaultValue: [] },
{ name: 'dependencies', defaultValue: [] },
];
}
}

export class ExecuteClientFunctionCommand extends ExecuteClientFunctionCommandBase {
static methodName = TYPE.executeClientFunction;

constructor (obj, testRun) {
super(obj, testRun, TYPE.executeClientFunction);
}
}

export class ExecuteSelectorCommand extends ExecuteClientFunctionCommandBase {
static methodName = TYPE.executeSelector;
export class DebugCommand extends ActionCommandBase {
static methodName = camelCase(TYPE.debug);

constructor (obj, testRun) {
super(obj, testRun, TYPE.executeSelector);
super(obj, testRun, TYPE.debug);
}

getAssignableProperties () {
return [
{ name: 'visibilityCheck', defaultValue: false },
{ name: 'timeout', defaultValue: null },
{ name: 'apiFnChain' },
{ name: 'needError' },
{ name: 'index', defaultValue: 0 },
{ name: 'strictError' },
{ name: 'selector', init: initDebugOptions, required: false },
];
}
}

export class DebugCommand extends ActionCommandBase {
static methodName = camelCase(TYPE.debug);

constructor () {
super(null, null, TYPE.debug);
}
}
3 changes: 2 additions & 1 deletion src/test-run/commands/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ export class HideAssertionRetriesStatusCommand {
}

export class SetBreakpointCommand {
constructor (isTestError) {
constructor (isTestError, selector) {
this.type = TYPE.setBreakpoint;
this.isTestError = isTestError;
this.selector = selector;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/test-run/commands/validations/initializers.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import SelectorBuilder from '../../../client-functions/selectors/selector-builder';
import { ActionSelectorError } from '../../../errors/test-run';
import { APIError } from '../../../errors/runtime';
import { ExecuteSelectorCommand } from '../observation';
import { ExecuteSelectorCommand } from '../execute-client-function';
import { executeJsExpression } from '../../execute-js-expression';
import { isJSExpression } from '../utils';

Expand Down
Loading

0 comments on commit 61d6b4b

Please sign in to comment.