@checkly/playwright-reporter 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +55 -137
- package/dist/index.js +1057 -1
- package/package.json +13 -42
- package/README.md +0 -291
package/dist/index.js
CHANGED
|
@@ -1 +1,1057 @@
|
|
|
1
|
-
const a0_0x25037f=a0_0x4c90;(function(_0x1a495a,_0x3d6c57){const _0x2386d6=a0_0x4c90,_0x13aa0a=_0x1a495a();while(!![]){try{const _0x74df10=-parseInt(_0x2386d6(0x14b))/0x1+parseInt(_0x2386d6(0xde))/0x2*(parseInt(_0x2386d6(0xe9))/0x3)+parseInt(_0x2386d6(0x140))/0x4*(parseInt(_0x2386d6(0xe1))/0x5)+parseInt(_0x2386d6(0x1a0))/0x6+-parseInt(_0x2386d6(0x153))/0x7*(-parseInt(_0x2386d6(0x181))/0x8)+-parseInt(_0x2386d6(0x14f))/0x9*(parseInt(_0x2386d6(0x199))/0xa)+-parseInt(_0x2386d6(0x13b))/0xb;if(_0x74df10===_0x3d6c57)break;else _0x13aa0a['push'](_0x13aa0a['shift']());}catch(_0x3ed807){_0x13aa0a['push'](_0x13aa0a['shift']());}}}(a0_0x1d61,0xa49be));import*as a0_0x5d4dd3 from'fs';import*as a0_0x38b769 from'path';var AssetCollector=class{constructor(_0x292948){const _0x2afc61=a0_0x4c90;this[_0x2afc61(0x1a3)]=_0x292948;}async[a0_0x25037f(0x168)](_0x32fe3a){const _0x9ffa2a=a0_0x25037f,_0x23a039=[],_0x50fd5d=new Set();if(!a0_0x5d4dd3[_0x9ffa2a(0xd6)](this[_0x9ffa2a(0x1a3)]))return _0x23a039;return await this[_0x9ffa2a(0xe7)](this[_0x9ffa2a(0x1a3)],'',_0x23a039,_0x50fd5d),_0x23a039;}async[a0_0x25037f(0xe7)](_0x3d16bf,_0x36fad9,_0x55634a,_0x37aaa0){const _0x1d8660=a0_0x25037f;let _0x22e56b;try{_0x22e56b=a0_0x5d4dd3[_0x1d8660(0xe5)](_0x3d16bf,{'withFileTypes':!![]});}catch{return;}const _0x5003e3=a0_0x38b769[_0x1d8660(0x105)](a0_0x38b769[_0x1d8660(0x137)](this[_0x1d8660(0x1a3)]));for(const _0x3a7fdb of _0x22e56b){const _0x1bd2f9=a0_0x38b769[_0x1d8660(0x176)](_0x3d16bf,_0x3a7fdb[_0x1d8660(0x169)]),_0x552375=a0_0x38b769['relative'](this['testResultsDir'],_0x1bd2f9),_0x1a91de=a0_0x38b769['join'](_0x5003e3,_0x552375);if(this['shouldSkipFile'](_0x3a7fdb[_0x1d8660(0x169)]))continue;if(_0x3a7fdb[_0x1d8660(0x106)]())await this[_0x1d8660(0xe7)](_0x1bd2f9,_0x1a91de,_0x55634a,_0x37aaa0);else{if(_0x3a7fdb[_0x1d8660(0x100)]()){if(!_0x37aaa0['has'](_0x1bd2f9)){const _0xb42ac2=this['createAsset'](_0x1bd2f9,_0x1a91de);_0x55634a['push'](_0xb42ac2),_0x37aaa0[_0x1d8660(0x179)](_0x1bd2f9);}}}}}[a0_0x25037f(0x175)](_0x4998f4){const _0x3c623f=a0_0x25037f,_0x537d09=[/^\./,/\.tmp$/i,/~$/,/\.swp$/i,/\.lock$/i,/^playwright-test-report\.json$/i];return _0x537d09[_0x3c623f(0x16a)](_0x226119=>_0x226119[_0x3c623f(0x180)](_0x4998f4));}[a0_0x25037f(0xda)](_0x5c70cb,_0x202990){const _0x469b33=a0_0x25037f,_0x8330cb=a0_0x38b769[_0x469b33(0x1a5)](_0x5c70cb)['toLowerCase'](),{type:_0x4005e3,contentType:_0x2701dc}=this[_0x469b33(0xe0)](_0x8330cb),_0x4a4a7c=_0x202990[_0x469b33(0x110)](a0_0x38b769['sep'])[_0x469b33(0x176)]('/');return{'sourcePath':_0x5c70cb,'archivePath':_0x4a4a7c,'type':_0x4005e3,'contentType':_0x2701dc};}['determineAssetType'](_0x5ac8ba){const _0x24d50a=a0_0x25037f;if([_0x24d50a(0xf3),_0x24d50a(0x184),_0x24d50a(0xdf),_0x24d50a(0x19e),_0x24d50a(0xf5),_0x24d50a(0x11f)][_0x24d50a(0x146)](_0x5ac8ba))return{'type':_0x24d50a(0x109),'contentType':this[_0x24d50a(0x192)](_0x5ac8ba)};if([_0x24d50a(0xd7),'.mp4',_0x24d50a(0x139),_0x24d50a(0x11a)]['includes'](_0x5ac8ba))return{'type':_0x24d50a(0xd8),'contentType':this[_0x24d50a(0xf2)](_0x5ac8ba)};if(_0x5ac8ba===_0x24d50a(0x163))return{'type':_0x24d50a(0x13a),'contentType':_0x24d50a(0x16d)};if(_0x5ac8ba===_0x24d50a(0x134)||_0x5ac8ba===_0x24d50a(0x171))return{'type':'attachment','contentType':_0x24d50a(0x10b)};if(_0x5ac8ba===_0x24d50a(0x1b0))return{'type':_0x24d50a(0x158),'contentType':_0x24d50a(0x147)};if(_0x5ac8ba==='.txt'||_0x5ac8ba==='.log')return{'type':_0x24d50a(0x158),'contentType':_0x24d50a(0x10e)};return{'type':_0x24d50a(0x107),'contentType':_0x24d50a(0x193)};}[a0_0x25037f(0x192)](_0x1fe7c4){const _0x2acb95=a0_0x25037f,_0x2cc499={'.png':_0x2acb95(0xeb),'.jpg':_0x2acb95(0x13e),'.jpeg':'image/jpeg','.gif':_0x2acb95(0x19f),'.bmp':_0x2acb95(0x130),'.svg':_0x2acb95(0x122)};return _0x2cc499[_0x1fe7c4]||_0x2acb95(0xeb);}[a0_0x25037f(0xf2)](_0x9228bc){const _0x3030d2=a0_0x25037f,_0x522f11={'.webm':_0x3030d2(0x12e),'.mp4':'video/mp4','.avi':_0x3030d2(0x1ae),'.mov':_0x3030d2(0xd9)};return _0x522f11[_0x9228bc]||'video/webm';}};import*as a0_0x24212e from'fs';import{readFileSync as a0_0x546335}from'fs';import*as a0_0x3d351b from'path';import{dirname,join as a0_0x55d174}from'path';import{fileURLToPath}from'url';import a0_0x5c40a8 from'axios';var ApiError=class extends Error{[a0_0x25037f(0x18b)];constructor(_0x3050a7,_0x4c4d49){const _0xfd22f=a0_0x25037f;super(_0x3050a7[_0xfd22f(0x161)],_0x4c4d49),this[_0xfd22f(0x169)]=this[_0xfd22f(0x183)][_0xfd22f(0x169)],this['data']=_0x3050a7;}},ValidationError=class extends ApiError{constructor(_0x168fb8,_0x5e5449){super(_0x168fb8,_0x5e5449);}},UnauthorizedError=class extends ApiError{constructor(_0x1d3246,_0xeefdf1){super(_0x1d3246,_0xeefdf1);}},ForbiddenError=class extends ApiError{constructor(_0x105db4,_0x1be9ec){super(_0x105db4,_0x1be9ec);}},NotFoundError=class extends ApiError{constructor(_0x50a175,_0x45c56e){super(_0x50a175,_0x45c56e);}},RequestTimeoutError=class extends ApiError{constructor(_0x10a4b0,_0x387bd8){super(_0x10a4b0,_0x387bd8);}},ConflictError=class extends ApiError{constructor(_0x44eda5,_0xa8f484){super(_0x44eda5,_0xa8f484);}},ServerError=class extends ApiError{constructor(_0x3db616,_0x50c0e8){super(_0x3db616,_0x50c0e8);}},MiscellaneousError=class extends ApiError{constructor(_0x24ea9f,_0x1eca40){super(_0x24ea9f,_0x1eca40);}},MissingResponseError=class extends Error{constructor(_0x2d6c8d,_0x89b2d3){const _0x130ed4=a0_0x25037f;super(_0x2d6c8d,_0x89b2d3),this[_0x130ed4(0x169)]=_0x130ed4(0x101);}};function parseErrorData(_0x5c4f30,_0x5773ba){const _0x5d1657=a0_0x25037f;if(!_0x5c4f30)return void 0x0;if(typeof _0x5c4f30==='object'&&_0x5c4f30[_0x5d1657(0x1b6)]&&_0x5c4f30[_0x5d1657(0x1a4)]&&_0x5c4f30[_0x5d1657(0x161)])return{'statusCode':_0x5c4f30['statusCode'],'error':_0x5c4f30[_0x5d1657(0x1a4)],'message':_0x5c4f30['message'],'errorCode':_0x5c4f30[_0x5d1657(0x165)]};if(typeof _0x5c4f30==='object'&&_0x5c4f30[_0x5d1657(0x1a4)]&&!_0x5c4f30['message'])return{'statusCode':_0x5773ba['statusCode'],'error':_0x5c4f30['error'],'message':_0x5c4f30['error']};if(typeof _0x5c4f30===_0x5d1657(0x166)&&_0x5c4f30[_0x5d1657(0x1a4)]&&_0x5c4f30['message'])return{'statusCode':_0x5773ba[_0x5d1657(0x1b6)],'error':_0x5c4f30[_0x5d1657(0x1a4)],'message':_0x5c4f30[_0x5d1657(0x161)],'errorCode':_0x5c4f30[_0x5d1657(0x165)]};if(typeof _0x5c4f30===_0x5d1657(0x166)&&_0x5c4f30[_0x5d1657(0x161)])return{'statusCode':_0x5773ba[_0x5d1657(0x1b6)],'error':_0x5c4f30[_0x5d1657(0x161)],'message':_0x5c4f30['message'],'errorCode':_0x5c4f30['errorCode']};if(typeof _0x5c4f30===_0x5d1657(0x172))return{'statusCode':_0x5773ba[_0x5d1657(0x1b6)],'error':_0x5c4f30,'message':_0x5c4f30};return void 0x0;}function handleErrorResponse(_0x36c51e){const _0x1b3541=a0_0x25037f;if(!_0x36c51e[_0x1b3541(0x18f)])throw new MissingResponseError(_0x36c51e[_0x1b3541(0x161)]||_0x1b3541(0x156));const {status:_0x19f375,data:_0x441c44}=_0x36c51e[_0x1b3541(0x18f)],_0x1cac24=parseErrorData(_0x441c44,{'statusCode':_0x19f375});if(!_0x1cac24)throw new MiscellaneousError({'statusCode':_0x19f375,'error':_0x1b3541(0x154),'message':_0x36c51e[_0x1b3541(0x161)]||_0x1b3541(0x1b4)});switch(_0x19f375){case 0x190:throw new ValidationError(_0x1cac24);case 0x191:throw new UnauthorizedError(_0x1cac24);case 0x193:throw new ForbiddenError(_0x1cac24);case 0x194:throw new NotFoundError(_0x1cac24);case 0x198:throw new RequestTimeoutError(_0x1cac24);case 0x199:throw new ConflictError(_0x1cac24);default:if(_0x19f375>=0x1f4)throw new ServerError(_0x1cac24);throw new MiscellaneousError(_0x1cac24);}}function getVersion(){const _0x240d2d=a0_0x25037f;return _0x240d2d(0x174);}function createRequestInterceptor(_0xd412d0,_0x289c7f){return _0x2ae78b=>{const _0x177567=a0_0x4c90;return _0x2ae78b[_0x177567(0x119)]&&(_0x2ae78b['headers'][_0x177567(0x17f)]='Bearer\x20'+_0xd412d0,_0x2ae78b[_0x177567(0x119)][_0x177567(0x19b)]=_0x289c7f,_0x2ae78b[_0x177567(0x119)]['User-Agent']=_0x177567(0x15a)+getVersion()),_0x2ae78b;};}function createResponseErrorInterceptor(){return _0x436452=>{handleErrorResponse(_0x436452);};}var ChecklyClient=class{[a0_0x25037f(0x1a7)];['baseUrl'];[a0_0x25037f(0x104)];[a0_0x25037f(0xf1)];constructor(_0x1e7de9){const _0xbebcb5=a0_0x25037f;this[_0xbebcb5(0x104)]=_0x1e7de9[_0xbebcb5(0x104)],this[_0xbebcb5(0x1a7)]=_0x1e7de9[_0xbebcb5(0x1a7)],this['baseUrl']=_0x1e7de9[_0xbebcb5(0x188)],this[_0xbebcb5(0xf1)]=a0_0x5c40a8['create']({'baseURL':this[_0xbebcb5(0x188)],'timeout':0x1d4c0,'maxContentLength':Number[_0xbebcb5(0x182)],'maxBodyLength':Number[_0xbebcb5(0x182)]}),this[_0xbebcb5(0xf1)][_0xbebcb5(0xfc)][_0xbebcb5(0x124)]['use'](createRequestInterceptor(this[_0xbebcb5(0x1a7)],this['accountId'])),this['api']['interceptors'][_0xbebcb5(0x18f)][_0xbebcb5(0xf4)](_0x1139c2=>_0x1139c2,createResponseErrorInterceptor());}[a0_0x25037f(0x16c)](){const _0x4e54dc=a0_0x25037f;return this[_0x4e54dc(0xf1)];}};import a0_0x337fd1 from'form-data';var TestResults=class{constructor(_0x5c2d8d){const _0x125ee5=a0_0x25037f;this[_0x125ee5(0xf1)]=_0x5c2d8d;}async['createTestSession'](_0x7fe39a){const _0x22cafe=a0_0x25037f,_0x12b9eb=await this['api'][_0x22cafe(0x13c)](_0x22cafe(0x145),_0x7fe39a);return _0x12b9eb[_0x22cafe(0x18b)];}async['uploadTestResultAsset'](_0x4cda13,_0x3b6e14,_0x1ae84f){const _0x2acc8f=a0_0x25037f,_0x533dc4=new a0_0x337fd1();_0x533dc4[_0x2acc8f(0x155)](_0x2acc8f(0x15b),_0x1ae84f,{'filename':_0x2acc8f(0x19c),'contentType':_0x2acc8f(0x16d)});const _0x4edc6d=await this[_0x2acc8f(0xf1)][_0x2acc8f(0x13c)](_0x2acc8f(0xe4)+_0x4cda13+_0x2acc8f(0x131)+_0x3b6e14+_0x2acc8f(0x12b),_0x533dc4,{'headers':{..._0x533dc4[_0x2acc8f(0xd4)]()}});return _0x4edc6d[_0x2acc8f(0x18b)];}async[a0_0x25037f(0x14c)](_0x11e3e8,_0x106ec3,_0x3507e4){const _0x4e0a76=a0_0x25037f,_0x927d3d=await this[_0x4e0a76(0xf1)]['post']('/next/test-sessions/'+_0x11e3e8+'/results/'+_0x106ec3,_0x3507e4);return _0x927d3d[_0x4e0a76(0x18b)];}};import*as a0_0x34e740 from'fs';import*as a0_0x2f8d03 from'os';function a0_0x4c90(_0x2e1c0f,_0x127217){_0x2e1c0f=_0x2e1c0f-0xd3;const _0x1d61f6=a0_0x1d61();let _0x4c9085=_0x1d61f6[_0x2e1c0f];return _0x4c9085;}import*as a0_0x3ba4ad from'path';import{ZipArchive}from'archiver';var Zipper=class{[a0_0x25037f(0xd3)];constructor(_0xec772a){const _0x16573c=a0_0x25037f;this['outputPath']=_0xec772a[_0x16573c(0xd3)];}async[a0_0x25037f(0xe8)](_0x26330b,_0x2cda22){const _0x451191=[];return new Promise((_0x4bd962,_0x1daf9d)=>{const _0x3a2efe=a0_0x4c90;try{const _0x54862f=a0_0x34e740[_0x3a2efe(0xe2)](this[_0x3a2efe(0xd3)]),_0xcdeed4=new ZipArchive({'zlib':{'level':0x0}});_0xcdeed4['on'](_0x3a2efe(0x1ab),_0x251899=>{const _0x391eef=_0x3a2efe,_0x58a42c=_0x251899[_0x391eef(0x169)][_0x391eef(0x113)](/\\/g,'/'),_0x2c9eee=_0x251899[_0x391eef(0x157)]?.['contents']??0x0,_0x50eb64=_0x251899[_0x391eef(0x157)]?.['contents']+(_0x251899[_0x391eef(0xf6)]??0x0)-0x1;_0x451191[_0x391eef(0xee)]({'name':_0x58a42c,'start':_0x2c9eee,'end':_0x50eb64});}),_0x54862f['on'](_0x3a2efe(0x121),()=>{const _0x51dd03=_0x3a2efe,_0x179a32=_0xcdeed4[_0x51dd03(0x187)]();_0x4bd962({'zipPath':this[_0x51dd03(0xd3)],'size':_0x179a32,'entryCount':_0x451191[_0x51dd03(0x143)],'entries':_0x451191});}),_0xcdeed4['on'](_0x3a2efe(0x1a4),_0x26b3af=>{_0x1daf9d(_0x26b3af);}),_0x54862f['on'](_0x3a2efe(0x1a4),_0x333236=>{_0x1daf9d(_0x333236);}),_0xcdeed4['pipe'](_0x54862f);if(!a0_0x34e740['existsSync'](_0x26330b)){_0x1daf9d(new Error(_0x3a2efe(0x1af)+_0x26330b));return;}const _0x29f5f0=this[_0x3a2efe(0x189)](_0x26330b);_0xcdeed4['file'](_0x29f5f0,{'name':'output/playwright-test-report.json'});for(const _0x20fd2c of _0x2cda22){if(!a0_0x34e740[_0x3a2efe(0xd6)](_0x20fd2c[_0x3a2efe(0x16b)])){_0x1daf9d(new Error(_0x3a2efe(0x11c)+_0x20fd2c[_0x3a2efe(0x16b)]));return;}_0xcdeed4[_0x3a2efe(0x123)](_0x20fd2c[_0x3a2efe(0x16b)],{'name':_0x20fd2c[_0x3a2efe(0x10a)]});}_0xcdeed4['finalize']();}catch(_0x2d98ee){_0x1daf9d(_0x2d98ee);}});}['transformJsonReport'](_0x3984e8){const _0x2c0648=a0_0x25037f,_0x20d4b4=a0_0x34e740[_0x2c0648(0xf0)](_0x3984e8,_0x2c0648(0x10c)),_0x30d61e=JSON['parse'](_0x20d4b4);this['transformAttachmentPaths'](_0x30d61e);const _0x4864b4=a0_0x3ba4ad['join'](a0_0x2f8d03[_0x2c0648(0x12a)](),_0x2c0648(0x1a8)+Date[_0x2c0648(0x18d)]()+'.json');return a0_0x34e740[_0x2c0648(0x118)](_0x4864b4,JSON['stringify'](_0x30d61e,null,0x2)),_0x4864b4;}[a0_0x25037f(0x112)](_0x2ff750){const _0x501408=a0_0x25037f;if(typeof _0x2ff750!==_0x501408(0x166)||_0x2ff750===null)return;if(Array[_0x501408(0x1a1)](_0x2ff750)){_0x2ff750[_0x501408(0x102)](_0x74bd9a=>this[_0x501408(0x112)](_0x74bd9a));return;}_0x2ff750[_0x501408(0x10f)]&&Array[_0x501408(0x1a1)](_0x2ff750[_0x501408(0x10f)])&&_0x2ff750[_0x501408(0x10f)]['forEach'](_0x43b73a=>{const _0x3d2b6f=_0x501408;_0x43b73a[_0x3d2b6f(0x186)]&&typeof _0x43b73a[_0x3d2b6f(0x186)]===_0x3d2b6f(0x172)&&(_0x43b73a[_0x3d2b6f(0x186)]=this['normalizeAttachmentPath'](_0x43b73a['path']));}),Object[_0x501408(0xf7)](_0x2ff750)[_0x501408(0x102)](_0x2c5f37=>this['transformAttachmentPaths'](_0x2c5f37));}[a0_0x25037f(0x11e)](_0x113c03){const _0x2459c4=a0_0x25037f,_0x5e74b6=_0x113c03[_0x2459c4(0x113)](/\\/g,'/'),_0x4841e7=_0x5e74b6[_0x2459c4(0xfa)](_0x2459c4(0x126));if(_0x4841e7!==-0x1)return _0x5e74b6[_0x2459c4(0x1a9)](_0x4841e7);const _0x3fb0b0=_0x5e74b6['indexOf'](_0x2459c4(0x14a));if(_0x3fb0b0!==-0x1){const _0x51bd78=_0x5e74b6[_0x2459c4(0x1a9)](0x0,_0x3fb0b0),_0x5ab4f9=_0x51bd78[_0x2459c4(0x115)]('/'),_0x19cc96=_0x5ab4f9!==-0x1?_0x5ab4f9+0x1:0x0;return _0x5e74b6[_0x2459c4(0x1a9)](_0x19cc96);}const _0x21f4f9=['__screenshots__/',_0x2459c4(0xdc),_0x2459c4(0x1b1),_0x2459c4(0x142)];for(const _0x2077a4 of _0x21f4f9){const _0x2b257b=_0x5e74b6['indexOf'](_0x2077a4);if(_0x2b257b!==-0x1)return _0x5e74b6[_0x2459c4(0x1a9)](_0x2b257b);}return console[_0x2459c4(0x12f)](_0x2459c4(0xdd)+_0x113c03),_0x113c03;}},__filename=fileURLToPath(import.meta.url),__dirname=dirname(__filename),packageJson=JSON[a0_0x25037f(0x13f)](a0_0x546335(a0_0x55d174(__dirname,'..',a0_0x25037f(0x159)),'utf-8')),pkgVersion=packageJson[a0_0x25037f(0xdb)],pluralRules=new Intl[(a0_0x25037f(0x18a))](a0_0x25037f(0x17c)),projectForms={'zero':a0_0x25037f(0x14d),'one':'Project','two':a0_0x25037f(0x129),'few':a0_0x25037f(0x129),'many':a0_0x25037f(0x129),'other':'Projects'};function getApiUrl(_0x2329a1){const _0x542f2d=a0_0x25037f,_0x3963e5={'local':_0x542f2d(0xe6),'development':_0x542f2d(0x15c),'staging':'https://api-test.checklyhq.com','production':_0x542f2d(0x11d)};return _0x3963e5[_0x2329a1];}function getEnvironment(_0x4e9281){const _0x5ded98=a0_0x25037f,_0xe27536=_0x4e9281?.[_0x5ded98(0xed)],_0x1e298e=process['env'][_0x5ded98(0x12d)],_0x13d83c=_0xe27536||_0x1e298e||_0x5ded98(0x17a),_0x1ae78c=[_0x5ded98(0x1a6),_0x5ded98(0x111),_0x5ded98(0x150),'production'];if(!_0x1ae78c[_0x5ded98(0x146)](_0x13d83c))return console[_0x5ded98(0x12f)]('[Checkly\x20Reporter]\x20Invalid\x20environment\x20\x22'+_0x13d83c+_0x5ded98(0x1ad)),_0x5ded98(0x17a);return _0x13d83c;}function getDirectoryName(){const _0x1db52d=a0_0x25037f,_0x4938c5=process[_0x1db52d(0x18e)]();let _0x354907=a0_0x3d351b['basename'](_0x4938c5);return(!_0x354907||_0x354907==='/'||_0x354907==='.')&&(_0x354907=_0x1db52d(0x114)),_0x354907=_0x354907[_0x1db52d(0x113)](/[<>:"|?*]/g,'-'),_0x354907[_0x1db52d(0x143)]>0xff&&(_0x354907=_0x354907[_0x1db52d(0x1a9)](0x0,0xff)),_0x354907;}var ChecklyReporter=class{['options'];[a0_0x25037f(0x15d)];[a0_0x25037f(0x15e)];[a0_0x25037f(0xe3)];[a0_0x25037f(0x1aa)];[a0_0x25037f(0x190)];[a0_0x25037f(0xff)]={'passed':0x0,'failed':0x0,'flaky':0x0};constructor(_0x409731={}){const _0xfe9a07=a0_0x25037f,_0xcf40d4=getEnvironment(_0x409731),_0x197ccc=getApiUrl(_0xcf40d4),_0x2419ea=process['env'][_0xfe9a07(0x191)]||_0x409731[_0xfe9a07(0x1a7)],_0x1e6769=process[_0xfe9a07(0x125)][_0xfe9a07(0x17b)]||_0x409731[_0xfe9a07(0x104)];this['options']={'accountId':_0x1e6769,'apiKey':_0x2419ea,'outputPath':_0x409731['outputPath']??_0xfe9a07(0x108),'jsonReportPath':_0x409731[_0xfe9a07(0x1a2)]??_0xfe9a07(0x120),'testResultsDir':_0x409731[_0xfe9a07(0x1a3)]??'test-results','dryRun':_0x409731['dryRun']??![],'sessionName':_0x409731[_0xfe9a07(0x17d)]},this['assetCollector']=new AssetCollector(this[_0xfe9a07(0x12c)]['testResultsDir']),this['zipper']=new Zipper({'outputPath':this[_0xfe9a07(0x12c)][_0xfe9a07(0xd3)]});if(!this[_0xfe9a07(0x12c)][_0xfe9a07(0xea)]&&this['options'][_0xfe9a07(0x1a7)]&&this[_0xfe9a07(0x12c)][_0xfe9a07(0x104)]){const _0x5ff83=new ChecklyClient({'apiKey':this[_0xfe9a07(0x12c)]['apiKey'],'accountId':this[_0xfe9a07(0x12c)][_0xfe9a07(0x104)],'baseUrl':_0x197ccc});this['testResults']=new TestResults(_0x5ff83[_0xfe9a07(0x16c)]());}}[a0_0x25037f(0x138)](_0x2a547e){const _0x5508d5=a0_0x25037f,{sessionName:_0x248951}=this[_0x5508d5(0x12c)];if(typeof _0x248951===_0x5508d5(0x17e))return _0x248951(_0x2a547e);if(typeof _0x248951===_0x5508d5(0x172))return _0x248951;return'Playwright\x20Test\x20Session:\x20'+_0x2a547e[_0x5508d5(0x167)];}[a0_0x25037f(0xec)](_0x5e3e32,_0x3f2a66){const _0x3290db=a0_0x25037f;this[_0x3290db(0x190)]=new Date();if(!this[_0x3290db(0xe3)])return;try{const _0x4ac4d2=getDirectoryName(),_0x4ab124=this[_0x3290db(0x138)]({'directoryName':_0x4ac4d2,'config':_0x5e3e32,'suite':_0x3f2a66}),_0x1be3ae=[{'name':_0x4ac4d2}],_0x2cec3f=process['env'][_0x3290db(0x127)]?'https://github.com/'+process[_0x3290db(0x125)][_0x3290db(0x127)]:void 0x0,_0x559466=_0x2cec3f?{'repoUrl':_0x2cec3f,'commitId':process[_0x3290db(0x125)][_0x3290db(0x177)],'branchName':process[_0x3290db(0x125)]['GITHUB_REF_NAME'],'commitOwner':process[_0x3290db(0x125)][_0x3290db(0x16f)],'commitMessage':process[_0x3290db(0x125)]['GITHUB_EVENT_NAME']}:void 0x0;this[_0x3290db(0xe3)]['createTestSession']({'name':_0x4ab124,'environment':process[_0x3290db(0x125)][_0x3290db(0x162)]||_0x3290db(0x180),'repoInfo':_0x559466,'startedAt':this['startTime']['getTime'](),'testResults':_0x1be3ae,'provider':'PW_REPORTER'})[_0x3290db(0x198)](_0x48ba5b=>{const _0x2255ea=_0x3290db;this[_0x2255ea(0x1aa)]=_0x48ba5b;})[_0x3290db(0x15f)](_0x255409=>{const _0x56bcab=_0x3290db;console[_0x56bcab(0x1a4)](_0x56bcab(0x136),_0x255409[_0x56bcab(0x161)]);});}catch(_0x4d890a){console[_0x3290db(0x1a4)]('[Checkly\x20Reporter]\x20Error\x20in\x20onBegin:',_0x4d890a);}}[a0_0x25037f(0xd5)](_0x1992ad,_0x291c42){const _0x3bd4db=a0_0x25037f;try{const _0xdc5445=_0x1992ad[_0x3bd4db(0x148)](),_0xec18fb=_0x291c42[_0x3bd4db(0x19a)]===_0x1992ad[_0x3bd4db(0x197)]||_0xdc5445!==_0x3bd4db(0x16e);if(!_0xec18fb)return;const _0x4e2d2a=_0xdc5445===_0x3bd4db(0xfd);if(_0x4e2d2a)this[_0x3bd4db(0xff)]['flaky']++,this[_0x3bd4db(0xff)][_0x3bd4db(0x135)]++;else{if(_0x291c42[_0x3bd4db(0x19d)]==='passed')this[_0x3bd4db(0xff)][_0x3bd4db(0x135)]++;else(_0x291c42[_0x3bd4db(0x19d)]===_0x3bd4db(0x185)||_0x291c42[_0x3bd4db(0x19d)]==='timedOut')&&this[_0x3bd4db(0xff)][_0x3bd4db(0x185)]++;}}catch(_0x158fed){console[_0x3bd4db(0x1a4)]('[Checkly\x20Reporter]\x20Error\x20in\x20onTestEnd:',_0x158fed);}}async[a0_0x25037f(0x11b)](){const _0x55e923=a0_0x25037f;try{const _0x31c1ca=this[_0x55e923(0x12c)]['jsonReportPath'];if(!a0_0x24212e[_0x55e923(0xd6)](_0x31c1ca)){console['error'](_0x55e923(0x1b2)+_0x31c1ca),console[_0x55e923(0x1a4)](_0x55e923(0xef)),console[_0x55e923(0x1a4)](_0x55e923(0x18c));return;}const _0x547fab=a0_0x24212e['readFileSync'](_0x31c1ca,_0x55e923(0x10c)),_0x38ab84=JSON[_0x55e923(0x13f)](_0x547fab),_0x2eb42d=await this[_0x55e923(0x15d)]['collectAssets'](_0x38ab84),_0x3e746a=await this[_0x55e923(0x15e)][_0x55e923(0xe8)](_0x31c1ca,_0x2eb42d);if(this['testResults']&&this[_0x55e923(0x1aa)]){await this['uploadResults'](_0x38ab84,_0x3e746a[_0x55e923(0x170)],_0x3e746a['entries']);if(!this[_0x55e923(0x12c)][_0x55e923(0xea)])try{a0_0x24212e[_0x55e923(0x132)](_0x3e746a[_0x55e923(0x170)]);}catch(_0x417f1c){console['warn'](_0x55e923(0x141)+_0x417f1c);}}this['testResults']&&this[_0x55e923(0x1aa)]?.[_0x55e923(0x152)]&&this['printSummary'](_0x38ab84,this[_0x55e923(0x1aa)]);}catch(_0x506259){console[_0x55e923(0x1a4)](_0x55e923(0x10d),_0x506259);}}[a0_0x25037f(0x151)](_0x336e59,_0x392538){const _0x1c3966=a0_0x25037f,_0x49bfb5=pluralRules[_0x1c3966(0x164)](_0x336e59[_0x1c3966(0x1ac)][_0x1c3966(0x144)][_0x1c3966(0x143)]);console[_0x1c3966(0x160)](_0x1c3966(0x14e)),console[_0x1c3966(0x160)](_0x1c3966(0xfe)+pkgVersion),console[_0x1c3966(0x160)]('🎭\x20Playwright:\x20'+_0x336e59['config']['version']),console[_0x1c3966(0x160)](_0x1c3966(0x116)+projectForms[_0x49bfb5]+':\x20'+_0x336e59[_0x1c3966(0x1ac)][_0x1c3966(0x144)]['map'](({name:_0x5ec683})=>_0x5ec683)['join'](',')),console[_0x1c3966(0x160)](_0x1c3966(0xf9)+_0x392538[_0x1c3966(0x152)]),console[_0x1c3966(0x160)]('\x0a======================================================');}async[a0_0x25037f(0x196)](_0x45eb42,_0x279816,_0x16ddf8){const _0x2593bc=a0_0x25037f;if(!this[_0x2593bc(0xe3)]||!this[_0x2593bc(0x1aa)])return;try{const {failed:_0x35b0a0,flaky:_0x45f4f3}=this[_0x2593bc(0xff)],_0x5c9dbb=_0x35b0a0>0x0?_0x2593bc(0x195):_0x2593bc(0x1b5),_0x5e8eb0=_0x35b0a0===0x0&&_0x45f4f3>0x0,_0x36431d=new Date(),_0x12c827=this[_0x2593bc(0x190)]?Math[_0x2593bc(0x173)](0x0,_0x36431d['getTime']()-this[_0x2593bc(0x190)][_0x2593bc(0x149)]()):0x0,_0xd3c5a8=(await a0_0x24212e[_0x2593bc(0xfb)][_0x2593bc(0x1b3)](_0x279816))[_0x2593bc(0x133)];if(this[_0x2593bc(0x1aa)]['testResults'][_0x2593bc(0x143)]>0x0){const _0x26dce5=this[_0x2593bc(0x1aa)]['testResults'][0x0];let _0x2c5831;if(_0xd3c5a8>0x0)try{const _0x4bbd64=a0_0x24212e[_0x2593bc(0x178)](_0x279816),_0x3d4bb2=await this[_0x2593bc(0xe3)][_0x2593bc(0xf8)](this['testSession'][_0x2593bc(0x194)],_0x26dce5['testResultId'],_0x4bbd64);_0x2c5831=_0x3d4bb2['assetId'];}catch(_0x41f4a3){const _0x1bfee4=_0x41f4a3 instanceof Error?_0x41f4a3['message']:String(_0x41f4a3);console[_0x2593bc(0x1a4)](_0x2593bc(0x128),_0x1bfee4);}await this['testResults']['updateTestResult'](this[_0x2593bc(0x1aa)]['testSessionId'],_0x26dce5['testResultId'],{'status':_0x5c9dbb,'assetEntries':_0x2c5831?_0x16ddf8:void 0x0,'isDegraded':_0x5e8eb0,'startedAt':this[_0x2593bc(0x190)]?.[_0x2593bc(0x117)](),'stoppedAt':_0x36431d[_0x2593bc(0x117)](),'responseTime':_0x12c827,'metadata':{'usageData':{'s3PostTotalBytes':_0xd3c5a8}}});}}catch(_0x4b7ca0){const _0x590df2=_0x4b7ca0 instanceof Error?_0x4b7ca0[_0x2593bc(0x161)]:String(_0x4b7ca0);console['error'](_0x2593bc(0x13d),_0x590df2);}}[a0_0x25037f(0x103)](_0x190c31){const _0x25acaa=a0_0x25037f;console[_0x25acaa(0x1a4)]('[Checkly\x20Reporter]\x20Global\x20error:',_0x190c31);}};function a0_0x1d61(){const _0x119b3a=['0.1.0','shouldSkipFile','join','GITHUB_SHA','createReadStream','add','production','CHECKLY_ACCOUNT_ID','en-US','sessionName','function','Authorization','test','4019448wjmKDN','POSITIVE_INFINITY','constructor','.jpg','failed','path','pointer','baseUrl','transformJsonReport','PluralRules','data','\x20\x20reporter:\x20[\x0a\x20\x20\x20\x20[\x27json\x27,\x20{\x20outputFile:\x20\x27test-results/playwright-test-report.json\x27\x20}],\x0a\x20\x20\x20\x20[\x27@checkly/playwright-reporter\x27]\x0a\x20\x20]','now','cwd','response','startTime','CHECKLY_API_KEY','getImageContentType','application/octet-stream','testSessionId','FAILED','uploadResults','retries','then','4204410mSaIvj','retry','x-checkly-account','assets.zip','status','.gif','image/gif','3335478FRUWTn','isArray','jsonReportPath','testResultsDir','error','extname','local','apiKey','playwright-test-report-','substring','testSession','entry','config','\x22,\x20using\x20\x22production\x22','video/x-msvideo','Report\x20file\x20not\x20found:\x20','.json','screenshots/','[Checkly\x20Reporter]\x20ERROR:\x20JSON\x20report\x20not\x20found\x20at:\x20','stat','An\x20error\x20occurred','PASSED','statusCode','outputPath','getHeaders','onTestEnd','existsSync','.webm','video','video/quicktime','createAsset','version','__snapshots__/','[Checkly\x20Reporter]\x20Could\x20not\x20normalize\x20attachment\x20path:\x20','34882nkVoqT','.jpeg','determineAssetType','785245HzpgOz','createWriteStream','testResults','/next/test-sessions/','readdirSync','http://127.0.0.1:3000','collectAssetsRecursive','createZip','15TPJqcE','dryRun','image/png','onBegin','environment','push','[Checkly\x20Reporter]\x20Make\x20sure\x20to\x20configure\x20the\x20json\x20reporter\x20before\x20the\x20checkly\x20reporter:','readFileSync','api','getVideoContentType','.png','use','.bmp','csize','values','uploadTestResultAsset','🔗\x20Test\x20session\x20URL:\x20','indexOf','promises','interceptors','flaky','🦝\x20Checkly\x20reporter:\x20','testCounts','isFile','MissingResponseError','forEach','onError','accountId','basename','isDirectory','other','checkly-report.zip','screenshot','archivePath','text/html','utf-8','[Checkly\x20Reporter]\x20ERROR\x20creating\x20report:','text/plain','attachments','split','development','transformAttachmentPaths','replace','playwright-tests','lastIndexOf','📔\x20','toISOString','writeFileSync','headers','.mov','onEnd','Asset\x20file\x20not\x20found:\x20','https://api.checklyhq.com','normalizeAttachmentPath','.svg','test-results/playwright-test-report.json','close','image/svg+xml','file','request','env','test-results/','GITHUB_REPOSITORY','[Checkly\x20Reporter]\x20Asset\x20upload\x20failed:','Projects','tmpdir','/assets','options','CHECKLY_ENV','video/webm','warn','image/bmp','/results/','unlinkSync','size','.html','passed','[Checkly\x20Reporter]\x20Failed\x20to\x20create\x20test\x20session:','resolve','resolveSessionName','.avi','trace','10372725gFlEFx','post','[Checkly\x20Reporter]\x20Failed\x20to\x20upload\x20results:','image/jpeg','parse','24vXADxZ','[Checkly\x20Reporter]\x20Warning:\x20Could\x20not\x20delete\x20ZIP\x20file:\x20','snapshots/','length','projects','/next/test-sessions/create','includes','application/json','outcome','getTime','-snapshots/','552620qOgnXt','updateTestResult','Project','\x0a======================================================\x0a','9zkaFKf','staging','printSummary','link','14mltush','Unknown\x20error','append','Network\x20error','_offsets','attachment','package.json','@checkly/playwright-reporter/','assets','https://api-dev.checklyhq.com','assetCollector','zipper','catch','log','message','NODE_ENV','.zip','select','errorCode','object','directoryName','collectAssets','name','some','sourcePath','getAxiosInstance','application/zip','unexpected','GITHUB_ACTOR','zipPath','.htm','string','max'];a0_0x1d61=function(){return _0x119b3a;};return a0_0x1d61();}export{AssetCollector,ChecklyReporter,Zipper,ChecklyReporter as default};
|
|
1
|
+
// ../utils/src/asset-collector.ts
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
var AssetCollector = class {
|
|
5
|
+
constructor(testResultsDir) {
|
|
6
|
+
this.testResultsDir = testResultsDir;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Collects assets from test results directory
|
|
10
|
+
* Uses a two-phase approach to support both normal test runs and blob merge scenarios:
|
|
11
|
+
* 1. First, extract attachment paths from the JSON report (works for merge scenarios)
|
|
12
|
+
* 2. Fall back to directory scanning for any additional assets not in the report
|
|
13
|
+
*
|
|
14
|
+
* @param report Optional Playwright JSONReport with attachment paths
|
|
15
|
+
* @returns Array of Asset objects with source and archive paths
|
|
16
|
+
*/
|
|
17
|
+
async collectAssets(report) {
|
|
18
|
+
const assets = [];
|
|
19
|
+
const addedPaths = /* @__PURE__ */ new Set();
|
|
20
|
+
if (report) {
|
|
21
|
+
this.collectAssetsFromReport(report, assets, addedPaths);
|
|
22
|
+
}
|
|
23
|
+
if (fs.existsSync(this.testResultsDir)) {
|
|
24
|
+
await this.collectAssetsRecursive(this.testResultsDir, assets, addedPaths);
|
|
25
|
+
}
|
|
26
|
+
return assets;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Extracts assets from JSON report attachment paths
|
|
30
|
+
* Essential for blob merge scenarios where attachments are extracted to temporary locations
|
|
31
|
+
*/
|
|
32
|
+
collectAssetsFromReport(report, assets, addedPaths) {
|
|
33
|
+
const processResults = (results) => {
|
|
34
|
+
for (const result of results) {
|
|
35
|
+
if (result.attachments && Array.isArray(result.attachments)) {
|
|
36
|
+
for (const attachment of result.attachments) {
|
|
37
|
+
if (attachment.path && typeof attachment.path === "string") {
|
|
38
|
+
const sourcePath = attachment.path;
|
|
39
|
+
if (addedPaths.has(sourcePath)) continue;
|
|
40
|
+
if (!fs.existsSync(sourcePath)) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const archivePath = this.determineArchivePath(sourcePath);
|
|
44
|
+
const asset = this.createAsset(sourcePath, archivePath);
|
|
45
|
+
assets.push(asset);
|
|
46
|
+
addedPaths.add(sourcePath);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const processSuite = (suite) => {
|
|
53
|
+
if (suite.specs) {
|
|
54
|
+
for (const spec of suite.specs) {
|
|
55
|
+
if (spec.tests) {
|
|
56
|
+
for (const test of spec.tests) {
|
|
57
|
+
if (test.results) {
|
|
58
|
+
processResults(test.results);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (suite.suites) {
|
|
65
|
+
for (const nestedSuite of suite.suites) {
|
|
66
|
+
processSuite(nestedSuite);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
if (report.suites) {
|
|
71
|
+
for (const suite of report.suites) {
|
|
72
|
+
processSuite(suite);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Determines the archive path for an asset based on its source path
|
|
78
|
+
* Handles both test-results paths and blob merge extraction paths
|
|
79
|
+
*/
|
|
80
|
+
determineArchivePath(sourcePath) {
|
|
81
|
+
const normalizedPath = sourcePath.replace(/\\/g, "/");
|
|
82
|
+
const testResultsIndex = normalizedPath.indexOf("test-results/");
|
|
83
|
+
if (testResultsIndex !== -1) {
|
|
84
|
+
return normalizedPath.substring(testResultsIndex);
|
|
85
|
+
}
|
|
86
|
+
const blobResourcesMatch = normalizedPath.match(/.*?[\w-]*blob-report[\w-]*\/resources\/(.+)$/);
|
|
87
|
+
if (blobResourcesMatch) {
|
|
88
|
+
return `test-results/resources/${blobResourcesMatch[1]}`;
|
|
89
|
+
}
|
|
90
|
+
const resourcesIndex = normalizedPath.indexOf("/resources/");
|
|
91
|
+
if (resourcesIndex !== -1) {
|
|
92
|
+
const filename = normalizedPath.split("/").pop() || "";
|
|
93
|
+
return `test-results/resources/${filename}`;
|
|
94
|
+
}
|
|
95
|
+
const parts = normalizedPath.split("/");
|
|
96
|
+
if (parts.length >= 2) {
|
|
97
|
+
return `test-results/${parts.slice(-2).join("/")}`;
|
|
98
|
+
}
|
|
99
|
+
return `test-results/${path.basename(sourcePath)}`;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Recursively traverses directories to collect assets
|
|
103
|
+
*/
|
|
104
|
+
async collectAssetsRecursive(currentDir, assets, addedPaths) {
|
|
105
|
+
let entries;
|
|
106
|
+
try {
|
|
107
|
+
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
108
|
+
} catch {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const baseDirName = path.basename(path.resolve(this.testResultsDir));
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
114
|
+
const relativeFromBase = path.relative(this.testResultsDir, fullPath);
|
|
115
|
+
const archivePath = path.join(baseDirName, relativeFromBase);
|
|
116
|
+
if (this.shouldSkipFile(entry.name)) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (entry.isDirectory()) {
|
|
120
|
+
await this.collectAssetsRecursive(fullPath, assets, addedPaths);
|
|
121
|
+
} else if (entry.isFile()) {
|
|
122
|
+
if (!addedPaths.has(fullPath)) {
|
|
123
|
+
const asset = this.createAsset(fullPath, archivePath);
|
|
124
|
+
assets.push(asset);
|
|
125
|
+
addedPaths.add(fullPath);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Determines if a file should be skipped
|
|
132
|
+
*/
|
|
133
|
+
shouldSkipFile(filename) {
|
|
134
|
+
const skipPatterns = [
|
|
135
|
+
/^\./,
|
|
136
|
+
// Hidden files (.DS_Store, .gitkeep, etc.)
|
|
137
|
+
/\.tmp$/i,
|
|
138
|
+
// Temporary files
|
|
139
|
+
/~$/,
|
|
140
|
+
// Backup files
|
|
141
|
+
/\.swp$/i,
|
|
142
|
+
// Vim swap files
|
|
143
|
+
/\.lock$/i,
|
|
144
|
+
// Lock files
|
|
145
|
+
/^playwright-test-report\.json$/i
|
|
146
|
+
// Skip the JSON report itself
|
|
147
|
+
];
|
|
148
|
+
return skipPatterns.some((pattern) => pattern.test(filename));
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Creates an Asset object from file path
|
|
152
|
+
*/
|
|
153
|
+
createAsset(sourcePath, archivePath) {
|
|
154
|
+
const ext = path.extname(sourcePath).toLowerCase();
|
|
155
|
+
const { type, contentType } = this.determineAssetType(ext);
|
|
156
|
+
const normalizedArchivePath = archivePath.split(path.sep).join("/");
|
|
157
|
+
return {
|
|
158
|
+
sourcePath,
|
|
159
|
+
archivePath: normalizedArchivePath,
|
|
160
|
+
type,
|
|
161
|
+
contentType
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Determines asset type and content type from file extension
|
|
166
|
+
*/
|
|
167
|
+
determineAssetType(ext) {
|
|
168
|
+
if ([".png", ".jpg", ".jpeg", ".gif", ".bmp", ".svg"].includes(ext)) {
|
|
169
|
+
return {
|
|
170
|
+
type: "screenshot",
|
|
171
|
+
contentType: this.getImageContentType(ext)
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if ([".webm", ".mp4", ".avi", ".mov"].includes(ext)) {
|
|
175
|
+
return {
|
|
176
|
+
type: "video",
|
|
177
|
+
contentType: this.getVideoContentType(ext)
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (ext === ".zip") {
|
|
181
|
+
return {
|
|
182
|
+
type: "trace",
|
|
183
|
+
contentType: "application/zip"
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (ext === ".html" || ext === ".htm") {
|
|
187
|
+
return {
|
|
188
|
+
type: "attachment",
|
|
189
|
+
contentType: "text/html"
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
if (ext === ".json") {
|
|
193
|
+
return {
|
|
194
|
+
type: "attachment",
|
|
195
|
+
contentType: "application/json"
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
if (ext === ".txt" || ext === ".log") {
|
|
199
|
+
return {
|
|
200
|
+
type: "attachment",
|
|
201
|
+
contentType: "text/plain"
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
type: "other",
|
|
206
|
+
contentType: "application/octet-stream"
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Gets content type for image files
|
|
211
|
+
*/
|
|
212
|
+
getImageContentType(ext) {
|
|
213
|
+
const contentTypes = {
|
|
214
|
+
".png": "image/png",
|
|
215
|
+
".jpg": "image/jpeg",
|
|
216
|
+
".jpeg": "image/jpeg",
|
|
217
|
+
".gif": "image/gif",
|
|
218
|
+
".bmp": "image/bmp",
|
|
219
|
+
".svg": "image/svg+xml"
|
|
220
|
+
};
|
|
221
|
+
return contentTypes[ext] || "image/png";
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Gets content type for video files
|
|
225
|
+
*/
|
|
226
|
+
getVideoContentType(ext) {
|
|
227
|
+
const contentTypes = {
|
|
228
|
+
".webm": "video/webm",
|
|
229
|
+
".mp4": "video/mp4",
|
|
230
|
+
".avi": "video/x-msvideo",
|
|
231
|
+
".mov": "video/quicktime"
|
|
232
|
+
};
|
|
233
|
+
return contentTypes[ext] || "video/webm";
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// ../utils/src/zipper.ts
|
|
238
|
+
import * as fs2 from "fs";
|
|
239
|
+
import * as os from "os";
|
|
240
|
+
import * as path2 from "path";
|
|
241
|
+
import { ZipArchive } from "archiver";
|
|
242
|
+
var Zipper = class {
|
|
243
|
+
outputPath;
|
|
244
|
+
constructor(options) {
|
|
245
|
+
this.outputPath = options.outputPath;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Creates a ZIP archive containing the JSON report and assets
|
|
249
|
+
* @param reportPath - Path to the JSON report file
|
|
250
|
+
* @param assets - Array of assets to include in the ZIP
|
|
251
|
+
* @returns ZIP creation result with metadata
|
|
252
|
+
*/
|
|
253
|
+
async createZip(reportPath, assets) {
|
|
254
|
+
const entries = [];
|
|
255
|
+
return new Promise((resolve2, reject) => {
|
|
256
|
+
try {
|
|
257
|
+
const output = fs2.createWriteStream(this.outputPath);
|
|
258
|
+
const archive = new ZipArchive({
|
|
259
|
+
zlib: { level: 0 }
|
|
260
|
+
});
|
|
261
|
+
archive.on("entry", (entryData) => {
|
|
262
|
+
const entryName = entryData.name.replace(/\\/g, "/");
|
|
263
|
+
const start = entryData._offsets?.contents ?? 0;
|
|
264
|
+
const end = entryData._offsets?.contents + (entryData.csize ?? 0) - 1;
|
|
265
|
+
entries.push({
|
|
266
|
+
name: entryName,
|
|
267
|
+
start,
|
|
268
|
+
end
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
output.on("close", () => {
|
|
272
|
+
const zipSize = archive.pointer();
|
|
273
|
+
resolve2({
|
|
274
|
+
zipPath: this.outputPath,
|
|
275
|
+
size: zipSize,
|
|
276
|
+
entryCount: entries.length,
|
|
277
|
+
entries
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
archive.on("error", (err) => {
|
|
281
|
+
reject(err);
|
|
282
|
+
});
|
|
283
|
+
output.on("error", (err) => {
|
|
284
|
+
reject(err);
|
|
285
|
+
});
|
|
286
|
+
archive.pipe(output);
|
|
287
|
+
if (!fs2.existsSync(reportPath)) {
|
|
288
|
+
reject(new Error(`Report file not found: ${reportPath}`));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const transformedReportPath = this.transformJsonReport(reportPath);
|
|
292
|
+
archive.file(transformedReportPath, { name: "output/playwright-test-report.json" });
|
|
293
|
+
for (const asset of assets) {
|
|
294
|
+
if (!fs2.existsSync(asset.sourcePath)) {
|
|
295
|
+
console.warn(`[Checkly Reporter] Skipping missing asset: ${asset.sourcePath}`);
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
archive.file(asset.sourcePath, { name: asset.archivePath });
|
|
299
|
+
}
|
|
300
|
+
archive.finalize();
|
|
301
|
+
} catch (error) {
|
|
302
|
+
reject(error);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Transforms the JSON report to use relative paths for attachments
|
|
308
|
+
* This ensures the UI can map attachment paths to ZIP entries
|
|
309
|
+
* @param reportPath - Path to the original JSON report
|
|
310
|
+
* @returns Path to the transformed JSON report (in temp directory)
|
|
311
|
+
*/
|
|
312
|
+
transformJsonReport(reportPath) {
|
|
313
|
+
const reportContent = fs2.readFileSync(reportPath, "utf-8");
|
|
314
|
+
const report = JSON.parse(reportContent);
|
|
315
|
+
this.transformAttachmentPaths(report);
|
|
316
|
+
const tempReportPath = path2.join(os.tmpdir(), `playwright-test-report-${Date.now()}.json`);
|
|
317
|
+
fs2.writeFileSync(tempReportPath, JSON.stringify(report, null, 2));
|
|
318
|
+
return tempReportPath;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Recursively transforms attachment paths in the report structure
|
|
322
|
+
* Converts absolute paths to relative paths matching ZIP structure
|
|
323
|
+
* @param obj - Object to transform (mutated in place)
|
|
324
|
+
*/
|
|
325
|
+
transformAttachmentPaths(obj) {
|
|
326
|
+
if (typeof obj !== "object" || obj === null) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (Array.isArray(obj)) {
|
|
330
|
+
obj.forEach((item) => this.transformAttachmentPaths(item));
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (obj.attachments && Array.isArray(obj.attachments)) {
|
|
334
|
+
obj.attachments.forEach((attachment) => {
|
|
335
|
+
if (attachment.path && typeof attachment.path === "string") {
|
|
336
|
+
attachment.path = this.normalizeAttachmentPath(attachment.path);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
Object.values(obj).forEach((value) => this.transformAttachmentPaths(value));
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Normalizes attachment paths by extracting the relevant snapshot directory portion.
|
|
344
|
+
* Supports Playwright's default and common custom snapshot directory patterns,
|
|
345
|
+
* as well as blob merge resource paths.
|
|
346
|
+
*
|
|
347
|
+
* Priority order (first match wins):
|
|
348
|
+
* 1. test-results/ (highest priority, existing behavior)
|
|
349
|
+
* 2. blob-reports/resources/ (blob merge extraction paths)
|
|
350
|
+
* 3. snapshots directories (Playwright default pattern)
|
|
351
|
+
* 4. __screenshots__/ (common custom pattern)
|
|
352
|
+
* 5. __snapshots__/ (common custom pattern)
|
|
353
|
+
* 6. screenshots/ (simple custom pattern)
|
|
354
|
+
* 7. snapshots/ (simple custom pattern)
|
|
355
|
+
*
|
|
356
|
+
* @param attachmentPath - Absolute or relative path to attachment
|
|
357
|
+
* @returns Normalized path starting from the matched directory, or original path if no match
|
|
358
|
+
*/
|
|
359
|
+
normalizeAttachmentPath(attachmentPath) {
|
|
360
|
+
const normalizedPath = attachmentPath.replace(/\\/g, "/");
|
|
361
|
+
const testResultsIndex = normalizedPath.indexOf("test-results/");
|
|
362
|
+
if (testResultsIndex !== -1) {
|
|
363
|
+
return normalizedPath.substring(testResultsIndex);
|
|
364
|
+
}
|
|
365
|
+
const blobResourcesMatch = normalizedPath.match(/.*?([\w-]*blob-report[\w-]*\/resources\/.+)$/);
|
|
366
|
+
if (blobResourcesMatch) {
|
|
367
|
+
const filename = normalizedPath.split("/").pop() || "";
|
|
368
|
+
return `test-results/resources/${filename}`;
|
|
369
|
+
}
|
|
370
|
+
const resourcesIndex = normalizedPath.indexOf("/resources/");
|
|
371
|
+
if (resourcesIndex !== -1) {
|
|
372
|
+
const filename = normalizedPath.split("/").pop() || "";
|
|
373
|
+
return `test-results/resources/${filename}`;
|
|
374
|
+
}
|
|
375
|
+
const snapshotsIndex = normalizedPath.indexOf("-snapshots/");
|
|
376
|
+
if (snapshotsIndex !== -1) {
|
|
377
|
+
const pathBeforeSnapshots = normalizedPath.substring(0, snapshotsIndex);
|
|
378
|
+
const lastSlashIndex = pathBeforeSnapshots.lastIndexOf("/");
|
|
379
|
+
const startIndex = lastSlashIndex !== -1 ? lastSlashIndex + 1 : 0;
|
|
380
|
+
return normalizedPath.substring(startIndex);
|
|
381
|
+
}
|
|
382
|
+
const patterns = ["__screenshots__/", "__snapshots__/", "screenshots/", "snapshots/"];
|
|
383
|
+
for (const pattern of patterns) {
|
|
384
|
+
const matchIndex = normalizedPath.indexOf(pattern);
|
|
385
|
+
if (matchIndex !== -1) {
|
|
386
|
+
return normalizedPath.substring(matchIndex);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
console.warn(`[Checkly Reporter] Could not normalize attachment path: ${attachmentPath}`);
|
|
390
|
+
return attachmentPath;
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
// src/reporter.ts
|
|
395
|
+
import * as fs3 from "fs";
|
|
396
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
397
|
+
import * as path3 from "path";
|
|
398
|
+
import { dirname, join as join3 } from "path";
|
|
399
|
+
import { fileURLToPath } from "url";
|
|
400
|
+
|
|
401
|
+
// ../clients/src/checkly-client.ts
|
|
402
|
+
import axios from "axios";
|
|
403
|
+
|
|
404
|
+
// ../clients/src/errors.ts
|
|
405
|
+
var ApiError = class extends Error {
|
|
406
|
+
data;
|
|
407
|
+
constructor(data, options) {
|
|
408
|
+
super(data.message, options);
|
|
409
|
+
this.name = this.constructor.name;
|
|
410
|
+
this.data = data;
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
var ValidationError = class extends ApiError {
|
|
414
|
+
constructor(data, options) {
|
|
415
|
+
super(data, options);
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
var UnauthorizedError = class extends ApiError {
|
|
419
|
+
constructor(data, options) {
|
|
420
|
+
super(data, options);
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
var ForbiddenError = class extends ApiError {
|
|
424
|
+
constructor(data, options) {
|
|
425
|
+
super(data, options);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
var NotFoundError = class extends ApiError {
|
|
429
|
+
constructor(data, options) {
|
|
430
|
+
super(data, options);
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
var RequestTimeoutError = class extends ApiError {
|
|
434
|
+
constructor(data, options) {
|
|
435
|
+
super(data, options);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
var ConflictError = class extends ApiError {
|
|
439
|
+
constructor(data, options) {
|
|
440
|
+
super(data, options);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
var ServerError = class extends ApiError {
|
|
444
|
+
constructor(data, options) {
|
|
445
|
+
super(data, options);
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
var MiscellaneousError = class extends ApiError {
|
|
449
|
+
constructor(data, options) {
|
|
450
|
+
super(data, options);
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
var MissingResponseError = class extends Error {
|
|
454
|
+
constructor(message, options) {
|
|
455
|
+
super(message, options);
|
|
456
|
+
this.name = "MissingResponseError";
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
function parseErrorData(data, options) {
|
|
460
|
+
if (!data) {
|
|
461
|
+
return void 0;
|
|
462
|
+
}
|
|
463
|
+
if (typeof data === "object" && data.statusCode && data.error && data.message) {
|
|
464
|
+
return {
|
|
465
|
+
statusCode: data.statusCode,
|
|
466
|
+
error: data.error,
|
|
467
|
+
message: data.message,
|
|
468
|
+
errorCode: data.errorCode
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
if (typeof data === "object" && data.error && !data.message) {
|
|
472
|
+
return {
|
|
473
|
+
statusCode: options.statusCode,
|
|
474
|
+
error: data.error,
|
|
475
|
+
message: data.error
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
if (typeof data === "object" && data.error && data.message) {
|
|
479
|
+
return {
|
|
480
|
+
statusCode: options.statusCode,
|
|
481
|
+
error: data.error,
|
|
482
|
+
message: data.message,
|
|
483
|
+
errorCode: data.errorCode
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
if (typeof data === "object" && data.message) {
|
|
487
|
+
return {
|
|
488
|
+
statusCode: options.statusCode,
|
|
489
|
+
error: data.message,
|
|
490
|
+
message: data.message,
|
|
491
|
+
errorCode: data.errorCode
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
if (typeof data === "string") {
|
|
495
|
+
return {
|
|
496
|
+
statusCode: options.statusCode,
|
|
497
|
+
error: data,
|
|
498
|
+
message: data
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
return void 0;
|
|
502
|
+
}
|
|
503
|
+
function handleErrorResponse(err) {
|
|
504
|
+
if (!err.response) {
|
|
505
|
+
throw new MissingResponseError(err.message || "Network error");
|
|
506
|
+
}
|
|
507
|
+
const { status, data } = err.response;
|
|
508
|
+
const errorData = parseErrorData(data, { statusCode: status });
|
|
509
|
+
if (!errorData) {
|
|
510
|
+
throw new MiscellaneousError({
|
|
511
|
+
statusCode: status,
|
|
512
|
+
error: "Unknown error",
|
|
513
|
+
message: err.message || "An error occurred"
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
switch (status) {
|
|
517
|
+
case 400:
|
|
518
|
+
throw new ValidationError(errorData);
|
|
519
|
+
case 401:
|
|
520
|
+
throw new UnauthorizedError(errorData);
|
|
521
|
+
case 403:
|
|
522
|
+
throw new ForbiddenError(errorData);
|
|
523
|
+
case 404:
|
|
524
|
+
throw new NotFoundError(errorData);
|
|
525
|
+
case 408:
|
|
526
|
+
throw new RequestTimeoutError(errorData);
|
|
527
|
+
case 409:
|
|
528
|
+
throw new ConflictError(errorData);
|
|
529
|
+
default:
|
|
530
|
+
if (status >= 500) {
|
|
531
|
+
throw new ServerError(errorData);
|
|
532
|
+
}
|
|
533
|
+
throw new MiscellaneousError(errorData);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// ../clients/src/checkly-client.ts
|
|
538
|
+
function getVersion() {
|
|
539
|
+
return "0.1.0";
|
|
540
|
+
}
|
|
541
|
+
function createRequestInterceptor(apiKey, accountId) {
|
|
542
|
+
return (config) => {
|
|
543
|
+
if (config.headers) {
|
|
544
|
+
config.headers.Authorization = `Bearer ${apiKey}`;
|
|
545
|
+
config.headers["x-checkly-account"] = accountId;
|
|
546
|
+
config.headers["User-Agent"] = `@checkly/playwright-reporter/${getVersion()}`;
|
|
547
|
+
}
|
|
548
|
+
return config;
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function createResponseErrorInterceptor() {
|
|
552
|
+
return (error) => {
|
|
553
|
+
handleErrorResponse(error);
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
var ChecklyClient = class {
|
|
557
|
+
apiKey;
|
|
558
|
+
baseUrl;
|
|
559
|
+
accountId;
|
|
560
|
+
api;
|
|
561
|
+
constructor(options) {
|
|
562
|
+
this.accountId = options.accountId;
|
|
563
|
+
this.apiKey = options.apiKey;
|
|
564
|
+
this.baseUrl = options.baseUrl;
|
|
565
|
+
this.api = axios.create({
|
|
566
|
+
baseURL: this.baseUrl,
|
|
567
|
+
timeout: 12e4,
|
|
568
|
+
// 120 second timeout for large uploads
|
|
569
|
+
maxContentLength: Number.POSITIVE_INFINITY,
|
|
570
|
+
// Allow large payloads
|
|
571
|
+
maxBodyLength: Number.POSITIVE_INFINITY
|
|
572
|
+
// Allow large request bodies
|
|
573
|
+
});
|
|
574
|
+
this.api.interceptors.request.use(createRequestInterceptor(this.apiKey, this.accountId));
|
|
575
|
+
this.api.interceptors.response.use((response) => response, createResponseErrorInterceptor());
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Gets the underlying axios instance
|
|
579
|
+
* Useful for creating resource-specific clients (e.g., TestResults)
|
|
580
|
+
*/
|
|
581
|
+
getAxiosInstance() {
|
|
582
|
+
return this.api;
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
// ../clients/src/test-results.ts
|
|
587
|
+
import FormData from "form-data";
|
|
588
|
+
var TestResults = class {
|
|
589
|
+
constructor(api) {
|
|
590
|
+
this.api = api;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Creates a new test session in Checkly
|
|
594
|
+
*
|
|
595
|
+
* @param request Test session creation request
|
|
596
|
+
* @returns Test session response with session ID and test result IDs
|
|
597
|
+
* @throws {ValidationError} If request data is invalid
|
|
598
|
+
* @throws {UnauthorizedError} If authentication fails
|
|
599
|
+
* @throws {ServerError} If server error occurs
|
|
600
|
+
*/
|
|
601
|
+
async createTestSession(request) {
|
|
602
|
+
const response = await this.api.post("/next/test-sessions/create", request);
|
|
603
|
+
return response.data;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Step 1: Upload test result assets to S3
|
|
607
|
+
* Streams a ZIP file containing test assets (traces, videos, screenshots)
|
|
608
|
+
*
|
|
609
|
+
* @param testSessionId ID of the test session
|
|
610
|
+
* @param testResultId ID of the test result
|
|
611
|
+
* @param assets Buffer or ReadableStream of the ZIP file
|
|
612
|
+
* @returns Upload response with assetId, region, key, and url
|
|
613
|
+
* @throws {ValidationError} If assets are invalid
|
|
614
|
+
* @throws {UnauthorizedError} If authentication fails
|
|
615
|
+
* @throws {NotFoundError} If test session or result not found
|
|
616
|
+
* @throws {PayloadTooLargeError} If assets exceed 500MB
|
|
617
|
+
* @throws {ServerError} If S3 upload fails
|
|
618
|
+
*/
|
|
619
|
+
async uploadTestResultAsset(testSessionId, testResultId, assets) {
|
|
620
|
+
const form = new FormData();
|
|
621
|
+
form.append("assets", assets, {
|
|
622
|
+
filename: "assets.zip",
|
|
623
|
+
contentType: "application/zip"
|
|
624
|
+
});
|
|
625
|
+
const response = await this.api.post(
|
|
626
|
+
`/next/test-sessions/${testSessionId}/results/${testResultId}/assets`,
|
|
627
|
+
form,
|
|
628
|
+
{
|
|
629
|
+
headers: {
|
|
630
|
+
...form.getHeaders()
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
);
|
|
634
|
+
return response.data;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Step 2: Update test result with status and optional asset reference
|
|
638
|
+
* Uses JSON payload for clean, easy-to-validate updates
|
|
639
|
+
*
|
|
640
|
+
* @param testSessionId ID of the test session
|
|
641
|
+
* @param testResultId ID of the test result to update
|
|
642
|
+
* @param request Test result update request (JSON)
|
|
643
|
+
* @returns Test result update response
|
|
644
|
+
* @throws {ValidationError} If request data is invalid
|
|
645
|
+
* @throws {UnauthorizedError} If authentication fails
|
|
646
|
+
* @throws {NotFoundError} If test session or result not found
|
|
647
|
+
* @throws {ServerError} If server error occurs
|
|
648
|
+
*/
|
|
649
|
+
async updateTestResult(testSessionId, testResultId, request) {
|
|
650
|
+
const response = await this.api.post(
|
|
651
|
+
`/next/test-sessions/${testSessionId}/results/${testResultId}`,
|
|
652
|
+
request
|
|
653
|
+
);
|
|
654
|
+
return response.data;
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
// src/reporter.ts
|
|
659
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
660
|
+
var __dirname = dirname(__filename);
|
|
661
|
+
var packageJson = JSON.parse(readFileSync3(join3(__dirname, "..", "package.json"), "utf-8"));
|
|
662
|
+
var pkgVersion = packageJson.version;
|
|
663
|
+
var pluralRules = new Intl.PluralRules("en-US");
|
|
664
|
+
var projectForms = {
|
|
665
|
+
zero: "Project",
|
|
666
|
+
one: "Project",
|
|
667
|
+
two: "Projects",
|
|
668
|
+
few: "Projects",
|
|
669
|
+
many: "Projects",
|
|
670
|
+
other: "Projects"
|
|
671
|
+
};
|
|
672
|
+
function getApiUrl(environment) {
|
|
673
|
+
const environments = {
|
|
674
|
+
local: "http://127.0.0.1:3000",
|
|
675
|
+
development: "https://api-dev.checklyhq.com",
|
|
676
|
+
staging: "https://api-test.checklyhq.com",
|
|
677
|
+
production: "https://api.checklyhq.com"
|
|
678
|
+
};
|
|
679
|
+
return environments[environment];
|
|
680
|
+
}
|
|
681
|
+
function getEnvironment(options) {
|
|
682
|
+
const envFromOptions = options?.environment;
|
|
683
|
+
const envFromEnvVar = process.env.CHECKLY_ENV;
|
|
684
|
+
const env = envFromOptions || envFromEnvVar || "production";
|
|
685
|
+
const validEnvironments = ["local", "development", "staging", "production"];
|
|
686
|
+
if (!validEnvironments.includes(env)) {
|
|
687
|
+
console.warn(`[Checkly Reporter] Invalid environment "${env}", using "production"`);
|
|
688
|
+
return "production";
|
|
689
|
+
}
|
|
690
|
+
return env;
|
|
691
|
+
}
|
|
692
|
+
function convertStepToJSON(step) {
|
|
693
|
+
return {
|
|
694
|
+
title: step.title,
|
|
695
|
+
duration: step.duration,
|
|
696
|
+
error: step.error,
|
|
697
|
+
steps: step.steps.length > 0 ? step.steps.map(convertStepToJSON) : void 0
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
function getDirectoryName() {
|
|
701
|
+
const cwd = process.cwd();
|
|
702
|
+
let dirName = path3.basename(cwd);
|
|
703
|
+
if (!dirName || dirName === "/" || dirName === ".") {
|
|
704
|
+
dirName = "playwright-tests";
|
|
705
|
+
}
|
|
706
|
+
dirName = dirName.replace(/[<>:"|?*]/g, "-");
|
|
707
|
+
if (dirName.length > 255) {
|
|
708
|
+
dirName = dirName.substring(0, 255);
|
|
709
|
+
}
|
|
710
|
+
return dirName;
|
|
711
|
+
}
|
|
712
|
+
var ChecklyReporter = class {
|
|
713
|
+
options;
|
|
714
|
+
assetCollector;
|
|
715
|
+
zipper;
|
|
716
|
+
testResults;
|
|
717
|
+
testSession;
|
|
718
|
+
startTime;
|
|
719
|
+
testCounts = {
|
|
720
|
+
passed: 0,
|
|
721
|
+
failed: 0,
|
|
722
|
+
flaky: 0
|
|
723
|
+
};
|
|
724
|
+
// Store steps per test result, keyed by "testId:retry"
|
|
725
|
+
stepsMap = /* @__PURE__ */ new Map();
|
|
726
|
+
// Store warnings per test result, keyed by "testId:retry"
|
|
727
|
+
warningsMap = /* @__PURE__ */ new Map();
|
|
728
|
+
constructor(options = {}) {
|
|
729
|
+
const environment = getEnvironment(options);
|
|
730
|
+
const baseUrl = getApiUrl(environment);
|
|
731
|
+
const apiKey = process.env.CHECKLY_API_KEY || options.apiKey;
|
|
732
|
+
const accountId = process.env.CHECKLY_ACCOUNT_ID || options.accountId;
|
|
733
|
+
this.options = {
|
|
734
|
+
accountId,
|
|
735
|
+
apiKey,
|
|
736
|
+
outputPath: options.outputPath ?? "checkly-report.zip",
|
|
737
|
+
jsonReportPath: options.jsonReportPath ?? "test-results/playwright-test-report.json",
|
|
738
|
+
testResultsDir: options.testResultsDir ?? "test-results",
|
|
739
|
+
dryRun: options.dryRun ?? false,
|
|
740
|
+
sessionName: options.sessionName
|
|
741
|
+
};
|
|
742
|
+
this.assetCollector = new AssetCollector(this.options.testResultsDir);
|
|
743
|
+
this.zipper = new Zipper({
|
|
744
|
+
outputPath: this.options.outputPath
|
|
745
|
+
});
|
|
746
|
+
if (!this.options.dryRun && this.options.apiKey && this.options.accountId) {
|
|
747
|
+
const client = new ChecklyClient({
|
|
748
|
+
apiKey: this.options.apiKey,
|
|
749
|
+
accountId: this.options.accountId,
|
|
750
|
+
baseUrl
|
|
751
|
+
});
|
|
752
|
+
this.testResults = new TestResults(client.getAxiosInstance());
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Resolves the session name from options
|
|
757
|
+
* Supports string, callback function, or falls back to default
|
|
758
|
+
*/
|
|
759
|
+
resolveSessionName(context) {
|
|
760
|
+
const { sessionName } = this.options;
|
|
761
|
+
if (typeof sessionName === "function") {
|
|
762
|
+
return sessionName(context);
|
|
763
|
+
}
|
|
764
|
+
if (typeof sessionName === "string") {
|
|
765
|
+
return sessionName;
|
|
766
|
+
}
|
|
767
|
+
return `Playwright Test Session: ${context.directoryName}`;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Checks if test result has a trace attachment and adds context-aware warning if missing
|
|
771
|
+
* The warning type depends on the trace configuration and test result state
|
|
772
|
+
*/
|
|
773
|
+
checkTraceAttachment(test, result) {
|
|
774
|
+
const warningsKey = `${test.id}:${result.retry}`;
|
|
775
|
+
const hasTrace = result.attachments?.some(
|
|
776
|
+
(attachment) => attachment.name === "trace" || attachment.contentType === "application/zip"
|
|
777
|
+
);
|
|
778
|
+
if (hasTrace) {
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const traceConfig = test.parent?.project()?.use?.trace;
|
|
782
|
+
const traceMode = typeof traceConfig === "object" ? traceConfig.mode : traceConfig;
|
|
783
|
+
const isRetry = result.retry > 0;
|
|
784
|
+
const testPassed = result.status === "passed";
|
|
785
|
+
let warningType;
|
|
786
|
+
let message;
|
|
787
|
+
switch (traceMode) {
|
|
788
|
+
case void 0:
|
|
789
|
+
return;
|
|
790
|
+
case "off":
|
|
791
|
+
warningType = "trace-off";
|
|
792
|
+
message = 'Traces are disabled. Set trace: "on" in playwright.config.ts to capture traces.';
|
|
793
|
+
break;
|
|
794
|
+
case "retain-on-failure":
|
|
795
|
+
if (testPassed) {
|
|
796
|
+
warningType = "trace-retained-on-failure";
|
|
797
|
+
message = 'No trace retained because test passed. Trace mode is "retain-on-failure" which discards traces for passing tests.';
|
|
798
|
+
} else {
|
|
799
|
+
warningType = "trace-missing";
|
|
800
|
+
message = 'Trace should exist but was not found. The test failed with trace: "retain-on-failure".';
|
|
801
|
+
}
|
|
802
|
+
break;
|
|
803
|
+
case "on-first-retry":
|
|
804
|
+
if (!isRetry) {
|
|
805
|
+
warningType = "trace-first-retry-only";
|
|
806
|
+
message = 'No trace for initial attempt. Trace mode is "on-first-retry" which only records traces on the first retry.';
|
|
807
|
+
} else if (result.retry === 1) {
|
|
808
|
+
warningType = "trace-missing";
|
|
809
|
+
message = 'Trace should exist but was not found. This is the first retry with trace: "on-first-retry".';
|
|
810
|
+
} else {
|
|
811
|
+
warningType = "trace-first-retry-only";
|
|
812
|
+
message = `No trace for retry #${result.retry}. Trace mode is "on-first-retry" which only records the first retry.`;
|
|
813
|
+
}
|
|
814
|
+
break;
|
|
815
|
+
case "on-all-retries":
|
|
816
|
+
if (!isRetry) {
|
|
817
|
+
warningType = "trace-retries-only";
|
|
818
|
+
message = 'No trace for initial attempt. Trace mode is "on-all-retries" which only records traces on retries.';
|
|
819
|
+
} else {
|
|
820
|
+
warningType = "trace-missing";
|
|
821
|
+
message = `Trace should exist but was not found. This is retry #${result.retry} with trace: "on-all-retries".`;
|
|
822
|
+
}
|
|
823
|
+
break;
|
|
824
|
+
case "retain-on-first-failure":
|
|
825
|
+
if (testPassed) {
|
|
826
|
+
warningType = "trace-retained-on-first-failure";
|
|
827
|
+
message = 'No trace retained because test passed. Trace mode is "retain-on-first-failure" which discards traces for passing tests.';
|
|
828
|
+
} else if (isRetry) {
|
|
829
|
+
warningType = "trace-retained-on-first-failure";
|
|
830
|
+
message = 'No trace for retries. Trace mode is "retain-on-first-failure" which only records the first run.';
|
|
831
|
+
} else {
|
|
832
|
+
warningType = "trace-missing";
|
|
833
|
+
message = 'Trace should exist but was not found. The test failed on first run with trace: "retain-on-first-failure".';
|
|
834
|
+
}
|
|
835
|
+
break;
|
|
836
|
+
case "on":
|
|
837
|
+
warningType = "trace-missing";
|
|
838
|
+
message = 'Trace should exist but was not found. Trace mode is "on" which should always record traces.';
|
|
839
|
+
break;
|
|
840
|
+
default:
|
|
841
|
+
warningType = "trace-missing";
|
|
842
|
+
message = `No trace found. Trace mode "${traceMode}" may not be generating traces for this result.`;
|
|
843
|
+
}
|
|
844
|
+
const warnings = this.warningsMap.get(warningsKey) || [];
|
|
845
|
+
warnings.push({ type: warningType, message });
|
|
846
|
+
this.warningsMap.set(warningsKey, warnings);
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Called once before running tests
|
|
850
|
+
* Creates test session in Checkly if credentials provided
|
|
851
|
+
*/
|
|
852
|
+
onBegin(config, suite) {
|
|
853
|
+
this.startTime = /* @__PURE__ */ new Date();
|
|
854
|
+
if (!this.testResults) {
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
try {
|
|
858
|
+
const directoryName = getDirectoryName();
|
|
859
|
+
const sessionName = this.resolveSessionName({ directoryName, config, suite });
|
|
860
|
+
const testResults = [{ name: directoryName }];
|
|
861
|
+
const repoUrl = process.env.GITHUB_REPOSITORY ? `https://github.com/${process.env.GITHUB_REPOSITORY}` : void 0;
|
|
862
|
+
const repoInfo = repoUrl ? {
|
|
863
|
+
repoUrl,
|
|
864
|
+
commitId: process.env.GITHUB_SHA,
|
|
865
|
+
branchName: process.env.GITHUB_REF_NAME,
|
|
866
|
+
commitOwner: process.env.GITHUB_ACTOR,
|
|
867
|
+
commitMessage: process.env.GITHUB_EVENT_NAME
|
|
868
|
+
} : void 0;
|
|
869
|
+
this.testResults.createTestSession({
|
|
870
|
+
name: sessionName,
|
|
871
|
+
environment: process.env.NODE_ENV || "test",
|
|
872
|
+
repoInfo,
|
|
873
|
+
startedAt: this.startTime.getTime(),
|
|
874
|
+
// Required timestamp in milliseconds
|
|
875
|
+
testResults,
|
|
876
|
+
provider: "PW_REPORTER"
|
|
877
|
+
}).then((response) => {
|
|
878
|
+
this.testSession = response;
|
|
879
|
+
}).catch((error) => {
|
|
880
|
+
console.error("[Checkly Reporter] Failed to create test session:", error.message);
|
|
881
|
+
});
|
|
882
|
+
} catch (error) {
|
|
883
|
+
console.error("[Checkly Reporter] Error in onBegin:", error);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Called for each test when it completes
|
|
888
|
+
* Captures steps and warnings, tracks test results for final status calculation
|
|
889
|
+
*/
|
|
890
|
+
onTestEnd(test, result) {
|
|
891
|
+
try {
|
|
892
|
+
this.checkTraceAttachment(test, result);
|
|
893
|
+
const stepsKey = `${test.id}:${result.retry}`;
|
|
894
|
+
if (result.steps && result.steps.length > 0) {
|
|
895
|
+
this.stepsMap.set(stepsKey, result.steps.map(convertStepToJSON));
|
|
896
|
+
}
|
|
897
|
+
const outcome = test.outcome();
|
|
898
|
+
const testIsComplete = result.retry === test.retries || outcome !== "unexpected";
|
|
899
|
+
if (!testIsComplete) {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
const isFlaky = outcome === "flaky";
|
|
903
|
+
if (isFlaky) {
|
|
904
|
+
this.testCounts.flaky++;
|
|
905
|
+
this.testCounts.passed++;
|
|
906
|
+
} else {
|
|
907
|
+
if (result.status === "passed") {
|
|
908
|
+
this.testCounts.passed++;
|
|
909
|
+
} else if (result.status === "failed" || result.status === "timedOut") {
|
|
910
|
+
this.testCounts.failed++;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
} catch (error) {
|
|
914
|
+
console.error("[Checkly Reporter] Error in onTestEnd:", error);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Called after all tests have completed
|
|
919
|
+
* This is where we create the ZIP archive and upload results
|
|
920
|
+
*/
|
|
921
|
+
async onEnd() {
|
|
922
|
+
try {
|
|
923
|
+
const jsonReportPath = this.options.jsonReportPath;
|
|
924
|
+
if (!fs3.existsSync(jsonReportPath)) {
|
|
925
|
+
console.error(`[Checkly Reporter] ERROR: JSON report not found at: ${jsonReportPath}`);
|
|
926
|
+
console.error("[Checkly Reporter] Make sure to configure the json reporter before the checkly reporter:");
|
|
927
|
+
console.error(
|
|
928
|
+
" reporter: [\n ['json', { outputFile: 'test-results/playwright-test-report.json' }],\n ['@checkly/playwright-reporter']\n ]"
|
|
929
|
+
);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
const reportContent = fs3.readFileSync(jsonReportPath, "utf-8");
|
|
933
|
+
const report = JSON.parse(reportContent);
|
|
934
|
+
this.injectDataIntoReport(report);
|
|
935
|
+
fs3.writeFileSync(jsonReportPath, JSON.stringify(report, null, 2), "utf-8");
|
|
936
|
+
const assets = await this.assetCollector.collectAssets(report);
|
|
937
|
+
const result = await this.zipper.createZip(jsonReportPath, assets);
|
|
938
|
+
if (this.testResults && this.testSession) {
|
|
939
|
+
await this.uploadResults(report, result.zipPath, result.entries);
|
|
940
|
+
if (!this.options.dryRun) {
|
|
941
|
+
try {
|
|
942
|
+
fs3.unlinkSync(result.zipPath);
|
|
943
|
+
} catch (cleanupError) {
|
|
944
|
+
console.warn(`[Checkly Reporter] Warning: Could not delete ZIP file: ${cleanupError}`);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
if (this.testResults && this.testSession?.link) {
|
|
949
|
+
this.printSummary(report, this.testSession);
|
|
950
|
+
}
|
|
951
|
+
} catch (error) {
|
|
952
|
+
console.error("[Checkly Reporter] ERROR creating report:", error);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
printSummary(report, testSession) {
|
|
956
|
+
const rule = pluralRules.select(report.config.projects.length);
|
|
957
|
+
console.log("\n======================================================\n");
|
|
958
|
+
console.log(`\u{1F99D} Checkly reporter: ${pkgVersion}`);
|
|
959
|
+
console.log(`\u{1F3AD} Playwright: ${report.config.version}`);
|
|
960
|
+
console.log(`\u{1F4D4} ${projectForms[rule]}: ${report.config.projects.map(({ name }) => name).join(",")}`);
|
|
961
|
+
console.log(`\u{1F517} Test session URL: ${testSession.link}`);
|
|
962
|
+
console.log("\n======================================================");
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Injects captured steps and warnings into the JSON report
|
|
966
|
+
* Traverses the report structure and matches by test ID + retry
|
|
967
|
+
*/
|
|
968
|
+
injectDataIntoReport(report) {
|
|
969
|
+
const processSuite = (suite) => {
|
|
970
|
+
for (const spec of suite.specs) {
|
|
971
|
+
for (const test of spec.tests) {
|
|
972
|
+
for (const result of test.results) {
|
|
973
|
+
const key = `${spec.id}:${result.retry}`;
|
|
974
|
+
const steps = this.stepsMap.get(key);
|
|
975
|
+
if (steps) {
|
|
976
|
+
result.steps = steps;
|
|
977
|
+
}
|
|
978
|
+
const warnings = this.warningsMap.get(key);
|
|
979
|
+
if (warnings && warnings.length > 0) {
|
|
980
|
+
result._checkly = { warnings };
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
if (suite.suites) {
|
|
986
|
+
for (const nestedSuite of suite.suites) {
|
|
987
|
+
processSuite(nestedSuite);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
for (const suite of report.suites) {
|
|
992
|
+
processSuite(suite);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Uploads test results to Checkly API
|
|
997
|
+
*/
|
|
998
|
+
async uploadResults(report, zipPath, entries) {
|
|
999
|
+
if (!this.testResults || !this.testSession) {
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
try {
|
|
1003
|
+
const { failed: failedCount, flaky: flakyCount } = this.testCounts;
|
|
1004
|
+
const overallStatus = failedCount > 0 ? "FAILED" : "PASSED";
|
|
1005
|
+
const isDegraded = failedCount === 0 && flakyCount > 0;
|
|
1006
|
+
const endTime = /* @__PURE__ */ new Date();
|
|
1007
|
+
const responseTime = this.startTime ? Math.max(0, endTime.getTime() - this.startTime.getTime()) : 0;
|
|
1008
|
+
const zipSizeBytes = (await fs3.promises.stat(zipPath)).size;
|
|
1009
|
+
if (this.testSession.testResults.length > 0) {
|
|
1010
|
+
const firstResult = this.testSession.testResults[0];
|
|
1011
|
+
let assetId;
|
|
1012
|
+
if (zipSizeBytes > 0) {
|
|
1013
|
+
try {
|
|
1014
|
+
const assets = fs3.createReadStream(zipPath);
|
|
1015
|
+
const uploadResponse = await this.testResults.uploadTestResultAsset(
|
|
1016
|
+
this.testSession.testSessionId,
|
|
1017
|
+
firstResult.testResultId,
|
|
1018
|
+
assets
|
|
1019
|
+
);
|
|
1020
|
+
assetId = uploadResponse.assetId;
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1023
|
+
console.error("[Checkly Reporter] Asset upload failed:", errorMessage);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
await this.testResults.updateTestResult(this.testSession.testSessionId, firstResult.testResultId, {
|
|
1027
|
+
status: overallStatus,
|
|
1028
|
+
assetEntries: assetId ? entries : void 0,
|
|
1029
|
+
isDegraded,
|
|
1030
|
+
startedAt: this.startTime?.toISOString(),
|
|
1031
|
+
stoppedAt: endTime.toISOString(),
|
|
1032
|
+
responseTime,
|
|
1033
|
+
metadata: {
|
|
1034
|
+
usageData: {
|
|
1035
|
+
s3PostTotalBytes: zipSizeBytes
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1042
|
+
console.error("[Checkly Reporter] Failed to upload results:", errorMessage);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Called when a global error occurs
|
|
1047
|
+
*/
|
|
1048
|
+
onError(error) {
|
|
1049
|
+
console.error("[Checkly Reporter] Global error:", error);
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
export {
|
|
1053
|
+
AssetCollector,
|
|
1054
|
+
ChecklyReporter,
|
|
1055
|
+
Zipper,
|
|
1056
|
+
ChecklyReporter as default
|
|
1057
|
+
};
|