Skip to content

Commit

Permalink
feat(error): use errorPatterns to show more understandable error info
Browse files Browse the repository at this point in the history
- show matched error pattern name instead of original title message error
- show error hints from error patterns
  • Loading branch information
DudaGod committed Feb 5, 2020
1 parent b8f4405 commit 5480e13
Show file tree
Hide file tree
Showing 15 changed files with 309 additions and 79 deletions.
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,19 @@ directory.
* **baseHost** (optional) - `String` - it changes original host for view in the browser; by default original host does not change
* **scaleImages** (optional) – `Boolean` – fit images into page width; `false` by default
* **lazyLoadOffset** (optional) - `Number` - allows you to specify how far above and below the viewport you want to begin loading images. Lazy loading would be disabled if you specify 0. `800` by default.
* **errorPatterns** (optional) - `Array` - error message patterns for 'Group by error' mode.
Array element must be `Object` ({'*name*': `String`, '*pattern*': `String`}) or `String` (interpret as *name* and *pattern*).
Test will be associated with group if test error matches on group error pattern.
New group will be created if test cannot be associated with existing groups.
* **errorPatterns** (optional) - `Array` - error message patterns are used:
* to show more understandable information about matched error;
* in 'Group by error' mode.

Array elements must be one of the types:
* `Object` with required fields *name*, *pattern* and optional field *hint* - {*name*: `String`, *pattern*: `String`, *hint*: `String`};
* `String` which will be interpret as *name* and *pattern*.

When one of error patterns are matched on error message then:
* *name* of error pattern will be displayed as title of error message and original error message will be hidden under details;
* *hint* of error pattern will be displayed after error *stack* field. Can be specified as html string. For example, `<div>some-useful-hint</div>`.

In 'Group by error' mode test will be associated with group if test error matches on group error pattern. New group will be created if test cannot be associated with existing groups.
* **metaInfoBaseUrls** (optional) `Object` - base paths for making link from Meta-info values. Object option must be Meta-info's key and value must be `String`. For example, {'file': 'base/path'}.
* **saveFormat** (optional) `String` - allows to specify the format, in which the results will be saved. Available values are:
* `js` - save tests results to data.js file as object. Default value.
Expand All @@ -48,7 +57,7 @@ New group will be created if test cannot be associated with existing groups.
* `sqlite.db` - Sqlite database with tests results
* `data.js` - report's config
* `databaseUrls.json` - absolute or relative URLs to Sqlite databases (`sqlite.db`) or/and URLs to other `databaseUrls.json` (see [merge-reports](#merge-reports))

You can't open local report by 'file://' protocol. Use gui mode or start a local server. For example, execute `npx http-server -p 8080` at terminal from folder where report placed and open page `http://localhost:8080` at browser.
* **customGui** (optional) `Object` – allows to specify custom controls for gui-mode and define actions for them. `{}` is default value. Ordinarily custom controls should be split by sections depending on the purposes of the controls. At least one section should be specified.
The structure of the custom-gui object:
Expand Down Expand Up @@ -162,7 +171,8 @@ module.exports = {
'Parameter .* must be a string',
{
name: 'Cannot read property of undefined',
pattern: 'Cannot read property .* of undefined'
pattern: 'Cannot read property .* of undefined',
hint: '<div>google it, i dont know how to fix it =(</div>'
}
]
}
Expand Down Expand Up @@ -190,7 +200,7 @@ Command that adds ability to merge reports which are created after running the t

#### When save format is js (default)

Command takes paths to directories with reports.
Command takes paths to directories with reports.
It merge "data.js" files into single file and move reports files to destination directory.

Example of usage:
Expand All @@ -202,7 +212,7 @@ npx hermione merge-reports src-report-1 src-report-2 -d dest-report

Command takes paths to databases files or "databaseUrls.json" files from other html reports.
It creates new html report at destination directory with common "databaseUrls.json"
which will contain link to databases files or "databaseUrls.json" files from input parameters.
which will contain link to databases files or "databaseUrls.json" files from input parameters.
Databases files will not be copied to destination directory.

Example of usage:
Expand Down
10 changes: 3 additions & 7 deletions lib/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,9 @@ const assertMetaInfoBaseUrls = (metaInfoBaseUrls) => {

const mapErrorPatterns = (errorPatterns) => {
return errorPatterns.map(patternInfo => {
if (typeof patternInfo === 'string') {
return {
name: patternInfo,
pattern: patternInfo
};
}
return patternInfo;
return _.isString(patternInfo)
? {name: patternInfo, pattern: patternInfo}
: patternInfo;
});
};

Expand Down
9 changes: 6 additions & 3 deletions lib/constants/errors.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
'use strict';

exports.getCommonErrors = () => ({
NO_REF_IMAGE_ERROR: 'NoRefImageError'
});
module.exports = {
getCommonErrors: () => ({
NO_REF_IMAGE_ERROR: 'NoRefImageError'
}),
ERROR_TITLE_TEXT_LENGTH: 200
};
1 change: 0 additions & 1 deletion lib/report-builder/report-builder-json.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,3 @@ module.exports = class ReportBuilderJson extends ReportBuilder {
return saveFormats.JS;
}
};

10 changes: 7 additions & 3 deletions lib/static/components/state/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ class State extends Component {
: null;
}

_getErrorPattern(error) {
return this.props.config.errorPatterns.find(({regexp}) => error.message.match(regexp));
}

render() {
const {status, expectedImg, actualImg, diffImg, stateName, diffClusters} = this.props.state;
const {image, error, errorDetails} = this.props;
Expand All @@ -132,12 +136,12 @@ class State extends Component {
}

if (isErroredStatus(status)) {
elem = <StateError image={Boolean(image)} actualImg={actualImg} error={error} errorDetails={errorDetails}/>;
elem = <StateError image={Boolean(image)} actualImg={actualImg} error={error} errorPattern={this._getErrorPattern(error)} errorDetails={errorDetails}/>;
} else if (isSuccessStatus(status) || isUpdatedStatus(status) || (isIdleStatus(status) && get(expectedImg, 'path'))) {
elem = <StateSuccess status={status} expectedImg={expectedImg} />;
} else if (isFailStatus(status)) {
elem = error
? <StateError image={Boolean(image)} actualImg={actualImg} error={error} errorDetails={errorDetails}/>
? <StateError image={Boolean(image)} actualImg={actualImg} error={error} errorPattern={this._getErrorPattern(error)} errorDetails={errorDetails}/>
: <StateFail expectedImg={expectedImg} actualImg={actualImg} diffImg={diffImg} diffClusters={diffClusters}/>;
}

Expand All @@ -158,5 +162,5 @@ class State extends Component {
}

export default connect(
({reporter: {gui, view: {expand, scaleImages}, closeIds}}) => ({gui, expand, scaleImages, closeIds})
({reporter: {gui, view: {expand, scaleImages}, closeIds, config}}) => ({gui, expand, scaleImages, closeIds, config})
)(State);
56 changes: 49 additions & 7 deletions lib/static/components/state/state-error.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,31 @@

import React, {Component, Fragment} from 'react';
import PropTypes from 'prop-types';
import {map} from 'lodash';
import {isEmpty, map} from 'lodash';
import ReactHtmlParser from 'react-html-parser';
import Screenshot from './screenshot';
import {isNoRefImageError} from '../../modules/utils';
import ErrorDetails from './error-details';
import Details from '../details';
import {ERROR_TITLE_TEXT_LENGTH} from '../../../constants/errors';

export default class StateError extends Component {
static propTypes = {
image: PropTypes.bool.isRequired,
error: PropTypes.object.isRequired,
actualImg: PropTypes.object
actualImg: PropTypes.object,
errorPattern: PropTypes.object
};

render() {
const {image, error, errorDetails, actualImg} = this.props;
const {image, error, errorDetails, actualImg, errorPattern = {}} = this.props;
const extendedError = isEmpty(errorPattern)
? error
: {...error, message: `${errorPattern.name}\n${error.message}`, hint: parseHtmlString(errorPattern.hint)};

return (
<div className="image-box__image image-box__image_single">
<div className="error">{this._errorToElements(error)}</div>
<div className="error">{this._errorToElements(extendedError)}</div>
{errorDetails && <ErrorDetails errorDetails={errorDetails}/>}
{this._drawImage(image, actualImg)}
</div>
Expand All @@ -39,15 +45,51 @@ export default class StateError extends Component {

_errorToElements(error) {
return map(error, (value, key) => {
const [titleText, ...content] = value.split('\n');
const title = <Fragment><span className="error__item-key">{key}:</span> {titleText}</Fragment>;
if (!value) {
return null;
}

let titleText = '';
let content = '';

if (typeof value === 'string') {
if (value.match(/\n/)) {
[titleText, ...content] = value.split('\n');
} else if (value.length < ERROR_TITLE_TEXT_LENGTH) {
titleText = value;
} else {
[titleText, ...content] = splitBySpace(value);
}

if (Array.isArray(content)) {
content = content.join('\n');
}
} else {
titleText = <span style={{color: '#ccc'}}>show more</span>;
content = value;
}

const title = <Fragment><span className="error__item-key">{key}: </span>{titleText}</Fragment>;

return <Details
key={key}
title={title}
content={content.length > 0 ? content.join('\n') : ''}
content={content}
extendClassNames="error__item"
/>;
});
}
}

function parseHtmlString(str = '') {
const html = str ? ReactHtmlParser(str) : null;

return Array.isArray(html) && html.length === 1 ? html[0] : html;
}

function splitBySpace(str) {
const spaceIndex = str.slice(0, ERROR_TITLE_TEXT_LENGTH).lastIndexOf(' ');
const breakIndex = spaceIndex === -1 ? ERROR_TITLE_TEXT_LENGTH : spaceIndex;

return [str.slice(0, breakIndex), str.slice(breakIndex + 1)];
}
14 changes: 3 additions & 11 deletions lib/static/modules/group-errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ function extractErrorsFromRetries(retries) {

function getErrorGroupList(testWithErrors, errorPatterns, filteredBrowsers, testNameFilter, strictMatchFilter) {
const errorGroups = {};
const errorPatternsWithRegExp = addRegExpToErrorPatterns(errorPatterns);

for (const [testName, browsers] of Object.entries(testWithErrors)) {
if (!isTestNameMatchFilters(testName, testNameFilter, strictMatchFilter)) {
Expand All @@ -98,7 +97,7 @@ function getErrorGroupList(testWithErrors, errorPatterns, filteredBrowsers, test
continue;
}
for (const errorText of errors) {
const patternInfo = matchGroup(errorText, errorPatternsWithRegExp);
const patternInfo = matchGroup(errorText, errorPatterns);
const {pattern, name} = patternInfo;

if (!errorGroups.hasOwnProperty(name)) {
Expand All @@ -124,15 +123,8 @@ function getErrorGroupList(testWithErrors, errorPatterns, filteredBrowsers, test
return Object.values(errorGroups);
}

function addRegExpToErrorPatterns(errorPatterns) {
return errorPatterns.map(patternInfo => ({
...patternInfo,
regexp: new RegExp(patternInfo.pattern)
}));
}

function matchGroup(errorText, errorPatternsWithRegExp) {
for (const group of errorPatternsWithRegExp) {
function matchGroup(errorText, errorPatterns) {
for (const group of errorPatterns) {
if (errorText.match(group.regexp)) {
return group;
}
Expand Down
3 changes: 3 additions & 0 deletions lib/static/modules/reducers/reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ function getInitialState(data) {
skips, suites, config, total, updated, passed,
failed, skipped, warned, retries, perBrowser, apiValues, gui = false, autoRun, date, saveFormat
} = data;

config.errorPatterns = config.errorPatterns.map((patternInfo) => ({...patternInfo, regexp: new RegExp(patternInfo.pattern)}));

const {errorPatterns, scaleImages, lazyLoadOffset, defaultView: viewMode} = config;
const viewQuery = getViewQuery(window.location.search);

Expand Down
23 changes: 11 additions & 12 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"opener": "^1.4.3",
"p-queue": "^5.0.0",
"qs": "^6.9.1",
"react-html-parser": "^2.0.2",
"react-markdown": "^3.2.0",
"reapop": "2.1.0",
"reapop-theme-wybo": "1.0.2",
Expand Down
Loading

0 comments on commit 5480e13

Please sign in to comment.