Skip to content

Commit

Permalink
report only modified options in reportActionDone(closes #4926) (#4931)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexKamaev committed Apr 6, 2020
1 parent 6f0cb7e commit b4b2b67
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 28 deletions.
45 changes: 38 additions & 7 deletions src/reporter/command/command-formatter.ts
@@ -1,7 +1,21 @@
import { isEmpty } from 'lodash';
import { ExecuteSelectorCommand, ExecuteClientFunctionCommand } from '../../test-run/commands/observation';
import { NavigateToCommand, SetNativeDialogHandlerCommand, UseRoleCommand } from '../../test-run/commands/actions';
import { createReplicator, SelectorNodeTransform } from '../../client-functions/replicator';
import { Command, FormattedCommand, SelectorInfo } from './interfaces';
import { Dictionary } from '../../configuration/interfaces';
import diff from '../../utils/diff';

import {
ActionOptions,
ResizeToFitDeviceOptions,
AssertionOptions
} from '../../test-run/commands/options';


function isCommandOptions (obj: object): boolean {
return obj instanceof ActionOptions || obj instanceof ResizeToFitDeviceOptions || obj instanceof AssertionOptions;
}

export class CommandFormatter {
private _elements: HTMLElement[] = [];
Expand Down Expand Up @@ -49,18 +63,22 @@ export class CommandFormatter {

private _prepareSelector (command: Command, propertyName: string): SelectorInfo {
const selectorChain = command.apiFnChain as string[];
const expression = selectorChain.join('');

const expression = selectorChain.join('');
const result: SelectorInfo = { expression };

let element = null;

if (this._result)
element = this._getElementByPropertyName(propertyName);

if (element)
return { expression, element };
result.element = element;

if (command.timeout)
result.timeout = command.timeout;

return { expression };
return result;
}

private _prepareClientFunction (command: Command): object {
Expand Down Expand Up @@ -91,12 +109,18 @@ export class CommandFormatter {
const sourceProperties = this._command._getAssignableProperties().map(prop => prop.name);

sourceProperties.forEach((key: string) => {
const prop = this._command[key];
const property = this._command[key];

if (prop instanceof ExecuteSelectorCommand)
formattedCommand[key] = this._prepareSelector(prop, key);
if (property instanceof ExecuteSelectorCommand)
formattedCommand[key] = this._prepareSelector(property, key);
else if (isCommandOptions(property)) {
const modifiedOptions = CommandFormatter._getModifiedOptions(property);

if (!isEmpty(modifiedOptions))
formattedCommand[key] = modifiedOptions;
}
else
formattedCommand[key] = prop;
formattedCommand[key] = property;
});
}

Expand All @@ -108,4 +132,11 @@ export class CommandFormatter {

this._elements = Array.isArray(decoded) ? decoded : [decoded];
}

private static _getModifiedOptions (commandOptions: object): Dictionary<object> | null {
const constructor = commandOptions.constructor as ObjectConstructor;
const defaultOptions = new constructor();

return diff(defaultOptions as Dictionary<object>, commandOptions as Dictionary<object>);
}
}
1 change: 1 addition & 0 deletions src/reporter/command/interfaces.ts
Expand Up @@ -12,5 +12,6 @@ export interface FormattedCommand {

export interface SelectorInfo {
expression: string;
timeout?: number;
element?: HTMLElement;
}
38 changes: 38 additions & 0 deletions src/utils/diff.ts
@@ -0,0 +1,38 @@
import { set, isObjectLike } from 'lodash';
import { Dictionary } from '../configuration/interfaces';

function getFullPropertyPath (property: string, parentProperty: string): string {
if (parentProperty)
return `${parentProperty}.${property}`;

return property;
}

function diff (source: Dictionary<object>, modified: Dictionary<object>, result: Dictionary<object>, parentProperty: string = ''): void {
for (const property in source) {
const fullPropertyPath = getFullPropertyPath(property, parentProperty);

if (!modified.hasOwnProperty(property))
continue;

const sourceValue = source[property] as Dictionary<object>;
const modifiedValue = modified[property] as Dictionary<object>;

if (sourceValue !== modifiedValue) {
if (isObjectLike(sourceValue) && isObjectLike(modifiedValue))
diff(sourceValue, modifiedValue, result, fullPropertyPath);
else
set(result, fullPropertyPath, modifiedValue);
}
}
}

export default (source: Dictionary<object>, modified: Dictionary<object>) => {
const result = {};

if (isObjectLike(source) && isObjectLike(modified))
diff(source, modified, result);

return result;

};
20 changes: 9 additions & 11 deletions test/functional/fixtures/reporter/test.js
Expand Up @@ -8,8 +8,6 @@ const {
createSyncTestStream
} = require('../../utils/stream');

const { ClickOptions, AssertionOptions } = require('../../../../lib/test-run/commands/options');

describe('Reporter', () => {
const stdoutWrite = process.stdout.write;
const stderrWrite = process.stderr.write;
Expand Down Expand Up @@ -273,8 +271,10 @@ describe('Reporter', () => {
action: 'done',
command: {
type: 'click',
options: new ClickOptions(),
selector: 'Selector(\'#target\')'
selector: 'Selector(\'#target\')',
options: {
offsetX: 10
}
},
test: {
id: 'test-id',
Expand Down Expand Up @@ -305,7 +305,6 @@ describe('Reporter', () => {
action: 'done',
command: {
type: 'click',
options: new ClickOptions(),
selector: 'Selector(\'#non-existing-target\')'
},
err: 'E24'
Expand Down Expand Up @@ -334,7 +333,9 @@ describe('Reporter', () => {
expected: true,
expected2: void 0,
message: 'assertion message',
options: new AssertionOptions({ timeout: 100 })
options: {
timeout: 100
}
}
},
]);
Expand All @@ -360,8 +361,7 @@ describe('Reporter', () => {
assertionType: 'eql',
expected: 'target',
expected2: void 0,
message: null,
options: new AssertionOptions()
message: null
}
},
]);
Expand Down Expand Up @@ -486,7 +486,6 @@ describe('Reporter', () => {
name: 'click',
action: 'done',
command: {
options: new ClickOptions(),
selector: 'Selector(\'#target\')',
type: 'click'
},
Expand Down Expand Up @@ -537,7 +536,6 @@ describe('Reporter', () => {
name: 'click',
action: 'done',
command: {
options: new ClickOptions(),
selector: 'Selector(\'#non-existing-element\')',
type: 'click'
},
Expand Down Expand Up @@ -585,7 +583,7 @@ describe('Reporter', () => {
describe('Action snapshots', () => {
it('Basic', () => {
const expected = [
{ expression: 'Selector(\'#input\')', element: { tagName: 'input', attributes: { value: '100', type: 'text', id: 'input' } } },
{ expression: 'Selector(\'#input\')', timeout: 11000, element: { tagName: 'input', attributes: { value: '100', type: 'text', id: 'input' } } },
{ expression: 'Selector(\'#obscuredInput\')', element: { tagName: 'div', attributes: { id: 'fixed' } } },
{ expression: 'Selector(\'#obscuredInput\')', element: { tagName: 'div', attributes: { id: 'fixed' } } },
{ expression: 'Selector(\'#obscuredDiv\')', element: { tagName: 'div', attributes: { id: 'obscuredDiv' } } },
Expand Down
Expand Up @@ -23,7 +23,7 @@ test('Simple test', async t => {
});

test('Simple command test', async t => {
await t.click(Selector('#target'));
await t.click(Selector('#target'), { offsetX: 10 });
});

test('Simple command err test', async t => {
Expand Down
Expand Up @@ -4,7 +4,7 @@ fixture `Reporter snapshots`
.page `http://localhost:3000/fixtures/reporter/pages/snapshots.html`;

test('Basic', async t => {
await t.click('#input');
await t.click(Selector('#input', { timeout: 11000 }));
await t.click('#obscuredInput');
await t.dragToElement('#obscuredInput', '#obscuredDiv');
await t.selectEditableContent('#p1', '#p2');
Expand Down
11 changes: 7 additions & 4 deletions test/server/data/test-controller-reporter-expected/index.js
Expand Up @@ -6,7 +6,6 @@ const mouseOptions = Object.assign({
modifiers: {
alt: true,
ctrl: true,
meta: true,
shift: true,
},
offsetX: 1,
Expand All @@ -16,8 +15,7 @@ const mouseOptions = Object.assign({
const clickOptions = Object.assign({ caretPos: 1 }, mouseOptions);

const dragToElementOptions = Object.assign({
destinationOffsetX: 3,
destinationOffsetY: 4
destinationOffsetX: 3
}, mouseOptions);

const typeTextOptions = Object.assign({
Expand Down Expand Up @@ -347,7 +345,12 @@ module.exports = [
selector: { expression:'Selector(\'#target\')' },
path: 'screenshotPath',
type: 'take-element-screenshot',
options: new ElementScreenshotOptions()
options: {
includeMargins: true,
crop: {
top: -100
}
}
},
test: {
id: 'test-id',
Expand Down
96 changes: 92 additions & 4 deletions test/server/test-controller-events-test.js
Expand Up @@ -64,16 +64,31 @@ const options = {
modifiers: {
alt: true,
ctrl: true,
meta: true,
shift: true
},
offsetX: 1,
offsetY: 2,
destinationOffsetX: 3,
destinationOffsetY: 4,
speed: 1,
replace: true,
paste: true,
paste: true
};

const actionsWithoutOptions = {
click: ['#target'],
rightClick: ['#target'],
doubleClick: ['#target'],
hover: ['#target'],
drag: ['#target', 100, 200],
dragToElement: ['#target', '#target'],
typeText: ['#input', 'test'],
selectText: ['#input', 1, 3],
selectTextAreaContent: ['#textarea', 1, 2, 3, 4],
selectEditableContent: ['#contenteditable', '#contenteditable'],
pressKey: ['enter'],
takeScreenshot: [{ path: 'screenshotPath', fullPage: true }],
takeElementScreenshot: ['#target', 'screenshotPath'],
resizeWindowToFitDevice: ['Sony Xperia Z']
};

const actions = {
Expand All @@ -93,7 +108,7 @@ const actions = {
setFilesToUpload: ['#file', '../test.js'],
clearUpload: ['#file'],
takeScreenshot: [{ path: 'screenshotPath', fullPage: true }],
takeElementScreenshot: ['#target', 'screenshotPath'],
takeElementScreenshot: ['#target', 'screenshotPath', { includeMargins: true, crop: { top: -100 } }],
resizeWindow: [200, 200],
resizeWindowToFitDevice: ['Sony Xperia Z', { portraitOrientation: true }],
maximizeWindow: [],
Expand Down Expand Up @@ -225,4 +240,77 @@ describe('TestController action events', () => {
expect(resultDuration).to.be.a('number').with.above(0);
});
});

it('Default command options should not be passed to the `reportTestActionDone` method', async () => {
const log = [];

initializeReporter({
async reportTestActionDone (name, { command }) {
log.push(name);

if (command.options)
log.push(command.options);
}
}, task);

const actionsKeys = Object.keys(actionsWithoutOptions);

for (let i = 0; i < actionsKeys.length; i++)
await testController[actionsKeys[i]].apply(testController, actionsWithoutOptions[actionsKeys[i]]);

expect(log).eql(actionsKeys);
});

it('Show only modified action options', async () => {
const doneLog = [];

initializeReporter({
async reportTestActionDone (name, { command }) {
const item = { name };

if (command.options)
item.options = command.options;

doneLog.push(item);
}
}, task);

await testController.click('#target', { caretPos: 1, modifiers: { shift: true } });
await testController.click('#target', { modifiers: { ctrl: false } });

await testController.resizeWindowToFitDevice('iPhone 5', { portraitOrientation: true });
await testController.resizeWindowToFitDevice('iPhone 5', { portraitOrientation: false });

await testController.expect(true).eql(true, 'message', { timeout: 500 });
await testController.expect(true).eql(true);

const expectedLog = [
{
name: 'click',
options: {
caretPos: 1,
modifiers: {
shift: true
}
}
},
{ name: 'click' },
{
name: 'resizeWindowToFitDevice',
options: {
portraitOrientation: true
}
},
{ name: 'resizeWindowToFitDevice' },
{
name: 'eql',
options: {
timeout: 500
}
},
{ name: 'eql' }
];

expect(doneLog).eql(expectedLog);
});
});

0 comments on commit b4b2b67

Please sign in to comment.