Skip to content

Commit

Permalink
refactor, add action name to command, add actionResult to reportTestA…
Browse files Browse the repository at this point in the history
…ctionDone
  • Loading branch information
Artem-Babich committed Dec 8, 2022
1 parent 3b8f5db commit 051dec3
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 52 deletions.
File renamed without changes.
2 changes: 1 addition & 1 deletion src/api/test-controller/custom-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default class CustomActions {
this[delegatedAPI(name)] = (...args) => {
const callsite = getCallsiteForMethod(name) || void 0;

return this._testController._enqueueCommand(RunCustomActionCommand, { fn, args }, this._validateCommand, callsite);
return this._testController._enqueueCommand(RunCustomActionCommand, { fn, args, name }, this._validateCommand, callsite);
};
});

Expand Down
40 changes: 5 additions & 35 deletions src/api/wrap-custom-action.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,12 @@
import TestController from './test-controller';
import testRunTracker from './test-run-tracker';
import TestRun from '../test-run';
import TestCafeErrorList from '../errors/error-list';
import { MissingAwaitError } from '../errors/test-run';
import addRenderedWarning from '../notifications/add-rendered-warning';
import WARNING_MESSAGES from '../notifications/warning-message';
import { addErrors, addWarnings } from './test-controller/add-errors';
import wrapTestFunction, { WrapTestFunctionExecutorArguments } from './wrap-test-function';

export default function wrapCustomAction (fn: Function): Function {
return async (testRun: TestRun, functionArgs: any) => {
let result = null;
const errList = new TestCafeErrorList();

testRun.controller = new TestController(testRun);

const executor = async function ({ testRun, functionArgs }: WrapTestFunctionExecutorArguments): Promise<any> {
const markeredfn = testRunTracker.addTrackingMarkerToFunction(testRun.id, fn, testRun.controller);

testRun.observedCallsites.clear();
testRunTracker.ensureEnabled();

try {
result = await markeredfn(...functionArgs);
}
catch (err) {
errList.addError(err);
}

if (!errList.hasUncaughtErrorsInTestCode) {
for (const { callsite, actionId } of testRun.observedCallsites.awaitedSnapshotWarnings.values())
addRenderedWarning(testRun.warningLog, { message: WARNING_MESSAGES.excessiveAwaitInAssertion, actionId }, callsite);

addWarnings(testRun.observedCallsites.unawaitedSnapshotCallsites, WARNING_MESSAGES.missingAwaitOnSnapshotProperty, testRun);
addErrors(testRun.observedCallsites.callsitesWithoutAwait, MissingAwaitError, errList);
}

if (errList.hasErrors)
throw errList;

return result;
return await markeredfn(...functionArgs);
};

return wrapTestFunction(fn, executor);
}
37 changes: 26 additions & 11 deletions src/api/wrap-test-function.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,48 @@
import TestController from './test-controller';
import testRunTracker from './test-run-tracker';
import TestRun from '../test-run';
import TestController from './test-controller';
import TestCafeErrorList from '../errors/error-list';
import { MissingAwaitError } from '../errors/test-run';
import addRenderedWarning from '../notifications/add-rendered-warning';
import WARNING_MESSAGES from '../notifications/warning-message';
import { addErrors, addWarnings } from './test-controller/add-errors';
import { addErrors, addWarnings } from './test-controller/add-message';

export interface WrapTestFunctionExecutorArguments {
testRun: TestRun;
functionArgs: any[];
fn: Function;
}

export default function wrapTestFunction (fn: Function): Function {
return async (testRun: TestRun) => {
let result = null;
const errList = new TestCafeErrorList();
const markeredfn = testRunTracker.addTrackingMarkerToFunction(testRun.id, fn);
const defaultExecutor = async function ({ testRun, fn }: WrapTestFunctionExecutorArguments): Promise<any> {
const markeredfn = testRunTracker.addTrackingMarkerToFunction(testRun.id, fn);

return await markeredfn(testRun.controller);
};

export default function wrapTestFunction (fn: Function, executor: Function = defaultExecutor): Function {
return async (testRun: TestRun, functionArgs: any) => {
let result = null;
const errList = new TestCafeErrorList();

testRun.controller = new TestController(testRun);

testRun.observedCallsites.clear();

testRunTracker.ensureEnabled();

try {
result = await markeredfn(testRun.controller);
result = await executor({ fn, functionArgs, testRun });
}
catch (err) {
errList.addError(err);
}

if (!errList.hasUncaughtErrorsInTestCode) {
for (const { callsite, actionId } of testRun.observedCallsites.awaitedSnapshotWarnings.values())
addRenderedWarning(testRun.warningLog, { message: WARNING_MESSAGES.excessiveAwaitInAssertion, actionId }, callsite);
for (const { callsite, actionId } of testRun.observedCallsites.awaitedSnapshotWarnings.values()) {
addRenderedWarning(testRun.warningLog, {
message: WARNING_MESSAGES.excessiveAwaitInAssertion,
actionId,
}, callsite);
}

addWarnings(testRun.observedCallsites.unawaitedSnapshotCallsites, WARNING_MESSAGES.missingAwaitOnSnapshotProperty, testRun);
addErrors(testRun.observedCallsites.callsitesWithoutAwait, MissingAwaitError, errList);
Expand All @@ -40,3 +54,4 @@ export default function wrapTestFunction (fn: Function): Function {
return result;
};
}

2 changes: 1 addition & 1 deletion src/errors/runtime/templates.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion src/reporter/command/command-formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { isEmpty } from 'lodash';
import { ExecuteSelectorCommand, ExecuteClientFunctionCommand } from '../../test-run/commands/observation';
import {
NavigateToCommand,
PressKeyCommand,
PressKeyCommand, RunCustomActionCommand,
SetNativeDialogHandlerCommand,
TypeTextCommand,
UseRoleCommand,
Expand Down Expand Up @@ -54,6 +54,9 @@ export class CommandFormatter {
else
this._assignProperties(this._command, formattedCommand);

if (this._command instanceof RunCustomActionCommand)
this._assignCustomActionResult(formattedCommand);

this._maskConfidentialInfo(formattedCommand);

return formattedCommand;
Expand Down Expand Up @@ -125,6 +128,11 @@ export class CommandFormatter {
return command.url;
}

private _assignCustomActionResult (formatedCommand: FormattedCommand) :void {
if (this._result !== void 0)
formatedCommand.actionResult = this._result;
}

private _assignProperties (command: CommandBase, formattedCommand: FormattedCommand): void {
if (!this._command.getReportedProperties)
return;
Expand Down
2 changes: 2 additions & 0 deletions src/test-run/commands/actions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,8 @@ export class RemoveRequestHooksCommand extends ActionCommandBase {
export class RunCustomActionCommand extends ActionCommandBase {
public constructor (obj: object, testRun: TestRun, validateProperties: boolean);
public fn: Function;
public name: string;
public args: any;
public actionResult: any;
}

1 change: 1 addition & 0 deletions src/test-run/commands/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,7 @@ export class RunCustomActionCommand extends ActionCommandBase {
getAssignableProperties () {
return [
{ name: 'fn', type: functionArgument, required: true },
{ name: 'name', type: stringArgument, required: true },
{ name: 'args', required: false },
];
}
Expand Down
5 changes: 5 additions & 0 deletions test/functional/fixtures/custom-actions/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ async function typeToInputAndCheckResult (inputSelector, buttonSelector, resultS
.expect(await this.custom.getSpanTextBySelector(resultSelector)).eql(inputText);
}

function getTextValue () {
return 'some text';
}

module.exports = {
getSpanTextBySelector,
clickBySelector,
typeTextAndClickButton,
typeToInputAndCheckResult,
getTextValue,
};
76 changes: 73 additions & 3 deletions test/functional/fixtures/custom-actions/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ const {
clickBySelector,
getSpanTextBySelector,
typeTextAndClickButton,
typeToInputAndCheckResult,
typeToInputAndCheckResult, getTextValue,
} = require('./actions');

const { expect } = require('chai');
const config = require('../../config');
const { expect } = require('chai');
const config = require('../../config');
const { createReporter } = require('../../utils/reporter');

(config.experimentalDebug ? describe.skip : describe)('[API] Custom Actions', function () {
it('Should run custom click action', function () {
Expand Down Expand Up @@ -40,6 +41,14 @@ const config = require('../../config');
});
});

it('Should run non-async custom action', function () {
return runTests('./testcafe-fixtures/index.js', 'Should run non-async custom action', {
customActions: {
getTextValue,
},
});
});

it('Should throw an exception inside custom action', function () {
return runTests('./testcafe-fixtures/index.js', 'Should throw an exception inside custom action', {
customActions: { clickBySelector },
Expand All @@ -58,5 +67,66 @@ const config = require('../../config');
expect(errs[0]).contains('TypeError: t.custom.clickBySelector is not a function');
});
});

it('Should report all actions in correct order', function () {
function ReporterRecord (phase, actionName, command) {
this.phase = phase;
this.actionName = actionName;
if (command.type !== 'run-custom-action')
return this;

delete command.actionId;
delete command.fn;
delete command.args;

this.command = command;
}

const result = [];
const expectedResult = [
{ phase: 'start', actionName: 'runCustomAction', command: { type: 'run-custom-action', name: 'typeToInputAndCheckResult' } },
{ phase: 'start', actionName: 'runCustomAction', command: { type: 'run-custom-action', name: 'typeTextAndClickButton' } },
{ phase: 'start', actionName: 'typeText' },
{ phase: 'end', actionName: 'typeText' },
{ phase: 'start', actionName: 'click' },
{ phase: 'end', actionName: 'click' },
{ phase: 'end', actionName: 'runCustomAction', command: { type: 'run-custom-action', name: 'typeTextAndClickButton' } },
{ phase: 'start', actionName: 'runCustomAction', command: { type: 'run-custom-action', name: 'getSpanTextBySelector' } },
{ phase: 'start', actionName: 'execute-selector' },
{ phase: 'end', actionName: 'execute-selector' },
{
phase: 'end',
actionName: 'runCustomAction',
command: {
type: 'run-custom-action',
name: 'getSpanTextBySelector',
actionResult: 'Some text',
},
},
{ phase: 'start', actionName: 'eql' },
{ phase: 'end', actionName: 'eql' },
{ phase: 'end', actionName: 'runCustomAction', command: { type: 'run-custom-action', name: 'typeToInputAndCheckResult' } },
];

const reporter = createReporter({
reportTestActionStart: (name, { command }) => {
result.push(new ReporterRecord('start', name, command));
},
reportTestActionDone: (name, { command }) => {
result.push(new ReporterRecord('end', name, command));
},
});

return runTests('./testcafe-fixtures/index.js', 'Should run custom action inside another custom action', {
customActions: {
typeToInputAndCheckResult,
typeTextAndClickButton,
getSpanTextBySelector,
},
reporter,
}).then(() => {
expect(result).to.deep.equal(expectedResult);
});
});
});

Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ test('Should run custom action inside another custom action', async t => {
await t.custom.typeToInputAndCheckResult('#input1', '#button2', '#result2', 'Some text');
});

test('Should run non-async custom action', async t => {
const result = await t.custom.getTextValue();

await t.expect(result).eql('some text');
});

test('Should throw an exception inside custom action', async t => {
await t.custom.clickBySelector('blablabla');
});

0 comments on commit 051dec3

Please sign in to comment.