diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..9142fa7 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,6 @@ +node_modules/ +playwright-report/ +test-results/ +blob-report/ +playwright/.cache/ +localFiles/ diff --git a/package-lock.json b/package-lock.json index 1062907..0f0161c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "xlsx": "~0.18.5" }, "devDependencies": { - "@playwright/test": "^1.57.0", + "@playwright/test": "~1.57.0", "@types/lodash": "~4.17.0", "@types/node": "~20.11.30", "@typescript-eslint/eslint-plugin": "~5.62.0", @@ -29,6 +29,7 @@ "eslint-plugin-playwright": "~1.0.1", "eslint-plugin-promise": "~6.0.0", "eslint-plugin-simple-import-sort": "~10.0.0", + "pdfjs-dist": "~3.11.174", "prettier": "~3.2.5", "typescript": "~5.1.6" } @@ -133,6 +134,28 @@ "deprecated": "Use @eslint/object-schema instead", "dev": true }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -183,6 +206,24 @@ "node": ">=18" } }, + "node_modules/@playwright/test/node_modules/playwright": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "dev": true, + "dependencies": { + "playwright-core": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -408,8 +449,17 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "deprecated": "Potential CWE-502 - Update to 1.3.1 or higher", "dev": true }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -439,6 +489,20 @@ "node": ">=0.8" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -479,6 +543,30 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -688,6 +776,23 @@ "node": ">=6" } }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/cfb": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", @@ -716,6 +821,17 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } + }, "node_modules/codepage": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", @@ -742,12 +858,31 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -846,6 +981,20 @@ } } }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -886,6 +1035,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -921,6 +1089,14 @@ "url": "https://dotenvx.com" } }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/env-var": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/env-var/-/env-var-7.4.1.tgz", @@ -1699,6 +1875,34 @@ "node": ">=0.8" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1755,6 +1959,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -1795,7 +2022,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -1961,6 +2188,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -1973,6 +2208,21 @@ "node": ">= 0.4" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -2145,6 +2395,17 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2404,6 +2665,34 @@ "node": ">=10" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2426,6 +2715,20 @@ "node": ">=8.6" } }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2447,12 +2750,74 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/nan": { + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.27.0.tgz", + "integrity": "sha512-hC+0LidcL3XE4rp1C4H54KujgXKzbfyTngZTwBByQxsOxCEKZT0MPQ4hOKUH2jU1OYstqdDH4onyHPDzcV0XdQ==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -2465,6 +2830,71 @@ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -2660,6 +3090,31 @@ "node": ">=8" } }, + "node_modules/path2d-polyfill": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz", + "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pdfjs-dist": { + "version": "3.11.174", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz", + "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "canvas": "^2.11.2", + "path2d-polyfill": "^2.0.1" + } + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2672,24 +3127,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/playwright": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", - "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", - "dev": true, - "dependencies": { - "playwright-core": "1.57.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, "node_modules/playwright-core": { "version": "1.57.0", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", @@ -2764,6 +3201,22 @@ } ] }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -2887,6 +3340,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, "node_modules/safe-regex-test": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", @@ -2919,6 +3394,14 @@ "node": ">=10" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -2999,6 +3482,49 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -3019,6 +3545,33 @@ "node": ">=0.8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", @@ -3125,6 +3678,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3143,6 +3716,14 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -3316,6 +3897,34 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3366,6 +3975,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/wmf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", diff --git a/package.json b/package.json index 718543f..ea05f3e 100755 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "eslint-plugin-playwright": "~1.0.1", "eslint-plugin-promise": "~6.0.0", "eslint-plugin-simple-import-sort": "~10.0.0", + "pdfjs-dist": "~3.11.174", "prettier": "~3.2.5", "typescript": "~5.1.6" }, diff --git a/src/consts/applicationUrls.ts b/src/consts/applicationUrls.ts index ee9c01b..24ad458 100644 --- a/src/consts/applicationUrls.ts +++ b/src/consts/applicationUrls.ts @@ -71,6 +71,7 @@ const USER_URL = { const PUTAWAY_URL = { base: './putAway', create: () => `${PUTAWAY_URL.base}/create`, + generatePdfPattern: /\/putAway\/generatePdf\//, }; const ORDER_URL = { diff --git a/src/pages/inbound/create/components/AddItemsTable.ts b/src/pages/inbound/create/components/AddItemsTable.ts index 94429bd..e49c8f5 100644 --- a/src/pages/inbound/create/components/AddItemsTable.ts +++ b/src/pages/inbound/create/components/AddItemsTable.ts @@ -72,10 +72,9 @@ class Row extends BasePageModel { if (!_.isNil(rowValues.lotNumber)) { await this.lotField.textbox.fill(rowValues.lotNumber); } - if (!_.isNil(rowValues.recipient?.name)) { - await this.recipientSelect.findAndSelectOption( - rowValues.recipient.name - ); + const recipientName = rowValues.recipient?.name; + if (!_.isNil(recipientName)) { + await this.recipientSelect.findAndSelectOption(recipientName); } if (!_.isNil(rowValues.expirationDate)) { await this.expirationDate.fill(rowValues.expirationDate); diff --git a/src/pages/putaway/putawayDetails/PutawayDetailsPage.ts b/src/pages/putaway/putawayDetails/PutawayDetailsPage.ts index cde87f4..ef281a5 100644 --- a/src/pages/putaway/putawayDetails/PutawayDetailsPage.ts +++ b/src/pages/putaway/putawayDetails/PutawayDetailsPage.ts @@ -1,5 +1,6 @@ import { expect, Page } from '@playwright/test'; +import FileHandler from '@/components/FileHandler'; import BasePageModel from '@/pages/BasePageModel'; import AuditingTable from './components/AuditingTable'; @@ -14,6 +15,7 @@ class PutawayDetailsPage extends BasePageModel { itemStatusTable: ItemStatusTable; itemDetailsTable: ItemDetailsTable; auditingTable: AuditingTable; + fileHandler: FileHandler; constructor(page: Page) { super(page); @@ -22,6 +24,7 @@ class PutawayDetailsPage extends BasePageModel { this.itemStatusTable = new ItemStatusTable(page); this.itemDetailsTable = new ItemDetailsTable(page); this.auditingTable = new AuditingTable(page); + this.fileHandler = new FileHandler(page); } async isLoaded() { diff --git a/src/pages/putaway/putawayDetails/components/ItemStatusTable.ts b/src/pages/putaway/putawayDetails/components/ItemStatusTable.ts index 28d0a18..6426087 100644 --- a/src/pages/putaway/putawayDetails/components/ItemStatusTable.ts +++ b/src/pages/putaway/putawayDetails/components/ItemStatusTable.ts @@ -26,6 +26,10 @@ class ItemStatusTable extends BasePageModel { get orderItemRows() { return this.table.locator('tr.order-item'); } + + orderItemRow(index: number) { + return new Row(this.page, this.orderItemRows.nth(index)); + } } class Row extends BasePageModel { @@ -39,6 +43,26 @@ class Row extends BasePageModel { return this.row.getByTestId('order-item-status-code'); } + get code() { + return this.row.getByTestId('product-code'); + } + + get productName() { + return this.row.getByTestId('product-name'); + } + + get quantity() { + return this.row.getByTestId('quantity'); + } + + get lotNumber() { + return this.row.getByTestId('lot-number'); + } + + get expirationDate() { + return this.row.getByTestId('expiration-date'); + } + get originBin() { return this.row.getByTestId('origin-bin-location'); } diff --git a/src/pages/receiving/components/CheckTable.ts b/src/pages/receiving/components/CheckTable.ts index c0207a7..e18af77 100644 --- a/src/pages/receiving/components/CheckTable.ts +++ b/src/pages/receiving/components/CheckTable.ts @@ -19,6 +19,21 @@ class CheckTable extends BasePageModel { return new Row(this.page, this.rows.nth(index)); } + // rows mixes container rows with item rows (e.g. the first item is at + // row(1), and multi-container shipments leave gaps), so it can't be indexed + // by item position. + get itemRows() { + return this.rows.filter({ + has: this.page + .getByTestId('label-field') + .and(this.page.getByLabel('Code', { exact: true })), + }); + } + + itemRow(index: number) { + return new Row(this.page, this.itemRows.nth(index)); + } + getColumnHeader(columnName: string) { return this.table .locator('.table-header') @@ -45,6 +60,12 @@ class Row extends BasePageModel { return this.row.getByTestId('label-field').getByText(name); } + get code() { + return this.row + .getByTestId('label-field') + .and(this.row.getByLabel('Code', { exact: true })); + } + get cancelRemainingCheckbox() { return this.row.getByTestId('form-field').getByTestId('checkbox'); } diff --git a/src/tests/putaway/assertPutawayDetailsPage.test.ts b/src/tests/putaway/assertPutawayDetailsPage.test.ts index 0c1c015..ff4d61e 100644 --- a/src/tests/putaway/assertPutawayDetailsPage.test.ts +++ b/src/tests/putaway/assertPutawayDetailsPage.test.ts @@ -1,17 +1,25 @@ +import path from 'node:path'; + import AppConfig from '@/config/AppConfig'; import { ShipmentType } from '@/constants/ShipmentType'; +import { PUTAWAY_URL } from '@/consts/applicationUrls'; import { expect, test } from '@/fixtures/fixtures'; import { Product } from '@/generated/ProductCodes.generated'; import { StockMovementResponse } from '@/types'; +import { deleteFile, writeBufferToFile } from '@/utils/FileIOUtils'; +import { pdfContainsValues } from '@/utils/pdfUtils'; import RefreshCachesUtils from '@/utils/RefreshCaches'; import { deleteReceivedShipment, getShipmentId, getShipmentItemId, } from '@/utils/shipmentUtils'; +import { captureRowValues } from '@/utils/tableUtils'; test.describe('Assert putaway details page', () => { let STOCK_MOVEMENT: StockMovementResponse; + const downloadedFilePaths: string[] = []; + let expectedPdfValues: string[] = []; test.beforeEach( async ({ @@ -79,6 +87,10 @@ test.describe('Assert putaway details page', () => { stockMovementService, STOCK_MOVEMENT, }); + + while (downloadedFilePaths.length) { + deleteFile(downloadedFilePaths.pop() as string); + } } ); @@ -257,15 +269,40 @@ test.describe('Assert putaway details page', () => { const putawayOrderIdentifier = await putawayDetailsPage.orderHeaderTable.orderNumberValue.textContent(); - await test.step('Download putaway pdf', async () => { - const generatePutawayPdfFileName = - 'Putaway ' + `${putawayOrderIdentifier}`.toString().trim() + '.pdf'; + const detailsPagePdfFileName = + 'Putaway ' + `${putawayOrderIdentifier}`.toString().trim() + '.pdf'; + + await test.step('Download putaway pdf from details page', async () => { + await putawayDetailsPage.fileHandler.onDownload(); await putawayDetailsPage.generatePutawayListButton.click(); - const downloadPromise = page.waitForEvent('download'); - const download = await downloadPromise; - await expect(download.suggestedFilename()).toBe( - generatePutawayPdfFileName + const { fileName, fullFilePath } = + await putawayDetailsPage.fileHandler.saveFile(); + expect(fileName).toBe(detailsPagePdfFileName); + downloadedFilePaths.push(fullFilePath); + }); + + await test.step('Assert details page pdf contains table data', async () => { + const rowCount = + await putawayDetailsPage.itemStatusTable.orderItemRows.count(); + expect(rowCount).toBeGreaterThan(0); + + expectedPdfValues = await captureRowValues( + rowCount, + (i) => putawayDetailsPage.itemStatusTable.orderItemRow(i), + (r) => r.code, + (r) => r.productName, + (r) => r.quantity, + (r) => r.lotNumber, + (r) => r.expirationDate, + (r) => r.destinationBin ); + + expect( + await pdfContainsValues( + downloadedFilePaths[downloadedFilePaths.length - 1], + expectedPdfValues + ) + ).toBeTruthy(); }); await test.step('Edit pending putaway and reload without losing data', async () => { @@ -275,6 +312,40 @@ test.describe('Assert putaway details page', () => { await createPutawayPage.startStep.isLoaded(); }); + const createPagePdfFileNameRegex = new RegExp( + `^PutawayReport-${`${putawayOrderIdentifier}`.toString().trim()}\\.pdf$` + ); + + await test.step('Download putaway list pdf from create putaway page', async () => { + const pdfResponsePromise = page.waitForResponse( + (resp) => + PUTAWAY_URL.generatePdfPattern.test(resp.url()) && + resp.status() === 200 + ); + const downloadPromise = page.waitForEvent('download'); + await createPutawayPage.startStep.generatePutawayListButton.click(); + const [pdfResponse, download] = await Promise.all([ + pdfResponsePromise, + downloadPromise, + ]); + + const fileName = download.suggestedFilename(); + expect(fileName).toMatch(createPagePdfFileNameRegex); + + const fullFilePath = path.join(AppConfig.LOCAL_FILES_DIR_PATH, fileName); + writeBufferToFile(fullFilePath, await pdfResponse.body()); + downloadedFilePaths.push(fullFilePath); + }); + + await test.step('Assert create putaway page pdf contains table data', async () => { + expect( + await pdfContainsValues( + downloadedFilePaths[downloadedFilePaths.length - 1], + expectedPdfValues + ) + ).toBeTruthy(); + }); + await test.step('Complete putaway', async () => { await createPutawayPage.startStep.nextButton.click(); await createPutawayPage.completeStep.isLoaded(); @@ -305,17 +376,26 @@ test.describe('Assert putaway details page', () => { }); await test.step('Download putaway pdf from putaway list', async () => { + const generatePutawayPdfFileName = + 'Putaway ' + `${putawayOrderIdentifier}`.toString().trim() + '.pdf'; const row = putawayListPage.table.row(1); await row.actionsButton.click(); await row.generatePdf.click(); - const generatePutawayPdfFileName = - 'Putaway ' + `${putawayOrderIdentifier}`.toString().trim() + '.pdf'; + await putawayDetailsPage.fileHandler.onDownload(); await putawayDetailsPage.generatePutawayListButton.click(); - const downloadPromise = page.waitForEvent('download'); - const download = await downloadPromise; - await expect(download.suggestedFilename()).toBe( - generatePutawayPdfFileName - ); + const { fileName, fullFilePath } = + await putawayDetailsPage.fileHandler.saveFile(); + expect(fileName).toBe(generatePutawayPdfFileName); + downloadedFilePaths.push(fullFilePath); + }); + + await test.step('Assert putaway list pdf contains table data', async () => { + expect( + await pdfContainsValues( + downloadedFilePaths[downloadedFilePaths.length - 1], + expectedPdfValues + ) + ).toBeTruthy(); }); await test.step('Filter by completed status and ordered by on putaway list page', async () => { diff --git a/src/tests/receiving/assertCreationOfGoodsReceiptNote.test.ts b/src/tests/receiving/assertCreationOfGoodsReceiptNote.test.ts index b6b685a..ccf4392 100644 --- a/src/tests/receiving/assertCreationOfGoodsReceiptNote.test.ts +++ b/src/tests/receiving/assertCreationOfGoodsReceiptNote.test.ts @@ -4,6 +4,8 @@ import { expect, test } from '@/fixtures/fixtures'; import { Product } from '@/generated/ProductCodes.generated'; import { StockMovementResponse } from '@/types'; import BinLocationUtils from '@/utils/BinLocationUtils'; +import { pageContainsValues } from '@/utils/pageUtils'; +import { captureRowValues } from '@/utils/tableUtils'; test.describe('Assert Goods Receipt Note is created and opened', () => { let STOCK_MOVEMENT: StockMovementResponse; @@ -66,6 +68,8 @@ test.describe('Assert Goods Receipt Note is created and opened', () => { receivingPage, page, }) => { + let expectedValues: string[] = []; + await test.step('Go to stock movement show page', async () => { await stockMovementShowPage.goToPage(STOCK_MOVEMENT.id); await stockMovementShowPage.isLoaded(); @@ -99,6 +103,13 @@ test.describe('Assert Goods Receipt Note is created and opened', () => { await receivingPage.nextButton.click(); await receivingPage.checkStep.isLoaded(); await receivingPage.checkStep.isLoaded(); + const rowCount = await receivingPage.checkStep.table.itemRows.count(); + expect(rowCount).toBeGreaterThan(0); + expectedValues = await captureRowValues( + rowCount, + (i) => receivingPage.checkStep.table.itemRow(i), + (r) => r.code + ); await receivingPage.checkStep.receiveShipmentButton.click(); await stockMovementShowPage.isLoaded(); }); @@ -116,6 +127,7 @@ test.describe('Assert Goods Receipt Note is created and opened', () => { .downloadButton.click(); const popup = await popupPromise; await expect(popup.locator('.title')).toHaveText('Goods Receipt Note'); + expect(await pageContainsValues(popup, expectedValues)).toBeTruthy(); await popup.close(); }); @@ -152,6 +164,7 @@ test.describe('Assert Goods Receipt Note is created and opened', () => { .downloadButton.click(); const popup = await popupPromise; await expect(popup.locator('.title')).toHaveText('Goods Receipt Note'); + expect(await pageContainsValues(popup, expectedValues)).toBeTruthy(); await popup.close(); }); diff --git a/src/utils/FileIOUtils.ts b/src/utils/FileIOUtils.ts index 4cf04c7..1ce235f 100644 --- a/src/utils/FileIOUtils.ts +++ b/src/utils/FileIOUtils.ts @@ -52,12 +52,12 @@ const writeToFile = (path: string, data: unknown) => { const deleteFile = (path: string) => { if (fs.existsSync(path)) { - fs.unlink(path, (err) => { - if (err) { - console.log(err); - } - }); + fs.unlinkSync(path); } }; -export { deleteFile, readFile, writeToFile }; +const writeBufferToFile = (path: string, data: Buffer): void => { + fs.writeFileSync(path, data); +}; + +export { deleteFile, readFile, writeBufferToFile, writeToFile }; diff --git a/src/utils/pageUtils.ts b/src/utils/pageUtils.ts new file mode 100644 index 0000000..6d08553 --- /dev/null +++ b/src/utils/pageUtils.ts @@ -0,0 +1,10 @@ +import { Page } from '@playwright/test'; + +export const pageContainsValues = async ( + page: Page, + values: string[] +): Promise => { + await page.waitForLoadState(); + const text = await page.locator('body').innerText(); + return values.every((value) => text.includes(value)); +}; diff --git a/src/utils/pdfUtils.ts b/src/utils/pdfUtils.ts new file mode 100644 index 0000000..5a7a251 --- /dev/null +++ b/src/utils/pdfUtils.ts @@ -0,0 +1,33 @@ +import fs from 'node:fs'; + +import { getDocument } from 'pdfjs-dist/legacy/build/pdf.js'; +import type { TextItem } from 'pdfjs-dist/types/src/display/api'; + +export const pdfContainsValues = async ( + filePath: string, + values: string[] +): Promise => { + const pdfText = await extractPdfText(filePath); + return values.every((value) => pdfText.includes(value)); +}; + +export const extractPdfText = async (filePath: string): Promise => { + const data = new Uint8Array(fs.readFileSync(filePath)); + const doc = await getDocument({ data }).promise; + + // Get page numbers available in the PDF + const pageNumbers = Array.from({ length: doc.numPages }, (_, i) => i + 1); + // Extract text from each page + const pageTexts = await Promise.all( + pageNumbers.map(async (pageNum) => { + const page = await doc.getPage(pageNum); + const content = await page.getTextContent(); + return content.items + .map((item) => ('str' in item ? (item as TextItem).str : '')) + .join(' '); + }) + ); + + // Combine text from all pages into a single string + return pageTexts.join('\n'); +}; diff --git a/src/utils/tableUtils.ts b/src/utils/tableUtils.ts new file mode 100644 index 0000000..c6f0c0b --- /dev/null +++ b/src/utils/tableUtils.ts @@ -0,0 +1,18 @@ +import { Locator } from '@playwright/test'; + +export const captureRowValues = async ( + rowCount: number, + getRow: (index: number) => TRow, + ...getters: ((row: TRow) => Locator)[] +): Promise => { + const rows = await Promise.all( + Array.from({ length: rowCount }, async (_, i) => { + const row = getRow(i); + return Promise.all(getters.map((g) => g(row).textContent())); + }) + ); + return rows + .flat() + .map((v) => v?.trim()) + .filter((v): v is string => Boolean(v)); +};