Skip to content

Commit 979f7ff

Browse files
Merge pull request #473 from gemini-testing/HERMIONE-816.preload_images
perf: preload images in screenshot-accepter mode
2 parents 80d587a + 126037d commit 979f7ff

File tree

3 files changed

+121
-9
lines changed
  • lib/static
  • test/unit/lib/static/components/modals/screenshot-accepter

3 files changed

+121
-9
lines changed

lib/static/components/modals/screenshot-accepter/index.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@ import ReactDOM from 'react-dom';
33
import {connect} from 'react-redux';
44
import {bindActionCreators} from 'redux';
55
import PropTypes from 'prop-types';
6-
import {isEmpty, isNumber, size, get, findIndex} from 'lodash';
6+
import {isEmpty, isNumber, size, get, findIndex, last} from 'lodash';
77

88
import * as actions from '../../../modules/actions';
99
import ScreenshotAccepterHeader from './header';
1010
import ScreenshotAccepterMeta from './meta';
1111
import ScreenshotAccepterBody from './body';
1212
import {getAcceptableImagesByStateName} from '../../../modules/selectors/tree';
13+
import {preloadImage} from '../../../modules/utils';
1314

1415
import './style.css';
1516

17+
const PRELOAD_IMAGE_COUNT = 3;
18+
1619
class ScreenshotAccepter extends Component {
1720
static propTypes = {
1821
image: PropTypes.shape({
@@ -45,6 +48,10 @@ class ScreenshotAccepter extends Component {
4548
};
4649

4750
this.totalImagesCount = size(imagesByStateName);
51+
52+
for (let i = 1; i <= PRELOAD_IMAGE_COUNT; i++) {
53+
this._preloadAdjacentImages(i);
54+
}
4855
}
4956

5057
componentDidUpdate() {
@@ -59,6 +66,7 @@ class ScreenshotAccepter extends Component {
5966
const images = this._getActiveImages(activeImageIndex);
6067

6168
this.setState({retryIndex: images.length - 1, activeImageIndex});
69+
this._preloadAdjacentImages();
6270
}
6371

6472
onScreenshotAccept = async (imageId) => {
@@ -104,6 +112,7 @@ class ScreenshotAccepter extends Component {
104112
stateNameImageIds: stateNameIds,
105113
retryIndex: newImages.length - 1
106114
});
115+
this._preloadAdjacentImages();
107116
}
108117

109118
onScreenshotUndo = async () => {
@@ -130,6 +139,7 @@ class ScreenshotAccepter extends Component {
130139
stateNameImageIds: previousStateNameImageId,
131140
retryIndex: images.length - 1
132141
});
142+
this._preloadAdjacentImages();
133143
}
134144

135145
onShowMeta = () => {
@@ -153,6 +163,19 @@ class ScreenshotAccepter extends Component {
153163
: [];
154164
}
155165

166+
_preloadAdjacentImages(offset = PRELOAD_IMAGE_COUNT) {
167+
const screensCount = size(this.state.stateNameImageIds);
168+
const previosImagesIndex = (screensCount + this.state.activeImageIndex - offset) % screensCount;
169+
const nextImagesIndex = (this.state.activeImageIndex + offset) % screensCount;
170+
171+
[previosImagesIndex, nextImagesIndex].filter(ind => ind >= 0).forEach(preloadingImagesIndex => {
172+
const stateNameImageId = this.state.stateNameImageIds[preloadingImagesIndex];
173+
const {expectedImg, actualImg, diffImg} = last(this.props.imagesByStateName[stateNameImageId]);
174+
175+
[expectedImg, actualImg, diffImg].filter(Boolean).forEach(({path}) => preloadImage(path));
176+
});
177+
}
178+
156179
render() {
157180
const {retryIndex, stateNameImageIds, activeImageIndex, showMeta} = this.state;
158181
const images = this._getActiveImages();

lib/static/modules/utils.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ function parseKeyToGroupTestsBy(key) {
155155
return [groupSection, groupKey];
156156
}
157157

158+
function preloadImage(url) {
159+
new Image().src = url;
160+
}
161+
158162
module.exports = {
159163
isNoRefImageError,
160164
isAssertViewError,
@@ -172,5 +176,6 @@ module.exports = {
172176
isBrowserMatchViewMode,
173177
shouldShowBrowser,
174178
iterateSuites,
175-
parseKeyToGroupTestsBy
179+
parseKeyToGroupTestsBy,
180+
preloadImage
176181
};

test/unit/lib/static/components/modals/screenshot-accepter/index.js

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {mkConnectedComponent} from '../../utils';
77

88
describe('<ScreenshotAccepter/>', () => {
99
const sandbox = sinon.sandbox.create();
10-
let ScreenshotAccepter, ScreenshotAccepterHeader, ScreenshotAccepterMeta, ScreenshotAccepterBody, actionsStub, selectors, parentNode;
10+
let ScreenshotAccepter, ScreenshotAccepterHeader, ScreenshotAccepterMeta, ScreenshotAccepterBody;
11+
let actionsStub, selectors, parentNode, preloadImageStub;
1112

1213
const mkResult = (opts) => {
1314
const result = defaults(opts, {
@@ -36,7 +37,7 @@ describe('<ScreenshotAccepter/>', () => {
3637
const mkScreenshotAccepterComponent = (props = {}, initialState = {}) => {
3738
props = defaults(props, {
3839
image: mkImage(),
39-
onClose: sinon.stub()
40+
onClose: sandbox.stub()
4041
});
4142
initialState = defaults(initialState, {
4243
tree: mkStateTree(),
@@ -58,21 +59,23 @@ describe('<ScreenshotAccepter/>', () => {
5859
};
5960

6061
parentNode = {
61-
scrollTo: sinon.stub()
62+
scrollTo: sandbox.stub()
6263
};
6364

64-
ScreenshotAccepterHeader = sinon.stub().returns(null);
65-
ScreenshotAccepterMeta = sinon.stub().returns(null);
66-
ScreenshotAccepterBody = sinon.stub().returns(null);
65+
ScreenshotAccepterHeader = sandbox.stub().returns(null);
66+
ScreenshotAccepterMeta = sandbox.stub().returns(null);
67+
ScreenshotAccepterBody = sandbox.stub().returns(null);
6768

6869
sandbox.stub(ReactDOM, 'findDOMNode').returns({parentNode});
70+
preloadImageStub = sandbox.stub();
6971

7072
ScreenshotAccepter = proxyquire('lib/static/components/modals/screenshot-accepter', {
7173
'./header': {default: ScreenshotAccepterHeader},
7274
'./meta': {default: ScreenshotAccepterMeta},
7375
'./body': {default: ScreenshotAccepterBody},
7476
'../../../modules/actions': actionsStub,
75-
'../../../modules/selectors/tree': selectors
77+
'../../../modules/selectors/tree': selectors,
78+
'../../../modules/utils': {preloadImage: preloadImageStub}
7679
}).default;
7780
});
7881

@@ -309,4 +312,85 @@ describe('<ScreenshotAccepter/>', () => {
309312
assert.notCalled(actionsStub.applyDelayedTestResults);
310313
});
311314
});
315+
316+
describe('should preload images', () => {
317+
const mkImgPath_ = (label, imageId) => `${label}-${imageId}`;
318+
const mkBrowserId_ = (fullName, browserName) => `${fullName} ${browserName}`;
319+
const mkStateName_ = (browserId, stateName) => `${browserId} ${stateName}`;
320+
const mkImgId_ = (browserId, retry, state) => `${browserId} ${retry} ${state}`;
321+
const mkImg_ = (label, imageId) => ({path: mkImgPath_(label, imageId)});
322+
323+
const mkImgs_ = (id) => ({
324+
id,
325+
actualImg: mkImg_('actual', id),
326+
expectedImg: mkImg_('expected', id),
327+
diffImg: mkImg_('diff', id)
328+
});
329+
330+
const eachLabel_ = (cb) => ['expected', 'actual', 'diff'].forEach(cb);
331+
332+
beforeEach(() => {
333+
const browserIds = Array(10).fill(0).map((_, ind) => mkBrowserId_(`test-${ind + 1}`, 'bro-1'));
334+
const image = mkImage({id: 'img-2', parentId: 'res-1', stateName: 'plain'});
335+
const resultsById = mkResult({id: 'res-1', parentId: mkBrowserId_('test-2', 'bro-1'), imageIds: ['img-2']});
336+
const tree = mkStateTree({resultsById});
337+
selectors.getAcceptableImagesByStateName.returns(browserIds.reduce((acc, browserId) => {
338+
const stateName = mkStateName_(browserId, 'plain');
339+
const images = [1, 2].map(retry => mkImgs_(mkImgId_(browserId, retry, 'plain')));
340+
341+
acc[stateName] = images;
342+
343+
return acc;
344+
}, {}));
345+
346+
mkScreenshotAccepterComponent({image}, {tree});
347+
});
348+
349+
it('from adjacent screens', () => {
350+
[9, 10, 1, 3, 4, 5].forEach(ind => {
351+
eachLabel_(label => {
352+
const browserId = mkBrowserId_(`test-${ind}`, 'bro-1');
353+
const imageId = mkImgId_(browserId, 2, 'plain');
354+
const imagePath = mkImgPath_(label, imageId);
355+
356+
assert.calledWith(preloadImageStub, imagePath);
357+
});
358+
});
359+
});
360+
361+
it('only from last retry', () => {
362+
eachLabel_(label => {
363+
const browserId = mkBrowserId_(`test-${3}`, 'bro-1');
364+
const firstImageId = mkImgId_(browserId, 1, 'plain');
365+
const secondImageId = mkImgId_(browserId, 2, 'plain');
366+
367+
assert.neverCalledWith(preloadImageStub, mkImgPath_(label, firstImageId));
368+
assert.calledWith(preloadImageStub, mkImgPath_(label, secondImageId));
369+
});
370+
});
371+
372+
it('on active image change', () => {
373+
const browserId = mkBrowserId_('test-6', 'bro-1');
374+
const imageId = mkImgId_(browserId, 2, 'plain');
375+
376+
eachLabel_(label => assert.neverCalledWith(preloadImageStub, mkImgPath_(label, imageId)));
377+
378+
ScreenshotAccepterHeader.firstCall.args[0].onActiveImageChange(2);
379+
380+
eachLabel_(label => assert.calledWith(preloadImageStub, mkImgPath_(label, imageId)));
381+
});
382+
383+
it('on image accept', async () => {
384+
const preloadingBrowserId = mkBrowserId_('test-6', 'bro-1');
385+
const preloadingImageId = mkImgId_(preloadingBrowserId, 2, 'plain');
386+
const acceptingBrowserId = mkBrowserId_('test-2', 'bro-1');
387+
const acceptingImageId = mkImgId_(acceptingBrowserId, 2, 'plain');
388+
389+
eachLabel_(label => assert.neverCalledWith(preloadImageStub, mkImgPath_(label, preloadingImageId)));
390+
391+
await ScreenshotAccepterHeader.firstCall.args[0].onScreenshotAccept(acceptingImageId);
392+
393+
eachLabel_(label => assert.calledWith(preloadImageStub, mkImgPath_(label, preloadingImageId)));
394+
});
395+
});
312396
});

0 commit comments

Comments
 (0)