1
1
import fs from "fs" ;
2
2
import path from "path" ;
3
3
import { PNG } from "pngjs" ;
4
- import pixelmatch from "pixelmatch" ;
4
+ import pixelmatch , { PixelmatchOptions } from "pixelmatch" ;
5
5
import moveFile from "move-file" ;
6
6
import sanitize from "sanitize-filename" ;
7
- import { FILE_SUFFIX , IMAGE_SNAPSHOT_PREFIX , TASK } from ". /constants" ;
8
- import { alignImagesToSameSize , importAndScaleImage } from ". /image.utils" ;
7
+ import { FILE_SUFFIX , IMAGE_SNAPSHOT_PREFIX , TASK } from "@ /constants" ;
8
+ import { alignImagesToSameSize , importAndScaleImage } from "@ /image.utils" ;
9
9
10
10
type CompareImagesCfg = {
11
11
scaleFactor : number ;
@@ -14,142 +14,140 @@ type CompareImagesCfg = {
14
14
imgOld : string ;
15
15
updateImages : boolean ;
16
16
maxDiffThreshold : number ;
17
- diffConfig : Parameters < typeof pixelmatch > [ 5 ] ;
18
- } & Parameters < typeof pixelmatch > [ 5 ] ;
17
+ diffConfig : PixelmatchOptions ;
18
+ } ;
19
19
20
20
const round = ( n : number ) => Math . ceil ( n * 1000 ) / 1000 ;
21
21
22
- const initGetScreenshotPathTask : ( ) => Cypress . Tasks = ( ) => ( {
23
- [ TASK . getScreenshotPath ] ( { title, imagesDir, specPath } ) {
24
- return path . join (
25
- IMAGE_SNAPSHOT_PREFIX ,
26
- path . dirname ( specPath ) ,
27
- ...imagesDir . split ( "/" ) ,
28
- `${ sanitize ( title ) } ${ FILE_SUFFIX . actual } .png`
29
- ) ;
30
- } ,
31
- } ) ;
32
-
33
22
const unlinkSyncSafe = ( path : string ) =>
34
23
fs . existsSync ( path ) && fs . unlinkSync ( path ) ;
35
24
const moveSyncSafe = ( pathFrom : string , pathTo : string ) =>
36
25
fs . existsSync ( pathFrom ) && moveFile . sync ( pathFrom , pathTo ) ;
37
26
38
- const initApproveImageTask : ( ) => Cypress . Tasks = ( ) => ( {
39
- [ TASK . approveImage ] ( { img } ) {
40
- const oldImg = img . replace ( FILE_SUFFIX . actual , "" ) ;
41
- unlinkSyncSafe ( oldImg ) ;
27
+ export const getScreenshotPathTask : Cypress . Task = ( {
28
+ title,
29
+ imagesDir,
30
+ specPath,
31
+ } ) =>
32
+ path . join (
33
+ IMAGE_SNAPSHOT_PREFIX ,
34
+ path . dirname ( specPath ) ,
35
+ ...imagesDir . split ( "/" ) ,
36
+ `${ sanitize ( title ) } ${ FILE_SUFFIX . actual } .png`
37
+ ) ;
42
38
43
- const diffImg = img . replace ( FILE_SUFFIX . actual , FILE_SUFFIX . diff ) ;
44
- unlinkSyncSafe ( diffImg ) ;
39
+ export const approveImageTask = ( { img } : { img : string } ) => {
40
+ const oldImg = img . replace ( FILE_SUFFIX . actual , "" ) ;
41
+ unlinkSyncSafe ( oldImg ) ;
45
42
46
- moveSyncSafe ( img , oldImg ) ;
43
+ const diffImg = img . replace ( FILE_SUFFIX . actual , FILE_SUFFIX . diff ) ;
44
+ unlinkSyncSafe ( diffImg ) ;
47
45
48
- return null ;
49
- } ,
50
- } ) ;
46
+ moveSyncSafe ( img , oldImg ) ;
51
47
52
- const initCompareImagesTask = ( ) => ( {
53
- async [ TASK . compareImages ] ( cfg : CompareImagesCfg ) : Promise < null | {
54
- error ?: boolean ;
55
- message ?: string ;
56
- imgDiff ?: number ;
57
- maxDiffThreshold ?: number ;
58
- } > {
59
- const messages = [ ] as string [ ] ;
60
- let imgDiff : number | undefined ;
61
- let error = false ;
62
-
63
- if ( fs . existsSync ( cfg . imgOld ) && ! cfg . updateImages ) {
64
- const rawImgNew = await importAndScaleImage ( {
65
- scaleFactor : cfg . scaleFactor ,
66
- path : cfg . imgNew ,
67
- } ) ;
68
- const rawImgOld = PNG . sync . read ( fs . readFileSync ( cfg . imgOld ) ) ;
69
- const isImgSizeDifferent =
70
- rawImgNew . height !== rawImgOld . height ||
71
- rawImgNew . width !== rawImgOld . width ;
72
-
73
- const [ imgNew , imgOld ] = isImgSizeDifferent
74
- ? alignImagesToSameSize ( rawImgNew , rawImgOld )
75
- : [ rawImgNew , rawImgOld ] ;
76
-
77
- const { width, height } = imgNew ;
78
- const diff = new PNG ( { width, height } ) ;
79
- const diffConfig = Object . assign ( { includeAA : true } , cfg . diffConfig ) ;
80
-
81
- const diffPixels = pixelmatch (
82
- imgNew . data ,
83
- imgOld . data ,
84
- diff . data ,
85
- width ,
86
- height ,
87
- diffConfig
48
+ return null ;
49
+ } ;
50
+
51
+ export const compareImagesTask = async (
52
+ cfg : CompareImagesCfg
53
+ ) : Promise < null | {
54
+ error ?: boolean ;
55
+ message ?: string ;
56
+ imgDiff ?: number ;
57
+ maxDiffThreshold ?: number ;
58
+ } > => {
59
+ const messages = [ ] as string [ ] ;
60
+ let imgDiff : number | undefined ;
61
+ let error = false ;
62
+
63
+ if ( fs . existsSync ( cfg . imgOld ) && ! cfg . updateImages ) {
64
+ const rawImgNew = await importAndScaleImage ( {
65
+ scaleFactor : cfg . scaleFactor ,
66
+ path : cfg . imgNew ,
67
+ } ) ;
68
+ const rawImgOld = PNG . sync . read ( fs . readFileSync ( cfg . imgOld ) ) ;
69
+ const isImgSizeDifferent =
70
+ rawImgNew . height !== rawImgOld . height ||
71
+ rawImgNew . width !== rawImgOld . width ;
72
+
73
+ const [ imgNew , imgOld ] = isImgSizeDifferent
74
+ ? alignImagesToSameSize ( rawImgNew , rawImgOld )
75
+ : [ rawImgNew , rawImgOld ] ;
76
+
77
+ const { width, height } = imgNew ;
78
+ const diff = new PNG ( { width, height } ) ;
79
+ const diffConfig = Object . assign ( { includeAA : true } , cfg . diffConfig ) ;
80
+
81
+ const diffPixels = pixelmatch (
82
+ imgNew . data ,
83
+ imgOld . data ,
84
+ diff . data ,
85
+ width ,
86
+ height ,
87
+ diffConfig
88
+ ) ;
89
+ imgDiff = diffPixels / ( width * height ) ;
90
+
91
+ if ( isImgSizeDifferent ) {
92
+ messages . push (
93
+ `Warning: Images size mismatch - new screenshot is ${ rawImgNew . width } px by ${ rawImgNew . height } px while old one is ${ rawImgOld . width } px by ${ rawImgOld . height } (width x height).`
88
94
) ;
89
- imgDiff = diffPixels / ( width * height ) ;
90
-
91
- if ( isImgSizeDifferent ) {
92
- messages . push (
93
- `Warning: Images size mismatch - new screenshot is ${ rawImgNew . width } px by ${ rawImgNew . height } px while old one is ${ rawImgOld . width } px by ${ rawImgOld . height } (width x height).`
94
- ) ;
95
- }
96
-
97
- if ( imgDiff > cfg . maxDiffThreshold ) {
98
- messages . unshift (
99
- `Image diff factor (${ round (
100
- imgDiff
101
- ) } ) is bigger than maximum threshold option ${ cfg . maxDiffThreshold } .`
102
- ) ;
103
- error = true ;
104
- }
105
-
106
- if ( error ) {
107
- fs . writeFileSync (
108
- cfg . imgNew . replace ( FILE_SUFFIX . actual , FILE_SUFFIX . diff ) ,
109
- PNG . sync . write ( diff )
110
- ) ;
111
- return {
112
- error,
113
- message : messages . join ( "\n" ) ,
114
- imgDiff,
115
- maxDiffThreshold : cfg . maxDiffThreshold ,
116
- } ;
117
- } else {
118
- // don't overwrite file if it's the same (imgDiff < cfg.maxDiffThreshold && !isImgSizeDifferent)
119
- fs . unlinkSync ( cfg . imgNew ) ;
120
- }
121
- } else {
122
- // there is no "old screenshot" or screenshots should be immediately updated
123
- imgDiff = 0 ;
124
- moveFile . sync ( cfg . imgNew , cfg . imgOld ) ;
125
95
}
126
96
127
- if ( typeof imgDiff !== "undefined" ) {
97
+ if ( imgDiff > cfg . maxDiffThreshold ) {
128
98
messages . unshift (
129
- `Image diff (${ round (
99
+ `Image diff factor (${ round (
130
100
imgDiff
131
- ) } %) is within boundaries of maximum threshold option ${
132
- cfg . maxDiffThreshold
133
- } .`
101
+ ) } ) is bigger than maximum threshold option ${ cfg . maxDiffThreshold } .`
102
+ ) ;
103
+ error = true ;
104
+ }
105
+
106
+ if ( error ) {
107
+ fs . writeFileSync (
108
+ cfg . imgNew . replace ( FILE_SUFFIX . actual , FILE_SUFFIX . diff ) ,
109
+ PNG . sync . write ( diff )
134
110
) ;
135
111
return {
112
+ error,
136
113
message : messages . join ( "\n" ) ,
137
114
imgDiff,
138
115
maxDiffThreshold : cfg . maxDiffThreshold ,
139
116
} ;
117
+ } else {
118
+ // don't overwrite file if it's the same (imgDiff < cfg.maxDiffThreshold && !isImgSizeDifferent)
119
+ fs . unlinkSync ( cfg . imgNew ) ;
140
120
}
121
+ } else {
122
+ // there is no "old screenshot" or screenshots should be immediately updated
123
+ imgDiff = 0 ;
124
+ moveFile . sync ( cfg . imgNew , cfg . imgOld ) ;
125
+ }
126
+
127
+ if ( typeof imgDiff !== "undefined" ) {
128
+ messages . unshift (
129
+ `Image diff (${ round (
130
+ imgDiff
131
+ ) } %) is within boundaries of maximum threshold option ${
132
+ cfg . maxDiffThreshold
133
+ } .`
134
+ ) ;
135
+ return {
136
+ message : messages . join ( "\n" ) ,
137
+ imgDiff,
138
+ maxDiffThreshold : cfg . maxDiffThreshold ,
139
+ } ;
140
+ }
141
+
142
+ return null ;
143
+ } ;
141
144
142
- return null ;
143
- } ,
144
- } ) ;
145
+ export const doesFileExistTask : Cypress . Task = ( { path } ) =>
146
+ fs . existsSync ( path ) ;
145
147
146
- export const initTaskHooks = ( on : Cypress . PluginEvents ) => {
147
- on ( "task" , {
148
- ...initGetScreenshotPathTask ( ) ,
149
- [ TASK . doesFileExist ] ( { path } ) {
150
- return fs . existsSync ( path ) ;
151
- } ,
152
- ...initApproveImageTask ( ) ,
153
- ...initCompareImagesTask ( ) ,
154
- } ) ;
155
- } ;
148
+ export const initTasks = ( ) => ( {
149
+ [ TASK . getScreenshotPath ] : getScreenshotPathTask ,
150
+ [ TASK . doesFileExist ] : doesFileExistTask ,
151
+ [ TASK . approveImage ] : approveImageTask ,
152
+ [ TASK . compareImages ] : compareImagesTask ,
153
+ } ) ;
0 commit comments