11const { defineConfig } = require ( "cypress" ) ;
2- const fs = require ( 'fs' ) ;
2+ const fs = require ( "fs" ) ;
3+ const pdfjsLib = require ( "pdfjs-dist/legacy/build/pdf.js" ) ;
4+ const { PNG } = require ( "pngjs" ) ;
5+ const sharp = require ( "sharp" ) ;
6+
7+ // Define missing DOMMatrix in Node context (for pdfjs)
8+ if ( typeof global . DOMMatrix === "undefined" ) {
9+ global . DOMMatrix = class DOMMatrix { } ;
10+ }
311
412module . exports = defineConfig ( {
513 e2e : {
@@ -14,20 +22,116 @@ module.exports = defineConfig({
1422 watchForFileChanges : true ,
1523 video : true ,
1624 setupNodeEvents ( on , config ) {
17- on ( 'after:spec' , ( spec , results ) => {
18- if ( results && results . video ) {
19- const failures = results . tests . some ( ( test ) =>
20- test . attempts . some ( ( attempt ) => attempt . state === 'failed' )
21- ) ;
22- if ( ! failures ) {
23- // delete the video if the spec passed and no tests retried
24- const videoPath = results . video ;
25- if ( fs . existsSync ( videoPath ) ) {
26- fs . unlinkSync ( videoPath ) ;
25+ // Task: verify PDF images, logo, and text content
26+ on ( "task" , {
27+ async verifyPdf ( { filePath, options = { } } ) {
28+ // options: { referenceLogoPath: string }
29+
30+ // Load PDF file
31+ const data = new Uint8Array ( fs . readFileSync ( filePath ) ) ;
32+ const pdfDoc = await pdfjsLib . getDocument ( { data } ) . promise ;
33+
34+ // Import pixelmatch only if logo check is needed
35+ let pixelmatch ;
36+ const doLogoCheck = ! ! options . referenceLogoPath ;
37+ if ( doLogoCheck ) {
38+ const pm = await import ( "pixelmatch" ) ;
39+ pixelmatch = pm . default ;
40+ }
41+
42+ let hasImage = false ;
43+ let logoFound = false ;
44+ let extractedText = "" ; //store text here
45+
46+ // Loop through all pages
47+ for ( let p = 1 ; p <= pdfDoc . numPages ; p ++ ) {
48+ const page = await pdfDoc . getPage ( p ) ;
49+
50+ //Extract text content from page
51+ const textContent = await page . getTextContent ( ) ;
52+ const pageText = textContent . items . map ( ( item ) => item . str ) . join ( " " ) ;
53+ extractedText += pageText + "\n" ;
54+
55+ //Check for image operators
56+ const ops = await page . getOperatorList ( ) ;
57+
58+ for ( let i = 0 ; i < ops . fnArray . length ; i ++ ) {
59+ const fn = ops . fnArray [ i ] ;
60+ const args = ops . argsArray [ i ] ;
61+
62+ if (
63+ fn === pdfjsLib . OPS . paintImageXObject ||
64+ fn === pdfjsLib . OPS . paintJpegXObject ||
65+ fn === pdfjsLib . OPS . paintInlineImageXObject
66+ ) {
67+ hasImage = true ;
68+
69+ if ( doLogoCheck && args [ 0 ] ) {
70+ const objName = args [ 0 ] ;
71+ const imgData = await page . objs . get ( objName ) ;
72+ if ( ! imgData ) {
73+ continue ;
74+ }
75+
76+ const pdfImg = new PNG ( { width : imgData . width , height : imgData . height } ) ;
77+ pdfImg . data = imgData . data ;
78+
79+ const pdfBuffer = PNG . sync . write ( pdfImg ) ;
80+ const refLogo = PNG . sync . read ( fs . readFileSync ( options . referenceLogoPath ) ) ;
81+
82+ const resizedPdfBuffer = await sharp ( pdfBuffer )
83+ . resize ( refLogo . width , refLogo . height )
84+ . png ( )
85+ . toBuffer ( ) ;
86+
87+ const resizedPdfImg = PNG . sync . read ( resizedPdfBuffer ) ;
88+
89+ const diff = new PNG ( { width : refLogo . width , height : refLogo . height } ) ;
90+ const mismatched = pixelmatch (
91+ refLogo . data ,
92+ resizedPdfImg . data ,
93+ diff . data ,
94+ refLogo . width ,
95+ refLogo . height ,
96+ { threshold : 0.1 }
97+ ) ;
98+
99+ if ( mismatched === 0 ) {
100+ logoFound = true ;
101+ break ;
102+ }
103+ }
104+ }
27105 }
106+
107+ if ( ( doLogoCheck && logoFound ) || ( ! doLogoCheck && hasImage ) ) {
108+ break ;
109+ }
110+ }
111+
112+ if ( doLogoCheck && ! logoFound ) {
113+ throw new Error ( "Logo in PDF does not match reference image" ) ;
114+ }
115+
116+ //Return with extracted text
117+ return {
118+ hasImage,
119+ logoFound,
120+ text : extractedText ,
121+ numPages : pdfDoc . numPages
122+ } ;
123+ } ,
124+ } ) ;
125+
126+ on ( "after:spec" , ( spec , results ) => {
127+ if ( results ?. video ) {
128+ const hasFailures = results . tests . some ( ( t ) => t . attempts . some ( ( a ) => a . state === "failed" ) ) ;
129+ if ( ! hasFailures && fs . existsSync ( results . video ) ) {
130+ fs . unlinkSync ( results . video ) ;
28131 }
29132 }
30133 } ) ;
134+
31135 on ( "before:browser:launch" , ( browser , launchOptions ) => {
32136 if ( [ "chrome" , "edge" , "electron" ] . includes ( browser . name ) ) {
33137 if ( browser . isHeadless ) {
@@ -42,6 +146,4 @@ module.exports = defineConfig({
42146 } ) ;
43147 } ,
44148 } ,
45- } ) ;
46-
47-
149+ } ) ;
0 commit comments