Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: prevented waiting element in the second time if an error was raised in the first #7111

Merged
merged 5 commits into from Jun 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions src/client-functions/selectors/selector-builder.js
Expand Up @@ -156,6 +156,7 @@ export default class SelectorBuilder extends ClientFunctionBuilder {
apiFnChain: this.options.apiFnChain,
visibilityCheck: !!this.options.visibilityCheck,
timeout: this.options.timeout,
strictError: this.options.strictError,
});
}

Expand Down
16 changes: 10 additions & 6 deletions src/client/driver/driver.js
Expand Up @@ -50,7 +50,6 @@ import {
CurrentIframeIsNotLoadedError,
CurrentIframeNotFoundError,
CurrentIframeIsInvisibleError,
CannotObtainInfoForElementSpecifiedBySelectorError,
UncaughtErrorInCustomClientScriptCode,
UncaughtErrorInCustomClientScriptLoadedFromModule,
ChildWindowIsNotLoadedError,
Expand Down Expand Up @@ -112,7 +111,11 @@ import getExecutorResultDriverStatus from './command-executors/get-executor-resu
import SelectorExecutor from './command-executors/client-functions/selector-executor';
import SelectorElementActionTransform from './command-executors/client-functions/replicator/transforms/selector-element-action-transform';
import BarriersComplex from '../../shared/barriers/complex-barrier';
import createErrorCtorCallback from '../../shared/errors/selector-error-ctor-callback';
import createErrorCtorCallback, {
getCannotObtainInfoErrorCtor,
getInvisibleErrorCtor,
getNotFoundErrorCtor,
} from '../../shared/errors/selector-error-ctor-callback';
import './command-executors/actions-initializer';

const settings = hammerhead.settings;
Expand Down Expand Up @@ -1207,14 +1210,15 @@ export default class Driver extends serviceUtils.EventEmitter {

_onExecuteSelectorCommand (command) {
const startTime = this.contextStorage.getItem(SELECTOR_EXECUTION_START_TIME) || new DateCtor();
const elementNotFoundOrNotVisible = fn => new CannotObtainInfoForElementSpecifiedBySelectorError(null, fn);
const createError = command.needError ? elementNotFoundOrNotVisible : null;
const elementNotFoundOrNotVisible = createErrorCtorCallback(getCannotObtainInfoErrorCtor());
const elementNotFound = command.strictError ? createErrorCtorCallback(getNotFoundErrorCtor()) : elementNotFoundOrNotVisible;
const elementIsInvisible = command.strictError ? createErrorCtorCallback(getInvisibleErrorCtor()) : elementNotFoundOrNotVisible;

getExecuteSelectorResultDriverStatus(command,
this.selectorTimeout,
startTime,
createError,
createError,
command.needError ? elementNotFound : null,
command.needError ? elementIsInvisible : null,
this.statusBar)
.then(driverStatus => {
this.contextStorage.setItem(SELECTOR_EXECUTION_START_TIME, null);
Expand Down
17 changes: 17 additions & 0 deletions src/shared/errors/selector-error-ctor-callback.ts
Expand Up @@ -2,6 +2,23 @@ import { AutomationErrorCtor } from '../types';
import { FnInfo, SelectorErrorCb } from '../../client/driver/command-executors/client-functions/types';
import * as Errors from './index';

export function getInvisibleErrorCtor (elementName?: string): AutomationErrorCtor | string {
return !elementName ? 'ActionElementIsInvisibleError' : {
name: 'ActionAdditionalElementIsInvisibleError',
firstArg: elementName,
};
}

export function getNotFoundErrorCtor (elementName?: string): AutomationErrorCtor | string {
return !elementName ? 'ActionElementNotFoundError' : {
name: 'ActionAdditionalElementNotFoundError',
firstArg: elementName,
};
}

export function getCannotObtainInfoErrorCtor (): AutomationErrorCtor | string {
return 'CannotObtainInfoForElementSpecifiedBySelectorError';
}

export default function createErrorCtorCallback (errCtor: AutomationErrorCtor | string): SelectorErrorCb {
// @ts-ignore
Expand Down
11 changes: 3 additions & 8 deletions src/shared/utils/elements-retriever.ts
Expand Up @@ -6,6 +6,7 @@ import {
ActionSelectorMatchesWrongNodeTypeError,
ActionAdditionalSelectorMatchesWrongNodeTypeError,
} from '../../shared/errors';
import { getInvisibleErrorCtor, getNotFoundErrorCtor } from '../errors/selector-error-ctor-callback';


export default class ElementsRetriever<T> {
Expand All @@ -27,14 +28,8 @@ export default class ElementsRetriever<T> {
this._ensureElementsPromise = this._ensureElementsPromise
.then(() => {
return this._executeSelectorFn(selector, {
invisible: !elementName ? 'ActionElementIsInvisibleError' : {
name: 'ActionAdditionalElementIsInvisibleError',
firstArg: elementName,
},
notFound: !elementName ? 'ActionElementNotFoundError' : {
name: 'ActionAdditionalElementNotFoundError',
firstArg: elementName,
},
invisible: getInvisibleErrorCtor(elementName),
notFound: getNotFoundErrorCtor(elementName),
}, this._ensureElementsStartTime);
})
.then(el => {
Expand Down
8 changes: 6 additions & 2 deletions src/test-run/commands/actions.js
Expand Up @@ -14,7 +14,11 @@ import {
CookieOptions,
} from './options';

import { initSelector, initUploadSelector } from './validations/initializers';
import {
initSelector,
initTypeSelector,
initUploadSelector,
} from './validations/initializers';
import { executeJsExpression } from '../execute-js-expression';
import { isJSExpression } from './utils';

Expand Down Expand Up @@ -217,7 +221,7 @@ export class TypeTextCommand extends ActionCommandBase {

_getAssignableProperties () {
return [
{ name: 'selector', init: initSelector, required: true },
{ name: 'selector', init: initTypeSelector, required: true },
{ name: 'text', type: nonEmptyStringArgument, required: true },
{ name: 'options', type: actionOptions, init: initTypeOptions, required: true },
];
Expand Down
1 change: 1 addition & 0 deletions src/test-run/commands/observation.d.ts
Expand Up @@ -21,6 +21,7 @@ export class ExecuteSelectorCommand extends ExecuteClientFunctionCommandBase {
public apiFnChain: string[];
public needError: boolean;
public index: number;
public strictError: boolean;
}

export class WaitCommand extends ActionCommandBase {
Expand Down
1 change: 1 addition & 0 deletions src/test-run/commands/observation.js
Expand Up @@ -56,6 +56,7 @@ export class ExecuteSelectorCommand extends ExecuteClientFunctionCommandBase {
{ name: 'apiFnChain' },
{ name: 'needError' },
{ name: 'index', defaultValue: 0 },
{ name: 'strictError' },
]);
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/test-run/commands/validations/initializers.js
Expand Up @@ -11,6 +11,13 @@ export function initUploadSelector (name, val, initOptions) {
return initSelector(name, val, initOptions);
}

export function initTypeSelector (name, val, initOptions) {
initOptions.needError = true;
initOptions.strictError = true;

return initSelector(name, val, initOptions);
}

export function initSelector (name, val, { testRun, ...options }) {
if (val instanceof ExecuteSelectorCommand)
return val;
Expand Down
17 changes: 12 additions & 5 deletions src/test-run/index.ts
Expand Up @@ -1044,12 +1044,12 @@ export default class TestRun extends AsyncEventEmitter {
command.generateScreenshotMark();
}

public async _adjustCommandOptionsAndEnvironment (command: CommandBase): Promise<void> {
public async _adjustCommandOptionsAndEnvironment (command: CommandBase, callsite: CallsiteRecord): Promise<void> {
if ((command as any).options?.confidential !== void 0)
return;

if (command.type === COMMAND_TYPE.typeText) {
const result = await this._internalExecuteCommand((command as any).selector);
const result = await this._internalExecuteCommand((command as any).selector, callsite);

if (!result)
return;
Expand Down Expand Up @@ -1095,14 +1095,21 @@ export default class TestRun extends AsyncEventEmitter {
let error = null;
let result = null;

await this._adjustCommandOptionsAndEnvironment(command);
const start = new Date().getTime();

try {
await this._adjustCommandOptionsAndEnvironment(command, callsite);
}
catch (err) {
error = err;
}

await this.emitActionEvent('action-start', actionArgs);

const start = new Date().getTime();

try {
result = await this._internalExecuteCommand(command, callsite);
if (!error)
result = await this._internalExecuteCommand(command, callsite);
}
catch (err) {
if (this.phase === TestRunPhase.pendingFinalization && err instanceof ExternalAssertionLibraryError)
Expand Down
18 changes: 18 additions & 0 deletions test/functional/fixtures/api/es-next/type/test.js
@@ -1,3 +1,4 @@
const config = require('../../../../config');
const expect = require('chai').expect;

// NOTE: we run tests in chrome only, because we mainly test server API functionality.
Expand Down Expand Up @@ -43,4 +44,21 @@ describe('[API] t.typeText()', function () {
expect(errs[0]).to.contains('> 19 | await t.typeText(NaN, \'a\');');
});
});

if (!config.proxyless) {
it('Should not execute selector twice for non-existing element due to "confidential" option (GH-6623)', function () {
return runTests('./testcafe-fixtures/type-test.js', 'Not found selector', {
shouldFail: true,
only: 'chrome',
selectorTimeout: 3000,
})
.catch(function (errs) {
expect(testReport.durationMs).lessThan(6000);
expect(errs[0]).to.contains(
'The specified selector does not match any element in the DOM tree.'
);
expect(errs[0]).to.contains('> 31 | await t.typeText(\'#not-found\', \'a\');');
});
});
}
});
Expand Up @@ -26,3 +26,7 @@ test('Incorrect action text', async t => {
test('Incorrect action options', async t => {
await t.typeText('#input', 'a', { replace: null, paste: null });
});

test('Not found selector', async t => {
await t.typeText('#not-found', 'a');
});
11 changes: 7 additions & 4 deletions test/server/test-run-commands-test.js
Expand Up @@ -69,8 +69,11 @@ function assertErrorMessage (fn, expectedErrMessage) {
expect(actualErr.message).eql(expectedErrMessage);
}

function makeSelector (str, skipVisibilityCheck) {
const builder = new SelectorBuilder(str, { visibilityCheck: !skipVisibilityCheck }, { instantiation: 'Selector' });
function makeSelector (str, skipVisibilityCheck, needError, strictError) {
const builder = new SelectorBuilder(str, {
visibilityCheck: !skipVisibilityCheck,
needError, strictError,
}, { instantiation: 'Selector' });
const command = builder.getCommand([]);

command.actionId = 'child-command-selector';
Expand Down Expand Up @@ -565,7 +568,7 @@ describe('Test run commands', () => {
expect(JSON.parse(JSON.stringify(command))).eql({
type: TYPE.typeText,
actionId: TYPE.typeText,
selector: makeSelector('#yo'),
selector: makeSelector('#yo', false, true, true),
text: 'testText',

options: {
Expand Down Expand Up @@ -598,7 +601,7 @@ describe('Test run commands', () => {
expect(JSON.parse(JSON.stringify(command))).eql({
type: TYPE.typeText,
actionId: TYPE.typeText,
selector: makeSelector('#yo'),
selector: makeSelector('#yo', false, true, true),
text: 'testText',

options: {
Expand Down