From 6947cd147b4ea71b449d5cf83246a8f8f35ba075 Mon Sep 17 00:00:00 2001 From: Diego Torres Date: Thu, 3 Jul 2025 21:10:37 -0500 Subject: [PATCH 1/5] fix: update database URL in .env.test and add dotenv-cli to dependencies; refactor Playwright config to comment out unused browsers --- .env.test | 5 +---- package-lock.json | 27 +++++++++++++++++++++++++++ package.json | 11 ++++++++++- playwright.config.ts | 16 ++++++++-------- 4 files changed, 46 insertions(+), 13 deletions(-) diff --git a/.env.test b/.env.test index 5b49967..c9ea21d 100644 --- a/.env.test +++ b/.env.test @@ -1,7 +1,4 @@ -DATABASE_URL="postgresql://diego@localhost:5432/fullstock?schema=public" +DATABASE_URL="postgresql://diego@localhost:5432/fullstock_test?schema=public" # Admin Database (for database creation/deletion) ADMIN_DB_NAME=postgres - -# This was inserted by `prisma init`: -[object Promise] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 65ac888..788a223 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,7 @@ "@typescript-eslint/parser": "^8.31.0", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.21", + "dotenv-cli": "^8.0.0", "eslint": "^9.25.1", "eslint-plugin-import": "^2.31.0", "eslint-plugin-react-hooks": "^5.0.0", @@ -6122,6 +6123,32 @@ "url": "https://dotenvx.com" } }, + "node_modules/dotenv-cli": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-8.0.0.tgz", + "integrity": "sha512-aLqYbK7xKOiTMIRf1lDPbI+Y+Ip/wo5k3eyp6ePysVaSqbyxjyK3dK35BTxG+rmd7djf5q2UPs4noPNH+cj0Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6", + "dotenv": "^16.3.0", + "dotenv-expand": "^10.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "dotenv": "cli.js" + } + }, + "node_modules/dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", diff --git a/package.json b/package.json index d84a35d..ad81c24 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,15 @@ "preview": "vite preview", "start": "react-router-serve ./build/server/index.js", "type-check": "react-router typegen && tsc", - "test": "vitest" + "test": "vitest", + "prisma": "prisma", + "prisma:generate": "prisma generate", + "prisma:migrate": "prisma migrate dev --name init", + "prisma:migrate:deploy": "prisma migrate deploy", + "prisma:migrate:status": "prisma migrate status", + "prisma:studio": "prisma studio", + "prisma:seed": "prisma db seed", + "test:prisma:migrate:deploy": "dotenv -e .env.test -- prisma migrate deploy" }, "prisma": { "seed": "tsx ./prisma/seed.ts" @@ -59,6 +67,7 @@ "@typescript-eslint/parser": "^8.31.0", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.21", + "dotenv-cli": "^8.0.0", "eslint": "^9.25.1", "eslint-plugin-import": "^2.31.0", "eslint-plugin-react-hooks": "^5.0.0", diff --git a/playwright.config.ts b/playwright.config.ts index a51b324..466622f 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -43,15 +43,15 @@ export default defineConfig({ use: { ...devices["Desktop Chrome"] }, }, - { - name: "firefox", - use: { ...devices["Desktop Firefox"] }, - }, + // { + // name: "firefox", + // use: { ...devices["Desktop Firefox"] }, + // }, - { - name: "webkit", - use: { ...devices["Desktop Safari"] }, - }, + // { + // name: "webkit", + // use: { ...devices["Desktop Safari"] }, + // }, /* Test against mobile viewports. */ // { From 3b2c500b5049ed525e394b8d04de30f80b09038b Mon Sep 17 00:00:00 2001 From: Diego Torres Date: Fri, 4 Jul 2025 20:11:20 -0500 Subject: [PATCH 2/5] feat: refactor Playwright tests to use base URL and clean database utility; add global setup for test environment --- .env.test | 2 +- .gitignore | 2 +- package.json | 4 +++- playwright.config.ts | 19 ++++++++++-------- src/e2e/demo.signin.spec.ts | 31 +++++++----------------------- src/e2e/demo.spec.ts | 4 +++- src/e2e/guest-create-order.spec.ts | 4 ++-- src/e2e/setup.ts | 5 +++++ src/e2e/user-create-order.spec.ts | 27 ++++++-------------------- src/e2e/utils-tests-e2e.ts | 16 +++++++++++++++ 10 files changed, 55 insertions(+), 59 deletions(-) create mode 100644 src/e2e/setup.ts diff --git a/.env.test b/.env.test index c9ea21d..7b1edd0 100644 --- a/.env.test +++ b/.env.test @@ -1,4 +1,4 @@ DATABASE_URL="postgresql://diego@localhost:5432/fullstock_test?schema=public" # Admin Database (for database creation/deletion) -ADMIN_DB_NAME=postgres +ADMIN_DB_NAME=postgres \ No newline at end of file diff --git a/.gitignore b/.gitignore index 304ff76..02f5e7b 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,7 @@ dist-ssr # React Router .react-router/ -.build/ +build/ .env # Playwright diff --git a/package.json b/package.json index ad81c24..b24dcbc 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,9 @@ "prisma:migrate:status": "prisma migrate status", "prisma:studio": "prisma studio", "prisma:seed": "prisma db seed", - "test:prisma:migrate:deploy": "dotenv -e .env.test -- prisma migrate deploy" + "test:prisma:migrate:deploy": "dotenv -e .env.test -- prisma migrate deploy", + "test:e2e": "playwright test", + "test:prisma:seed": "dotenv -e .env.test prisma db seed" }, "prisma": { "seed": "tsx ./prisma/seed.ts" diff --git a/playwright.config.ts b/playwright.config.ts index 466622f..e4d5c07 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,6 +1,8 @@ import { defineConfig, devices } from "@playwright/test"; import dotenv from "dotenv"; +import { baseUrl, command } from "@/e2e/utils-tests-e2e"; + // Load test environment variables dotenv.config({ path: ".env.test" }); @@ -18,15 +20,16 @@ dotenv.config({ path: ".env.test" }); export default defineConfig({ testDir: "./src/e2e", /* Run tests in files in parallel */ - fullyParallel: true, + // globalSetup: require.resolve("./src/e2e/setup.ts"), + fullyParallel: false, /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ retries: process.env.CI ? 2 : 0, /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, + workers: 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: "html", + reporter: "list", /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ @@ -75,9 +78,9 @@ export default defineConfig({ ], /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://localhost:3000', - // reuseExistingServer: !process.env.CI, - // }, + webServer: { + command: command, + url: baseUrl, + reuseExistingServer: !process.env.CI, + }, }); diff --git a/src/e2e/demo.signin.spec.ts b/src/e2e/demo.signin.spec.ts index ce33412..b0057b0 100644 --- a/src/e2e/demo.signin.spec.ts +++ b/src/e2e/demo.signin.spec.ts @@ -4,10 +4,12 @@ import { prisma } from "@/db/prisma"; import { hashPassword } from "@/lib/security"; import type { CreateUserDTO } from "@/models/user.model"; +import { baseUrl, cleanDatabase } from "./utils-tests-e2e"; + test.describe("Visitante inicio sesion", () => { - let testUserId: number; + test.beforeEach(async () => { + await cleanDatabase(); - test.beforeAll(async () => { const testUser: CreateUserDTO = { email: "diego@codeable.com", name: null, @@ -15,38 +17,19 @@ test.describe("Visitante inicio sesion", () => { isGuest: false, }; - const existingUser = await prisma.user.findUnique({ - where: { email: testUser.email }, - }); - - if (existingUser) { - await prisma.user.delete({ - where: { id: existingUser.id }, - }); - } - - const user = await prisma.user.create({ + await prisma.user.create({ data: testUser, }); - testUserId = user.id; - }); - - test.afterAll(async () => { - await prisma.user.delete({ - where: { id: testUserId }, - }); }); test("test", async ({ page }) => { - await page.goto("http://localhost:5173/"); + await page.goto(baseUrl); await page.getByTestId("login").click(); await page.getByRole("textbox", { name: "Correo electrónico" }).click(); await page .getByRole("textbox", { name: "Correo electrónico" }) .fill("diego@codeable.com"); - await page - .getByRole("textbox", { name: "Correo electrónico" }) - .press("Tab"); + await page.getByRole("textbox", { name: "Contraseña" }).fill("letmein"); await page.getByRole("button", { name: "Iniciar sesión" }).click(); diff --git a/src/e2e/demo.spec.ts b/src/e2e/demo.spec.ts index 9ec8857..51ec64d 100644 --- a/src/e2e/demo.spec.ts +++ b/src/e2e/demo.spec.ts @@ -1,8 +1,10 @@ import { test, expect } from "@playwright/test"; +import { baseUrl } from "./utils-tests-e2e"; + test.describe("Visitor", () => { test("can add a product to the cart", async ({ page }) => { - await page.goto("http://localhost:5173/"); + await page.goto(baseUrl); await expect(page).toHaveTitle(/inicio/i); diff --git a/src/e2e/guest-create-order.spec.ts b/src/e2e/guest-create-order.spec.ts index 223637a..576f137 100644 --- a/src/e2e/guest-create-order.spec.ts +++ b/src/e2e/guest-create-order.spec.ts @@ -1,14 +1,14 @@ // import { createOrderFormData } from "@/lib/utils.tests"; import { expect, test } from "@playwright/test"; -import { createOrderFormData } from "./utils-tests-e2e"; +import { baseUrl, createOrderFormData } from "./utils-tests-e2e"; export type OrderFormData = Record; test.describe("Guest", () => { test("Guest can create an order", async ({ page }) => { // Navegar a la tienda y agregar un producto - await page.goto("http://localhost:5173/"); + await page.goto(baseUrl); await page.getByRole("menuitem", { name: "Polos" }).click(); await page.getByTestId("product-item").first().click(); diff --git a/src/e2e/setup.ts b/src/e2e/setup.ts new file mode 100644 index 0000000..1a503d4 --- /dev/null +++ b/src/e2e/setup.ts @@ -0,0 +1,5 @@ +import { cleanDatabase } from "./utils-tests-e2e"; + +export default async function globalSetup() { + await cleanDatabase(); +} diff --git a/src/e2e/user-create-order.spec.ts b/src/e2e/user-create-order.spec.ts index d5f0ed1..591f09a 100644 --- a/src/e2e/user-create-order.spec.ts +++ b/src/e2e/user-create-order.spec.ts @@ -4,10 +4,12 @@ import { prisma } from "@/db/prisma"; import { hashPassword } from "@/lib/security"; import type { CreateUserDTO } from "@/models/user.model"; +import { baseUrl, cleanDatabase } from "./utils-tests-e2e"; + test.describe("User", () => { - let testUserId: number; + test.beforeEach(async () => { + await cleanDatabase(); - test.beforeAll(async () => { const testUser: CreateUserDTO = { email: "diego@codeable.com", name: null, @@ -15,30 +17,13 @@ test.describe("User", () => { isGuest: false, }; - const existingUser = await prisma.user.findUnique({ - where: { email: testUser.email }, - }); - - if (existingUser) { - await prisma.user.delete({ - where: { id: existingUser.id }, - }); - } - - const user = await prisma.user.create({ + await prisma.user.create({ data: testUser, }); - testUserId = user.id; - }); - - test.afterAll(async () => { - await prisma.user.delete({ - where: { id: testUserId }, - }); }); test("User can create an order", async ({ page }) => { - await page.goto("http://localhost:5173/"); + await page.goto(baseUrl); await page.getByRole("link", { name: "Iniciar sesión" }).click(); diff --git a/src/e2e/utils-tests-e2e.ts b/src/e2e/utils-tests-e2e.ts index 6bca144..1d3a40f 100644 --- a/src/e2e/utils-tests-e2e.ts +++ b/src/e2e/utils-tests-e2e.ts @@ -1,5 +1,7 @@ /* Helper functions → Playwright */ +import { prisma } from "@/db/prisma"; + export type OrderFormData = Record; export const createOrderFormData = ( @@ -16,3 +18,17 @@ export const createOrderFormData = ( Teléfono: "987456321", ...overrides, }); + +export async function cleanDatabase() { + await prisma.order.deleteMany(); + await prisma.cart.deleteMany(); + await prisma.user.deleteMany(); + + // Mantenemos product y category +} + +export const baseUrl = process.env.CI + ? "http://localhost:3000/" + : "http://localhost:5173/"; + +export const command = process.env.CI ? "npm run start" : "npm run dev"; From 81b2905a070a8b0d2b4a3489fd60363636e0df20 Mon Sep 17 00:00:00 2001 From: Diego Torres Date: Fri, 4 Jul 2025 20:25:59 -0500 Subject: [PATCH 3/5] feat: add end-to-end testing workflow with PostgreSQL service and migration steps --- .github/workflows/tests.yaml | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 44f4ba2..c8c94f3 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -57,3 +57,41 @@ jobs: - name: Run Unit Tests run: npm run test + + e2e-test: + runs-on: ubuntu-latest + needs: [test] + services: + postgres: + image: postgres:15 + ports: + - 5432:5432 + env: + POSTGRES_USER: diego + POSTGRES_DB: fullstock_test + POSTGRES_HOST_AUTH_METHOD: trust + options: >- + --health-cmd "pg_isready -U postgres" --health-interval 10s --health-timeout 5s --health-retries 5 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v3 + with: + node-version: "lts/*" + + - name: Install dependencies + run: npm clean-install + + - name: Build the application + run: npm run build + + - name: Migrate the database + run: npm run test:prisma:migrate:deploy + + - name: Seed the database + run: npm run test:prisma:seed + + - name: Run E2E Tests + run: npm run test:e2e From 893f8198503247b3eb4eb4279b6dbd3cdc534637 Mon Sep 17 00:00:00 2001 From: Diego Torres Date: Fri, 4 Jul 2025 20:33:25 -0500 Subject: [PATCH 4/5] feat: add Playwright browser installation step in CI workflow --- .github/workflows/tests.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c8c94f3..b02b8e3 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -84,6 +84,9 @@ jobs: - name: Install dependencies run: npm clean-install + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Build the application run: npm run build From ba223505230bfb73cc7ab1a87aee77f801c4cac6 Mon Sep 17 00:00:00 2001 From: Diego Torres Date: Fri, 4 Jul 2025 20:52:51 -0500 Subject: [PATCH 5/5] refactor: consolidate database cleanup logic in end-to-end tests --- src/e2e/demo.signin.spec.ts | 6 ++++-- src/e2e/demo.spec.ts | 6 +++++- src/e2e/guest-create-order.spec.ts | 6 +++++- src/e2e/user-create-order.spec.ts | 6 ++++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/e2e/demo.signin.spec.ts b/src/e2e/demo.signin.spec.ts index b0057b0..b718cdc 100644 --- a/src/e2e/demo.signin.spec.ts +++ b/src/e2e/demo.signin.spec.ts @@ -6,10 +6,12 @@ import type { CreateUserDTO } from "@/models/user.model"; import { baseUrl, cleanDatabase } from "./utils-tests-e2e"; +test.beforeEach(async () => { + await cleanDatabase(); +}); + test.describe("Visitante inicio sesion", () => { test.beforeEach(async () => { - await cleanDatabase(); - const testUser: CreateUserDTO = { email: "diego@codeable.com", name: null, diff --git a/src/e2e/demo.spec.ts b/src/e2e/demo.spec.ts index 51ec64d..90632b9 100644 --- a/src/e2e/demo.spec.ts +++ b/src/e2e/demo.spec.ts @@ -1,6 +1,10 @@ import { test, expect } from "@playwright/test"; -import { baseUrl } from "./utils-tests-e2e"; +import { baseUrl, cleanDatabase } from "./utils-tests-e2e"; + +test.beforeEach(async () => { + await cleanDatabase(); +}); test.describe("Visitor", () => { test("can add a product to the cart", async ({ page }) => { diff --git a/src/e2e/guest-create-order.spec.ts b/src/e2e/guest-create-order.spec.ts index 576f137..b9d0516 100644 --- a/src/e2e/guest-create-order.spec.ts +++ b/src/e2e/guest-create-order.spec.ts @@ -1,10 +1,14 @@ // import { createOrderFormData } from "@/lib/utils.tests"; import { expect, test } from "@playwright/test"; -import { baseUrl, createOrderFormData } from "./utils-tests-e2e"; +import { baseUrl, cleanDatabase, createOrderFormData } from "./utils-tests-e2e"; export type OrderFormData = Record; +test.beforeEach(async () => { + await cleanDatabase(); +}); + test.describe("Guest", () => { test("Guest can create an order", async ({ page }) => { // Navegar a la tienda y agregar un producto diff --git a/src/e2e/user-create-order.spec.ts b/src/e2e/user-create-order.spec.ts index 591f09a..5af2716 100644 --- a/src/e2e/user-create-order.spec.ts +++ b/src/e2e/user-create-order.spec.ts @@ -6,10 +6,12 @@ import type { CreateUserDTO } from "@/models/user.model"; import { baseUrl, cleanDatabase } from "./utils-tests-e2e"; +test.beforeEach(async () => { + await cleanDatabase(); +}); + test.describe("User", () => { test.beforeEach(async () => { - await cleanDatabase(); - const testUser: CreateUserDTO = { email: "diego@codeable.com", name: null,