diff --git a/.github/workflows/testing-lab.yml b/.github/workflows/testing-lab.yml new file mode 100644 index 0000000..4b29614 --- /dev/null +++ b/.github/workflows/testing-lab.yml @@ -0,0 +1,38 @@ +name: Testing Lab CI + +on: + push: + paths: + - '5 - Testing/Ejercicios/Testing-Lab/**' + pull_request: + paths: + - '5 - Testing/Ejercicios/Testing-Lab/**' + +jobs: + test: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./5 - Testing/Ejercicios/Testing-Lab + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + cache-dependency-path: './5 - Testing/Ejercicios/Testing-Lab/package-lock.json' + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3 + with: + directory: ./5 - Testing/Ejercicios/Testing-Lab/coverage + flags: unittests \ No newline at end of file diff --git a/5 - Testing/Ejercicios/Testing-Lab/.env.test b/5 - Testing/Ejercicios/Testing-Lab/.env.test new file mode 100644 index 0000000..f46533d --- /dev/null +++ b/5 - Testing/Ejercicios/Testing-Lab/.env.test @@ -0,0 +1 @@ +NODE_ENV=test \ No newline at end of file diff --git a/5 - Testing/Ejercicios/Testing-Lab/.gitignore b/5 - Testing/Ejercicios/Testing-Lab/.gitignore new file mode 100644 index 0000000..58786aa --- /dev/null +++ b/5 - Testing/Ejercicios/Testing-Lab/.gitignore @@ -0,0 +1,7 @@ + +# Playwright +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/5 - Testing/Ejercicios/Testing-Lab/e2e/login.spec.ts b/5 - Testing/Ejercicios/Testing-Lab/e2e/login.spec.ts new file mode 100644 index 0000000..50f69c6 --- /dev/null +++ b/5 - Testing/Ejercicios/Testing-Lab/e2e/login.spec.ts @@ -0,0 +1,57 @@ +import { test, expect } from "@playwright/test"; + +test.describe("Login Page", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/"); + }); + + test("should display login form", async ({ page }) => { + // Wait for form to be visible + const form = await page.getByTestId("login-form"); + await expect(form).toBeVisible(); + + // Check form elements + await expect(page.getByTestId("user-input")).toBeVisible(); + await expect(page.getByTestId("password-input")).toBeVisible(); + await expect(page.getByTestId("login-button")).toBeVisible(); + }); + + test("should show error message with invalid credentials", async ({ + page, + }) => { + // Fill in invalid credentials + await page.getByTestId("user-input").fill("invalid"); + await page.getByTestId("password-input").fill("invalid"); + + // Click login button + await page.getByTestId("login-button").click(); + + // Wait for error message + await expect( + page.getByText("Usuario y/o password no vĂ¡lidos") + ).toBeVisible(); + }); + + test("should login successfully with valid credentials", async ({ page }) => { + // Fill in valid credentials + await page.getByTestId("user-input").fill("admin"); + await page.getByTestId("password-input").fill("test"); + + // Click login button + await page.getByTestId("login-button").click(); + + // Wait for navigation + await page.waitForURL("#/submodule-list"); + }); + + // Debug helper test + test("debug page content", async ({ page }) => { + await page.goto("/"); + + // Log the page content for debugging + console.log(await page.content()); + + // Take a screenshot + await page.screenshot({ path: "login-debug.png", fullPage: true }); + }); +}); diff --git a/5 - Testing/Ejercicios/Testing-Lab/package-lock.json b/5 - Testing/Ejercicios/Testing-Lab/package-lock.json index 3b04edc..0acdc8d 100755 --- a/5 - Testing/Ejercicios/Testing-Lab/package-lock.json +++ b/5 - Testing/Ejercicios/Testing-Lab/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@dotenvx/dotenvx": "^1.38.3", "@emotion/css": "^11.13.5", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", @@ -33,10 +34,12 @@ "react-spinners": "^0.15.0" }, "devDependencies": { + "@playwright/test": "^1.50.1", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.1.0", "@types/jest": "^29.5.14", + "@types/node": "^22.13.5", "@types/react": "^19.0.1", "@types/react-dom": "^19.0.2", "@vitejs/plugin-react": "^4.3.4", @@ -351,6 +354,94 @@ "node": ">=6.9.0" } }, + "node_modules/@dotenvx/dotenvx": { + "version": "1.38.3", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.38.3.tgz", + "integrity": "sha512-6tquYDfAiJbgQbYwWfL0jJHiUumY5EiFXVswk9sTwn5lWICMwOPmMOrM9TEVLzesfNMYwDyUiMp5WAA6yXs+SQ==", + "license": "BSD-3-Clause", + "dependencies": { + "commander": "^11.1.0", + "dotenv": "^16.4.5", + "eciesjs": "^0.4.10", + "execa": "^5.1.1", + "fdir": "^6.2.0", + "ignore": "^5.3.0", + "object-treeify": "1.1.33", + "picomatch": "^4.0.2", + "which": "^4.0.0" + }, + "bin": { + "dotenvx": "src/cli/dotenvx.js", + "git-dotenvx": "src/cli/dotenvx.js" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/fdir": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@ecies/ciphers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.2.tgz", + "integrity": "sha512-ylfGR7PyTd+Rm2PqQowG08BCKA22QuX8NzrL+LxAAvazN10DMwdJ2fWwAzRj05FI/M8vNFGm3cv9Wq/GFWCBLg==", + "license": "MIT", + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + }, + "peerDependencies": { + "@noble/ciphers": "^1.0.0" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -1303,6 +1394,61 @@ } } }, + "node_modules/@noble/ciphers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz", + "integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz", + "integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.1" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", + "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@playwright/test": { + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.1.tgz", + "integrity": "sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.50.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -2435,6 +2581,15 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2728,6 +2883,18 @@ "csstype": "^3.0.2" } }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2750,6 +2917,23 @@ "dev": true, "license": "MIT" }, + "node_modules/eciesjs": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.14.tgz", + "integrity": "sha512-eJAgf9pdv214Hn98FlUzclRMYWF7WfoLlkS9nWMTm1qcCwn6Ad4EGD9lr9HXMBfSrZhYQujRE+p0adPRkctC6A==", + "license": "MIT", + "dependencies": { + "@ecies/ciphers": "^0.2.2", + "@noble/ciphers": "^1.0.0", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0" + }, + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.74", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.74.tgz", @@ -2994,6 +3178,94 @@ "@types/estree": "^1.0.0" } }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/execa/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/execa/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==", + "license": "ISC" + }, + "node_modules/execa/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/expect": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", @@ -3279,6 +3551,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-symbol-description": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", @@ -3568,6 +3852,15 @@ "node": ">= 14" } }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -3581,6 +3874,15 @@ "node": ">=0.10.0" } }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/immer": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", @@ -3918,6 +4220,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-string": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", @@ -4026,7 +4340,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/jackspeak": { @@ -4450,6 +4763,12 @@ "node": ">= 0.10.0" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -4485,6 +4804,15 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -4684,6 +5012,27 @@ "node": ">=4" } }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/nwsapi": { "version": "2.2.16", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", @@ -4723,6 +5072,15 @@ "node": ">= 0.4" } }, + "node_modules/object-treeify": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", + "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/object.assign": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", @@ -4742,6 +5100,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -4903,6 +5276,53 @@ "node": ">=4" } }, + "node_modules/playwright": { + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.1.tgz", + "integrity": "sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.50.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.1.tgz", + "integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -5843,6 +6263,15 @@ "node": ">=4" } }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", diff --git a/5 - Testing/Ejercicios/Testing-Lab/package.json b/5 - Testing/Ejercicios/Testing-Lab/package.json index 5f965c7..1a46d1f 100755 --- a/5 - Testing/Ejercicios/Testing-Lab/package.json +++ b/5 - Testing/Ejercicios/Testing-Lab/package.json @@ -11,6 +11,9 @@ "type-check": "tsc --noEmit", "type-check:watch": "npm run type-check -- --watch", "test": "vitest -c ./config/test/config.ts", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "test:e2e:debug": "playwright test --debug", "clean": "rimraf dist" }, "imports": { @@ -19,6 +22,7 @@ "author": "Lemoncode", "license": "MIT", "dependencies": { + "@dotenvx/dotenvx": "^1.38.3", "@emotion/css": "^11.13.5", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", @@ -43,10 +47,12 @@ "react-spinners": "^0.15.0" }, "devDependencies": { + "@playwright/test": "^1.50.1", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.1.0", "@types/jest": "^29.5.14", + "@types/node": "^22.13.5", "@types/react": "^19.0.1", "@types/react-dom": "^19.0.2", "@vitejs/plugin-react": "^4.3.4", diff --git a/5 - Testing/Ejercicios/Testing-Lab/playwright.config.ts b/5 - Testing/Ejercicios/Testing-Lab/playwright.config.ts new file mode 100644 index 0000000..f2141c2 --- /dev/null +++ b/5 - Testing/Ejercicios/Testing-Lab/playwright.config.ts @@ -0,0 +1,36 @@ +import { config } from "@dotenvx/dotenvx"; +config({ + path: ".env.test", +}); + +import { defineConfig, devices } from "@playwright/test"; + +const BASE_URL = "http://localhost:5173/"; + +export default defineConfig({ + testDir: "./e2e", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 1, + workers: process.env.CI ? 1 : undefined, + reporter: "html", + use: { + baseURL: BASE_URL, + trace: "on-first-retry", + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + ], + webServer: { + command: "npm run start:dev", + url: BASE_URL, + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/5 - Testing/Ejercicios/Testing-Lab/src/common/components/spinner/spinner.component.spec.tsx b/5 - Testing/Ejercicios/Testing-Lab/src/common/components/spinner/spinner.component.spec.tsx new file mode 100644 index 0000000..660e947 --- /dev/null +++ b/5 - Testing/Ejercicios/Testing-Lab/src/common/components/spinner/spinner.component.spec.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { usePromiseTracker } from "react-promise-tracker"; +import { SpinnerComponent } from "./spinner.component"; +import { vi } from "vitest"; + +// Mock react-promise-tracker +vi.mock("react-promise-tracker", () => ({ + usePromiseTracker: vi.fn(), +})); + +describe("SpinnerComponent", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should show Modal when promise is in progress", () => { + // Arrange + const mockUsePromiseTracker = usePromiseTracker as jest.Mock; + mockUsePromiseTracker.mockReturnValue({ promiseInProgress: true }); + + // Act + render(); + + // Assert + const modal = screen.getByRole("presentation"); + expect(modal).toBeInTheDocument(); + }); + + it("should not show Modal when promise is not in progress", () => { + // Arrange + const mockUsePromiseTracker = usePromiseTracker as jest.Mock; + mockUsePromiseTracker.mockReturnValue({ promiseInProgress: false }); + + // Act + render(); + + // Assert + const modal = screen.queryByRole("presentation"); + expect(modal).not.toBeInTheDocument(); + }); + + it("should render Loader component inside Modal when promise is in progress", () => { + // Arrange + const mockUsePromiseTracker = usePromiseTracker as jest.Mock; + mockUsePromiseTracker.mockReturnValue({ promiseInProgress: true }); + + // Act + render(); + + // Assert + const loaderContainer = screen.getByTestId("loader-container"); + expect(loaderContainer).toBeInTheDocument(); + }); +}); diff --git a/5 - Testing/Ejercicios/Testing-Lab/src/common/components/spinner/spinner.component.tsx b/5 - Testing/Ejercicios/Testing-Lab/src/common/components/spinner/spinner.component.tsx index 3f27b58..29bc242 100755 --- a/5 - Testing/Ejercicios/Testing-Lab/src/common/components/spinner/spinner.component.tsx +++ b/5 - Testing/Ejercicios/Testing-Lab/src/common/components/spinner/spinner.component.tsx @@ -1,14 +1,14 @@ -import React from 'react'; -import { usePromiseTracker } from 'react-promise-tracker'; -import { Modal } from '@mui/material'; -import Loader from 'react-spinners/ScaleLoader'; -import * as classes from './spinner.styles'; +import React from "react"; +import { usePromiseTracker } from "react-promise-tracker"; +import { Modal } from "@mui/material"; +import Loader from "react-spinners/ScaleLoader"; +import * as classes from "./spinner.styles"; export const SpinnerComponent: React.FunctionComponent = () => { const { promiseInProgress } = usePromiseTracker(); return ( -
+
diff --git a/5 - Testing/Ejercicios/Testing-Lab/src/pods/login/components/login-form.component.tsx b/5 - Testing/Ejercicios/Testing-Lab/src/pods/login/components/login-form.component.tsx index 79494e4..2686ce5 100755 --- a/5 - Testing/Ejercicios/Testing-Lab/src/pods/login/components/login-form.component.tsx +++ b/5 - Testing/Ejercicios/Testing-Lab/src/pods/login/components/login-form.component.tsx @@ -1,11 +1,11 @@ -import React from 'react'; -import { Formik, Form } from 'formik'; -import { Button } from '@mui/material'; -import { TextFieldComponent } from '#common/components'; -import { Login, createEmptyLogin } from '../login.vm'; -import { formValidation } from '../login.validation'; -import * as classes from './login-form.styles'; -import { literals } from '#core/i18n'; +import React from "react"; +import { Formik, Form } from "formik"; +import { Button } from "@mui/material"; +import { TextFieldComponent } from "#common/components"; +import { Login, createEmptyLogin } from "../login.vm"; +import { formValidation } from "../login.validation"; +import * as classes from "./login-form.styles"; +import { literals } from "#core/i18n"; interface Props { onLogin: (login: Login) => void; @@ -20,23 +20,26 @@ export const LoginFormComponent: React.FunctionComponent = (props) => { validate={formValidation.validateForm} > {() => ( -
+ diff --git a/5 - Testing/Ejercicios/Testing-Lab/vite.config.ts b/5 - Testing/Ejercicios/Testing-Lab/vite.config.ts index 0466183..081c8d9 100755 --- a/5 - Testing/Ejercicios/Testing-Lab/vite.config.ts +++ b/5 - Testing/Ejercicios/Testing-Lab/vite.config.ts @@ -1,5 +1,5 @@ -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [react()],