diff --git a/.gitignore b/.gitignore
index fd88a74..4c0406b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,4 +11,5 @@ storybook-static
coverage
test-results
playwright-report
+playwright-reports
.vitest
diff --git a/playwright-reports/html/data/06e44c615840f735bba73424e927e0954d8ffe9f.md b/playwright-reports/html/data/06e44c615840f735bba73424e927e0954d8ffe9f.md
deleted file mode 100644
index 847fb91..0000000
--- a/playwright-reports/html/data/06e44c615840f735bba73424e927e0954d8ffe9f.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Rate Limiting >> AI endpoints rate limit after daily quota exceeded
-- Location: tests/e2e/security.spec.ts:108:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 65
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/073b3a84e67a48c170161e0a7ee0aae27a123df8.md b/playwright-reports/html/data/073b3a84e67a48c170161e0a7ee0aae27a123df8.md
deleted file mode 100644
index 8f865c6..0000000
--- a/playwright-reports/html/data/073b3a84e67a48c170161e0a7ee0aae27a123df8.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - CORS Headers >> CORS headers are present on API responses
-- Location: tests/e2e/security.spec.ts:191:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/nonexistent-route
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/0e5a355480ad6428e83382d4468d07963b681082.md b/playwright-reports/html/data/0e5a355480ad6428e83382d4468d07963b681082.md
deleted file mode 100644
index f2e3b69..0000000
--- a/playwright-reports/html/data/0e5a355480ad6428e83382d4468d07963b681082.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: api.spec.ts >> Gateway API >> Gateway routes /api/ai/* to users service
-- Location: tests/e2e/api.spec.ts:240:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/ai/usage
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - Authorization: Bearer dummy
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/11ca00cf695dab257487e5bebac2e2c7a3169933.md b/playwright-reports/html/data/11ca00cf695dab257487e5bebac2e2c7a3169933.md
deleted file mode 100644
index 4ccea51..0000000
--- a/playwright-reports/html/data/11ca00cf695dab257487e5bebac2e2c7a3169933.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Input Validation >> XSS attempt in login email is handled safely
-- Location: tests/e2e/security.spec.ts:163:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 75
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/12598e1fbd51e19c41565b90e5b1515b3b5a9dcc.md b/playwright-reports/html/data/12598e1fbd51e19c41565b90e5b1515b3b5a9dcc.md
deleted file mode 100644
index 7238c0e..0000000
--- a/playwright-reports/html/data/12598e1fbd51e19c41565b90e5b1515b3b5a9dcc.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Token Handling >> Token with invalid signature is rejected
-- Location: tests/e2e/security.spec.ts:274:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/ai/usage
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxOTk5OTk5OTk5OX0.wronngsignature
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/1800f3ea9cfe96cf643841bb85b0e393d9bc4a72.md b/playwright-reports/html/data/1800f3ea9cfe96cf643841bb85b0e393d9bc4a72.md
deleted file mode 100644
index 23c6299..0000000
--- a/playwright-reports/html/data/1800f3ea9cfe96cf643841bb85b0e393d9bc4a72.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: api.spec.ts >> AI API Endpoints >> AI Usage Endpoint >> GET /ai/usage returns usage stats for company
-- Location: tests/e2e/api.spec.ts:144:5
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 65
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/1fcbd74fe24dee6388b780541426c639edc14c88.webm b/playwright-reports/html/data/1fcbd74fe24dee6388b780541426c639edc14c88.webm
deleted file mode 100644
index 49e0345..0000000
Binary files a/playwright-reports/html/data/1fcbd74fe24dee6388b780541426c639edc14c88.webm and /dev/null differ
diff --git a/playwright-reports/html/data/203efa55ac7aeeb02acb4df84d4a8a641f0b23aa.md b/playwright-reports/html/data/203efa55ac7aeeb02acb4df84d4a8a641f0b23aa.md
deleted file mode 100644
index 7b0f05c..0000000
--- a/playwright-reports/html/data/203efa55ac7aeeb02acb4df84d4a8a641f0b23aa.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Authorization >> User cannot access admin endpoints with regular user token
-- Location: tests/e2e/security.spec.ts:200:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 61
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/216d7d99c1dba7a3f5a89ff2c97220a1ca0a513e.md b/playwright-reports/html/data/216d7d99c1dba7a3f5a89ff2c97220a1ca0a513e.md
deleted file mode 100644
index 2591469..0000000
--- a/playwright-reports/html/data/216d7d99c1dba7a3f5a89ff2c97220a1ca0a513e.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Token Handling >> Token with invalid signature is rejected
-- Location: tests/e2e/security.spec.ts:274:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/ai/usage
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxOTk5OTk5OTk5OX0.wronngsignature
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/24d5a86915194471726ebb9579c0ce4bdf75f353.webm b/playwright-reports/html/data/24d5a86915194471726ebb9579c0ce4bdf75f353.webm
deleted file mode 100644
index 28169d5..0000000
Binary files a/playwright-reports/html/data/24d5a86915194471726ebb9579c0ce4bdf75f353.webm and /dev/null differ
diff --git a/playwright-reports/html/data/27a75f235c7c6cff89e69a1724e1a0d6a4032019.md b/playwright-reports/html/data/27a75f235c7c6cff89e69a1724e1a0d6a4032019.md
deleted file mode 100644
index ea20617..0000000
--- a/playwright-reports/html/data/27a75f235c7c6cff89e69a1724e1a0d6a4032019.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Authentication >> Protected endpoint rejects request without token
-- Location: tests/e2e/security.spec.ts:42:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/ai/usage
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/2bb9e70b5df9cda14db7bccb36404f908bdfe2d7.md b/playwright-reports/html/data/2bb9e70b5df9cda14db7bccb36404f908bdfe2d7.md
deleted file mode 100644
index 2678973..0000000
--- a/playwright-reports/html/data/2bb9e70b5df9cda14db7bccb36404f908bdfe2d7.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Rate Limiting >> Login rate limits after multiple failed attempts
-- Location: tests/e2e/security.spec.ts:73:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 76
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/30dc3bdd2b699405346a2b8d6d0953ddfed88280.md b/playwright-reports/html/data/30dc3bdd2b699405346a2b8d6d0953ddfed88280.md
deleted file mode 100644
index 72858cb..0000000
--- a/playwright-reports/html/data/30dc3bdd2b699405346a2b8d6d0953ddfed88280.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Response Headers >> API does not leak sensitive information in error responses
-- Location: tests/e2e/security.spec.ts:230:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 55
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/349260bce55b970764d34c52e3aadd9ed06e6bb4.md b/playwright-reports/html/data/349260bce55b970764d34c52e3aadd9ed06e6bb4.md
deleted file mode 100644
index 7ec8e33..0000000
--- a/playwright-reports/html/data/349260bce55b970764d34c52e3aadd9ed06e6bb4.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Authentication >> JWT token is not returned for invalid credentials
-- Location: tests/e2e/security.spec.ts:6:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 62
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/3bafe09d2a425af60248fc3d45d05a5551e1fa10.md b/playwright-reports/html/data/3bafe09d2a425af60248fc3d45d05a5551e1fa10.md
deleted file mode 100644
index 97aebaf..0000000
--- a/playwright-reports/html/data/3bafe09d2a425af60248fc3d45d05a5551e1fa10.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: api.spec.ts >> Auth API Endpoints >> POST /auth/login returns token for valid credentials
-- Location: tests/e2e/api.spec.ts:182:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 61
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/3fff103345fe4e6ae2d7112dded59a7464f68f4a.md b/playwright-reports/html/data/3fff103345fe4e6ae2d7112dded59a7464f68f4a.md
deleted file mode 100644
index 1d51598..0000000
--- a/playwright-reports/html/data/3fff103345fe4e6ae2d7112dded59a7464f68f4a.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: api.spec.ts >> AI API Endpoints >> Company AI - Generate Job Field >> POST /ai/generate-job-field returns generated content
-- Location: tests/e2e/api.spec.ts:41:5
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 65
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/4329687e0f6e6c05cb74888c9498fa97da221e19.md b/playwright-reports/html/data/4329687e0f6e6c05cb74888c9498fa97da221e19.md
deleted file mode 100644
index a93e890..0000000
--- a/playwright-reports/html/data/4329687e0f6e6c05cb74888c9498fa97da221e19.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Authentication >> Protected endpoint rejects request with malformed token
-- Location: tests/e2e/security.spec.ts:49:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/ai/usage
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - Authorization: Bearer invalid.malformed.token
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/433304ff69d838aa70f6274e6bc4b9f4ac95ee02.md b/playwright-reports/html/data/433304ff69d838aa70f6274e6bc4b9f4ac95ee02.md
deleted file mode 100644
index 3ceff5a..0000000
--- a/playwright-reports/html/data/433304ff69d838aa70f6274e6bc4b9f4ac95ee02.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Authentication >> Login without email returns proper error
-- Location: tests/e2e/security.spec.ts:20:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 27
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/433ec107ae011c65a5d8e0b230619139e033c5ad.md b/playwright-reports/html/data/433ec107ae011c65a5d8e0b230619139e033c5ad.md
deleted file mode 100644
index 7a1cc3c..0000000
--- a/playwright-reports/html/data/433ec107ae011c65a5d8e0b230619139e033c5ad.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Rate Limiting >> Login rate limits after multiple failed attempts
-- Location: tests/e2e/security.spec.ts:73:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 76
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/454d893103d2da92da2414583ee4d96021d18eae.md b/playwright-reports/html/data/454d893103d2da92da2414583ee4d96021d18eae.md
deleted file mode 100644
index 613e69f..0000000
--- a/playwright-reports/html/data/454d893103d2da92da2414583ee4d96021d18eae.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Authentication >> Login without password returns proper error
-- Location: tests/e2e/security.spec.ts:31:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 28
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/4bb4548304ad4b7e3bc6c05d099ee4faf1b4ecd2.md b/playwright-reports/html/data/4bb4548304ad4b7e3bc6c05d099ee4faf1b4ecd2.md
deleted file mode 100644
index 0ac57bf..0000000
--- a/playwright-reports/html/data/4bb4548304ad4b7e3bc6c05d099ee4faf1b4ecd2.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Input Validation >> XSS attempt in login email is handled safely
-- Location: tests/e2e/security.spec.ts:163:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 75
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/5a0d4d04689ba72eabcd73cfe2e48aeef318df71.md b/playwright-reports/html/data/5a0d4d04689ba72eabcd73cfe2e48aeef318df71.md
deleted file mode 100644
index 8147c1a..0000000
--- a/playwright-reports/html/data/5a0d4d04689ba72eabcd73cfe2e48aeef318df71.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Response Headers >> API does not leak sensitive information in error responses
-- Location: tests/e2e/security.spec.ts:230:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 55
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/5cc9d9d62cbff7d489c9b1c25cfeae1d4c4d3abf.md b/playwright-reports/html/data/5cc9d9d62cbff7d489c9b1c25cfeae1d4c4d3abf.md
deleted file mode 100644
index 3b97740..0000000
--- a/playwright-reports/html/data/5cc9d9d62cbff7d489c9b1c25cfeae1d4c4d3abf.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - CORS Headers >> CORS headers are present on API responses
-- Location: tests/e2e/security.spec.ts:191:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/nonexistent-route
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/61580f75bfe0e42a119ef7795079c96b58160034.md b/playwright-reports/html/data/61580f75bfe0e42a119ef7795079c96b58160034.md
deleted file mode 100644
index 60e7948..0000000
--- a/playwright-reports/html/data/61580f75bfe0e42a119ef7795079c96b58160034.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Authorization >> User cannot access admin endpoints with regular user token
-- Location: tests/e2e/security.spec.ts:200:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 61
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/6b5a98b22612f377b15ec8fa3a0877a588b8e7d0.md b/playwright-reports/html/data/6b5a98b22612f377b15ec8fa3a0877a588b8e7d0.md
deleted file mode 100644
index ce74ec6..0000000
--- a/playwright-reports/html/data/6b5a98b22612f377b15ec8fa3a0877a588b8e7d0.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Authentication >> Login without password returns proper error
-- Location: tests/e2e/security.spec.ts:31:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 28
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/70f0445f0ccf67a917ab8b0a36f15e738ee88abe.md b/playwright-reports/html/data/70f0445f0ccf67a917ab8b0a36f15e738ee88abe.md
deleted file mode 100644
index e6f673d..0000000
--- a/playwright-reports/html/data/70f0445f0ccf67a917ab8b0a36f15e738ee88abe.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Authentication >> Protected endpoint rejects request with malformed token
-- Location: tests/e2e/security.spec.ts:49:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/ai/usage
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - Authorization: Bearer invalid.malformed.token
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/76dd4ddcee69aa44313eaab9e8507d1aa33ef783.md b/playwright-reports/html/data/76dd4ddcee69aa44313eaab9e8507d1aa33ef783.md
deleted file mode 100644
index a667bde..0000000
--- a/playwright-reports/html/data/76dd4ddcee69aa44313eaab9e8507d1aa33ef783.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: api.spec.ts >> Gateway API >> Gateway returns 404 for unknown routes
-- Location: tests/e2e/api.spec.ts:255:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/nonexistent-route
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/7dc3bad8e0544ace88c7d46975f6ef75f42313e6.md b/playwright-reports/html/data/7dc3bad8e0544ace88c7d46975f6ef75f42313e6.md
deleted file mode 100644
index ea60b80..0000000
--- a/playwright-reports/html/data/7dc3bad8e0544ace88c7d46975f6ef75f42313e6.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: api.spec.ts >> Auth API Endpoints >> POST /auth/login returns 401 for invalid credentials
-- Location: tests/e2e/api.spec.ts:202:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 58
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/83970aa84141905481132a9341730c3d93412bfd.md b/playwright-reports/html/data/83970aa84141905481132a9341730c3d93412bfd.md
deleted file mode 100644
index f8c43aa..0000000
--- a/playwright-reports/html/data/83970aa84141905481132a9341730c3d93412bfd.md
+++ /dev/null
@@ -1,433 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: accessibility.spec.ts >> Public Frontend Accessibility >> homepage should have no accessibility violations
-- Location: tests/e2e/accessibility.spec.ts:5:3
-
-# Error details
-
-```
-Error: expect(received).toEqual(expected) // deep equality
-
-- Expected - 1
-+ Received + 58
-
-- Array []
-+ Array [
-+ Object {
-+ "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
-+ "help": "Elements must meet minimum color contrast ratio thresholds",
-+ "helpUrl": "https://dequeuniversity.com/rules/axe/4.11/color-contrast?application=playwright",
-+ "id": "color-contrast",
-+ "impact": "serious",
-+ "nodes": Array [
-+ Object {
-+ "all": Array [],
-+ "any": Array [
-+ Object {
-+ "data": Object {
-+ "bgColor": "#fd6116",
-+ "contrastRatio": 3.04,
-+ "expectedContrastRatio": "4.5:1",
-+ "fgColor": "#ffffff",
-+ "fontSize": "10.5pt (14px)",
-+ "fontWeight": "normal",
-+ "messageKey": null,
-+ },
-+ "id": "color-contrast",
-+ "impact": "serious",
-+ "message": "Element has insufficient color contrast of 3.04 (foreground color: #ffffff, background color: #fd6116, font size: 10.5pt (14px), font weight: normal). Expected contrast ratio of 4.5:1",
-+ "relatedNodes": Array [
-+ Object {
-+ "html": "",
-+ "target": Array [
-+ "button[data-hk=\"00000001000001000000000a1080\"]",
-+ ],
-+ },
-+ ],
-+ },
-+ ],
-+ "failureSummary": "Fix any of the following:
-+ Element has insufficient color contrast of 3.04 (foreground color: #ffffff, background color: #fd6116, font size: 10.5pt (14px), font weight: normal). Expected contrast ratio of 4.5:1",
-+ "html": "",
-+ "impact": "serious",
-+ "none": Array [],
-+ "target": Array [
-+ "button[data-hk=\"00000001000001000000000a1080\"]",
-+ ],
-+ },
-+ ],
-+ "tags": Array [
-+ "cat.color",
-+ "wcag2aa",
-+ "wcag143",
-+ "TTv5",
-+ "TT13.c",
-+ "EN-301-549",
-+ "EN-9.1.4.3",
-+ "ACT",
-+ "RGAAv4",
-+ "RGAA-3.2.1",
-+ ],
-+ },
-+ ]
-```
-
-# Page snapshot
-
-```yaml
-- generic [ref=e2]:
- - main [ref=e3]:
- - generic:
- - generic:
- - generic:
- - img
- - generic:
- - img
- - generic:
- - img
- - generic:
- - img
- - generic:
- - img
- - generic [ref=e4]:
- - navigation [ref=e6]:
- - link "Nxtgauge home" [ref=e7] [cursor=pointer]:
- - /url: /
- - img "NXTGAUGE" [ref=e8]
- - button "Open navigation menu" [ref=e9]:
- - generic [ref=e10]: Menu
- - generic [ref=e16]:
- - generic [ref=e17]:
- - heading "Trusted hiring and opportunities, verified." [level=1] [ref=e18]
- - paragraph [ref=e19]: Hire trusted professionals, post verified jobs, and apply faster in one platform.
- - generic [ref=e20]:
- - link "Get Started" [ref=e21] [cursor=pointer]:
- - /url: /signup/customer
- - link "How It Works" [ref=e22] [cursor=pointer]:
- - /url: /#how-it-works
- - paragraph [ref=e23]: Most profile and listing reviews are completed within 24-48 hours.
- - region "Nxtgauge opportunity graph preview" [ref=e25]:
- - generic [ref=e26]:
- - generic [ref=e27]:
- - img [ref=e28]
- - generic [ref=e45]: Developer
- - generic [ref=e46]: Tutor
- - generic [ref=e47]: Photographer
- - generic [ref=e48]:
- - generic [ref=e49]:
- - generic [ref=e50]: ✓
- - generic [ref=e51]: Everything from Nxtgauge
- - paragraph [ref=e52]: Opportunity Workspace
- - generic [ref=e53]:
- - strong [ref=e54]: Verified Profiles
- - generic [ref=e55]: Trust layer ready
- - generic [ref=e56]:
- - strong [ref=e57]: Matched Opportunities
- - generic [ref=e58]: Priority queue
- - generic [ref=e59]:
- - strong [ref=e60]: Responses
- - generic [ref=e61]: Tracked in one flow
- - generic [ref=e62]:
- - strong [ref=e63]: Updates
- - generic [ref=e64]: Live status signals
- - paragraph [ref=e66]: Opportunities start scattered
- - generic [ref=e68]:
- - generic [ref=e70]:
- - heading "Who Nxtgauge is for" [level=2] [ref=e71]
- - paragraph [ref=e72]: Start with one account, then activate the journey you need.
- - generic [ref=e73]:
- - article [ref=e74]:
- - img "Post a Job" [ref=e76]
- - generic [ref=e78]:
- - generic [ref=e79]:
- - img [ref=e81]
- - generic [ref=e85]:
- - img [ref=e86]
- - text: Company
- - heading "Post a Job" [level=3] [ref=e89]
- - paragraph [ref=e90]: Create verified job openings and find the right talent faster.
- - link "Explore Company" [ref=e91] [cursor=pointer]:
- - /url: /roles/company
- - article [ref=e92]:
- - img "Apply for Jobs" [ref=e94]
- - generic [ref=e96]:
- - generic [ref=e97]:
- - img [ref=e99]
- - generic [ref=e102]:
- - img [ref=e103]
- - text: Job Seeker
- - heading "Apply for Jobs" [level=3] [ref=e106]
- - paragraph [ref=e107]: Build your profile and apply to approved opportunities quickly.
- - link "Explore Job Seeker" [ref=e108] [cursor=pointer]:
- - /url: /roles/job-seeker
- - article [ref=e109]:
- - img "Hire a Professional" [ref=e111]
- - generic [ref=e113]:
- - generic [ref=e114]:
- - img [ref=e116]
- - generic [ref=e121]:
- - img [ref=e122]
- - text: Customer
- - heading "Hire a Professional" [level=3] [ref=e125]
- - paragraph [ref=e126]: Post your requirement and discover trusted specialists.
- - link "Explore Customer" [ref=e127] [cursor=pointer]:
- - /url: /roles/customer
- - article [ref=e128]:
- - img "Join as Professional" [ref=e130]
- - generic [ref=e132]:
- - generic [ref=e133]:
- - img [ref=e135]
- - generic [ref=e139]:
- - img [ref=e140]
- - text: Professional
- - heading "Join as Professional" [level=3] [ref=e143]
- - paragraph [ref=e144]: Create a trusted profile and grow through verified demand.
- - link "Explore Professionals" [ref=e145] [cursor=pointer]:
- - /url: /professionals
- - generic [ref=e147]:
- - generic [ref=e148]:
- - heading "Explore professional categories" [level=2] [ref=e150]
- - link "View all professionals" [ref=e151] [cursor=pointer]:
- - /url: /professionals
- - generic [ref=e152]:
- - button "All" [ref=e153] [cursor=pointer]
- - button "Creative" [ref=e154] [cursor=pointer]
- - button "Tech" [ref=e155] [cursor=pointer]
- - button "Education" [ref=e156] [cursor=pointer]
- - button "Wellness" [ref=e157] [cursor=pointer]
- - button "Events" [ref=e158] [cursor=pointer]
- - button "Marketing" [ref=e159] [cursor=pointer]
- - generic [ref=e160]:
- - article [ref=e161]:
- - img "Developer" [ref=e163]
- - generic [ref=e165]:
- - generic [ref=e167]: tech
- - heading "Developer" [level=3] [ref=e168]
- - paragraph [ref=e169]: Join Nxtgauge as a Developer and connect with customers and companies through a trust-first workflow.
- - generic [ref=e170]:
- - link "Explore Developer" [ref=e171] [cursor=pointer]:
- - /url: /professionals/developer
- - link "Join Developer" [ref=e172] [cursor=pointer]:
- - /url: /signup?intent=professional&role=DEVELOPER
- - article [ref=e173]:
- - img "Photographer" [ref=e175]
- - generic [ref=e177]:
- - generic [ref=e179]: creative
- - heading "Photographer" [level=3] [ref=e180]
- - paragraph [ref=e181]: Join as a Photographer and receive opportunities where trust and profile quality matter.
- - generic [ref=e182]:
- - link "Explore Photographer" [ref=e183] [cursor=pointer]:
- - /url: /professionals/photographer
- - link "Join Photographer" [ref=e184] [cursor=pointer]:
- - /url: /signup?intent=professional&role=PHOTOGRAPHER
- - article [ref=e185]:
- - img "Makeup Artist" [ref=e187]
- - generic [ref=e189]:
- - generic [ref=e191]: creative
- - heading "Makeup Artist" [level=3] [ref=e192]
- - paragraph [ref=e193]: Join Nxtgauge as a Makeup Artist and connect with verified requirements from customers and event planners.
- - generic [ref=e194]:
- - link "Explore Makeup Artist" [ref=e195] [cursor=pointer]:
- - /url: /professionals/makeup-artist
- - link "Join Makeup Artist" [ref=e196] [cursor=pointer]:
- - /url: /signup?intent=professional&role=MAKEUP_ARTIST
- - article [ref=e197]:
- - img "Tutor" [ref=e199]
- - generic [ref=e201]:
- - generic [ref=e203]: education
- - heading "Tutor" [level=3] [ref=e204]
- - paragraph [ref=e205]: Join as a Tutor to receive verified educational requirements and present your profile with confidence.
- - generic [ref=e206]:
- - link "Explore Tutor" [ref=e207] [cursor=pointer]:
- - /url: /professionals/tutor
- - link "Join Tutor" [ref=e208] [cursor=pointer]:
- - /url: /signup?intent=professional&role=TUTOR
- - article [ref=e209]:
- - img "Video Editor" [ref=e211]
- - generic [ref=e213]:
- - generic [ref=e215]: creative
- - heading "Video Editor" [level=3] [ref=e216]
- - paragraph [ref=e217]: Join as a Video Editor and connect with customers and companies through a review-based trust model.
- - generic [ref=e218]:
- - link "Explore Video Editor" [ref=e219] [cursor=pointer]:
- - /url: /professionals/video-editor
- - link "Join Video Editor" [ref=e220] [cursor=pointer]:
- - /url: /signup?intent=professional&role=VIDEO_EDITOR
- - article [ref=e221]:
- - img "Graphic Designer" [ref=e223]
- - generic [ref=e225]:
- - generic [ref=e227]: creative
- - heading "Graphic Designer" [level=3] [ref=e228]
- - paragraph [ref=e229]: Join as a Graphic Designer to connect with verified requirements and role-relevant opportunities.
- - generic [ref=e230]:
- - link "Explore Graphic Designer" [ref=e231] [cursor=pointer]:
- - /url: /professionals/graphic-designer
- - link "Join Graphic Designer" [ref=e232] [cursor=pointer]:
- - /url: /signup?intent=professional&role=GRAPHIC_DESIGNER
- - article [ref=e233]:
- - img "Social Media Manager" [ref=e235]
- - generic [ref=e237]:
- - generic [ref=e239]: marketing
- - heading "Social Media Manager" [level=3] [ref=e240]
- - paragraph [ref=e241]: Join as a Social Media Manager and grow with trusted customer and company requirements.
- - generic [ref=e242]:
- - link "Explore Social Media Manager" [ref=e243] [cursor=pointer]:
- - /url: /professionals/social-media-manager
- - link "Join Social Media Manager" [ref=e244] [cursor=pointer]:
- - /url: /signup?intent=professional&role=SOCIAL_MEDIA_MANAGER
- - article [ref=e245]:
- - img "Fitness Trainer" [ref=e247]
- - generic [ref=e249]:
- - generic [ref=e251]: wellness
- - heading "Fitness Trainer" [level=3] [ref=e252]
- - paragraph [ref=e253]: Join as a Fitness Trainer and grow through profile-led visibility in a trust-first marketplace.
- - generic [ref=e254]:
- - link "Explore Fitness Trainer" [ref=e255] [cursor=pointer]:
- - /url: /professionals/fitness-trainer
- - link "Join Fitness Trainer" [ref=e256] [cursor=pointer]:
- - /url: /signup?intent=professional&role=FITNESS_TRAINER
- - generic [ref=e258]:
- - generic [ref=e259]:
- - paragraph [ref=e260]: Why Nxtgauge
- - heading "Trust, approvals, and better matching in one flow." [level=2] [ref=e261]
- - generic [ref=e262]:
- - article [ref=e263]:
- - generic [ref=e265]:
- - paragraph [ref=e266]: Capability 1 of 6
- - img [ref=e268]
- - heading "Verified profiles & businesses" [level=3] [ref=e271]
- - paragraph [ref=e272]: Identity and profile checks reduce fake submissions and spam.
- - generic [ref=e274]:
- - button "Show Verified profiles & businesses" [ref=e275]:
- - img [ref=e276]
- - button "Show Approval-based quality (24-48 hours)" [ref=e279]:
- - img [ref=e280]
- - button "Show Smart matching using tags/skills" [ref=e282]:
- - img [ref=e283]
- - button "Show Focused discovery with filters" [ref=e285]:
- - img [ref=e286]
- - button "Show Controlled contact visibility" [ref=e289]:
- - img [ref=e290]
- - button "Show Notifications & updates" [ref=e293]:
- - img [ref=e294]
- - generic [ref=e297]:
- - button "Previous slide" [ref=e298]: ◀
- - button "Next slide" [ref=e299]: ▶
- - generic [ref=e301]:
- - generic [ref=e302]:
- - paragraph [ref=e303]: How it works
- - heading "Clear journey, zero confusion" [level=2] [ref=e304]
- - article [ref=e305]:
- - generic [ref=e306]:
- - img "Customers role" [ref=e308]
- - generic [ref=e309]:
- - paragraph [ref=e310]: Customers
- - heading "Hire trusted professionals with less noise" [level=3] [ref=e311]
- - paragraph [ref=e312]: Post requirements, pass review checks, and receive better-fit responses.
- - generic [ref=e313]:
- - paragraph [ref=e314]: Step Flow
- - generic [ref=e315]:
- - generic [ref=e316]:
- - generic [ref=e317]: "1"
- - generic [ref=e318]:
- - heading "Create account" [level=4] [ref=e319]
- - paragraph [ref=e320]: Sign up quickly with customer intent.
- - generic [ref=e321]:
- - generic [ref=e322]: "2"
- - generic [ref=e323]:
- - heading "Share requirement" [level=4] [ref=e324]
- - paragraph [ref=e325]: Add budget, scope, and timing.
- - generic [ref=e326]:
- - generic [ref=e327]: "3"
- - generic [ref=e328]:
- - heading "Review and verify" [level=4] [ref=e329]
- - paragraph [ref=e330]: Quality checks run before visibility.
- - generic [ref=e331]:
- - generic [ref=e332]: "4"
- - generic [ref=e333]:
- - heading "Track responses" [level=4] [ref=e334]
- - paragraph [ref=e335]: Monitor replies and status updates.
- - generic [ref=e337]:
- - button "Previous role" [ref=e338]: ←
- - button "Next role" [ref=e339]: →
- - generic [ref=e346]:
- - heading "Frequently asked questions" [level=2] [ref=e347]
- - paragraph [ref=e348]: Quick answers before you create your account.
- - generic [ref=e349]:
- - article [ref=e350]:
- - button "What is Nxtgauge? ⌄" [ref=e351] [cursor=pointer]:
- - generic [ref=e352]: What is Nxtgauge?
- - generic [ref=e353]: ⌄
- - paragraph [ref=e354]: Nxtgauge connects customers, professionals, companies, and job seekers in one trusted platform.
- - article [ref=e355]:
- - button "Who can join Nxtgauge? ⌄" [ref=e356] [cursor=pointer]:
- - generic [ref=e357]: Who can join Nxtgauge?
- - generic [ref=e358]: ⌄
- - article [ref=e359]:
- - button "Why does Nxtgauge require verification? ⌄" [ref=e360] [cursor=pointer]:
- - generic [ref=e361]: Why does Nxtgauge require verification?
- - generic [ref=e362]: ⌄
- - article [ref=e363]:
- - button "How long does account approval take? ⌄" [ref=e364] [cursor=pointer]:
- - generic [ref=e365]: How long does account approval take?
- - generic [ref=e366]: ⌄
- - article [ref=e367]:
- - button "Do I need to choose my role during signup? ⌄" [ref=e368] [cursor=pointer]:
- - generic [ref=e369]: Do I need to choose my role during signup?
- - generic [ref=e370]: ⌄
- - generic [ref=e372]:
- - generic [ref=e373]:
- - paragraph [ref=e374]: Quick Actions
- - heading "Start with one account. Choose your path after." [level=2] [ref=e375]
- - paragraph [ref=e376]: Hire, post, apply, or join as a professional from stable role-specific pages.
- - generic [ref=e377]:
- - link "Get Started" [ref=e378] [cursor=pointer]:
- - /url: /signup/customer
- - link "Explore Professionals" [ref=e379] [cursor=pointer]:
- - /url: /professionals
- - link "Post a Job" [ref=e380] [cursor=pointer]:
- - /url: /signup/company
- - generic [ref=e382]:
- - img "NXTGAUGE" [ref=e383]
- - paragraph [ref=e384]: © 2026 Nxtgauge. All rights reserved.
- - generic [ref=e385]:
- - link "Professionals" [ref=e386] [cursor=pointer]:
- - /url: /professionals
- - link "Terms" [ref=e387] [cursor=pointer]:
- - /url: /terms
- - link "Privacy" [ref=e388] [cursor=pointer]:
- - /url: /privacy
- - link "Help Center" [ref=e389] [cursor=pointer]:
- - /url: /help-center
- - button "AI Assistant" [ref=e390] [cursor=pointer]:
- - img [ref=e391]
-```
-
-# Test source
-
-```ts
- 1 | import { test, expect } from "@playwright/test";
- 2 | import AxeBuilder from "@axe-core/playwright";
- 3 |
- 4 | test.describe("Public Frontend Accessibility", () => {
- 5 | test("homepage should have no accessibility violations", async ({ page }) => {
- 6 | await page.goto("/");
- 7 | const results = await new AxeBuilder({ page }).analyze();
-> 8 | expect(results.violations).toEqual([]);
- | ^ Error: expect(received).toEqual(expected) // deep equality
- 9 | });
- 10 |
- 11 | test("professionals listing page should be accessible", async ({ page }) => {
- 12 | await page.goto("/professionals");
- 13 | const results = await new AxeBuilder({ page }).analyze();
- 14 | expect(results.violations).toEqual([]);
- 15 | });
- 16 | });
- 17 |
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/8b0ba6a85698d00d26816f25ffc2016d673ee525.md b/playwright-reports/html/data/8b0ba6a85698d00d26816f25ffc2016d673ee525.md
deleted file mode 100644
index ae74116..0000000
--- a/playwright-reports/html/data/8b0ba6a85698d00d26816f25ffc2016d673ee525.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: api.spec.ts >> AI API Endpoints >> Company AI - Generate Job Field >> POST /ai/generate-job-field returns generated content
-- Location: tests/e2e/api.spec.ts:41:5
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 65
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/93d0ae7967a30d546d9ef9a30b8d0717323a046b.md b/playwright-reports/html/data/93d0ae7967a30d546d9ef9a30b8d0717323a046b.md
deleted file mode 100644
index b0dafbd..0000000
--- a/playwright-reports/html/data/93d0ae7967a30d546d9ef9a30b8d0717323a046b.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: api.spec.ts >> Auth API Endpoints >> POST /auth/login rate limits after too many attempts
-- Location: tests/e2e/api.spec.ts:215:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 65
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/944a1c0a201049549ee4d1defb34c36d1928b32e.md b/playwright-reports/html/data/944a1c0a201049549ee4d1defb34c36d1928b32e.md
deleted file mode 100644
index a06887a..0000000
--- a/playwright-reports/html/data/944a1c0a201049549ee4d1defb34c36d1928b32e.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: api.spec.ts >> Auth API Endpoints >> POST /auth/login returns token for valid credentials
-- Location: tests/e2e/api.spec.ts:182:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 61
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/9b6f5347abef16bfd786a8e0614f285c3fa04de8.md b/playwright-reports/html/data/9b6f5347abef16bfd786a8e0614f285c3fa04de8.md
deleted file mode 100644
index 3283245..0000000
--- a/playwright-reports/html/data/9b6f5347abef16bfd786a8e0614f285c3fa04de8.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Token Handling >> Expired token is rejected
-- Location: tests/e2e/security.spec.ts:262:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/ai/usage
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoxNjAwMDAwMDAwfQ.dummysignature
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/9c4de630bf48b7ece7d902d260a13d9db3ab9ff6.md b/playwright-reports/html/data/9c4de630bf48b7ece7d902d260a13d9db3ab9ff6.md
deleted file mode 100644
index ab9d947..0000000
--- a/playwright-reports/html/data/9c4de630bf48b7ece7d902d260a13d9db3ab9ff6.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: api.spec.ts >> AI API Endpoints >> Company AI - Generate Job Field >> POST /ai/generate-job-field rate limits after daily quota
-- Location: tests/e2e/api.spec.ts:91:5
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 65
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/9d584d87128a07c6303af40cc2f3bb1e1be01989.md b/playwright-reports/html/data/9d584d87128a07c6303af40cc2f3bb1e1be01989.md
deleted file mode 100644
index 4cb2d4d..0000000
--- a/playwright-reports/html/data/9d584d87128a07c6303af40cc2f3bb1e1be01989.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Authentication >> JWT token is not returned for invalid credentials
-- Location: tests/e2e/security.spec.ts:6:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 62
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/a019d07c0d87598717d7e27edf94737ba8c29e92.md b/playwright-reports/html/data/a019d07c0d87598717d7e27edf94737ba8c29e92.md
deleted file mode 100644
index 4eeb42a..0000000
--- a/playwright-reports/html/data/a019d07c0d87598717d7e27edf94737ba8c29e92.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: api.spec.ts >> AI API Endpoints >> AI Usage Endpoint >> GET /ai/usage returns usage stats for company
-- Location: tests/e2e/api.spec.ts:144:5
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 65
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/a414f4178718fedadb56c714fa10233bbef71c51.png b/playwright-reports/html/data/a414f4178718fedadb56c714fa10233bbef71c51.png
deleted file mode 100644
index 70459e9..0000000
Binary files a/playwright-reports/html/data/a414f4178718fedadb56c714fa10233bbef71c51.png and /dev/null differ
diff --git a/playwright-reports/html/data/ad1c0adc3acdce2b311e2e7b21e93ad2233f1d94.md b/playwright-reports/html/data/ad1c0adc3acdce2b311e2e7b21e93ad2233f1d94.md
deleted file mode 100644
index 68a7506..0000000
--- a/playwright-reports/html/data/ad1c0adc3acdce2b311e2e7b21e93ad2233f1d94.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Input Validation >> Very long input is handled without crash
-- Location: tests/e2e/security.spec.ts:175:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 20038
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/ad5ed732feb82425e8df8acb2002071a774c0a11.md b/playwright-reports/html/data/ad5ed732feb82425e8df8acb2002071a774c0a11.md
deleted file mode 100644
index 75551af..0000000
--- a/playwright-reports/html/data/ad5ed732feb82425e8df8acb2002071a774c0a11.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Input Validation >> SQL injection in login email is handled safely
-- Location: tests/e2e/security.spec.ts:149:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 45
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/b4fa704806a42ae5abf765b58b255a2799760289.md b/playwright-reports/html/data/b4fa704806a42ae5abf765b58b255a2799760289.md
deleted file mode 100644
index 498a1b2..0000000
--- a/playwright-reports/html/data/b4fa704806a42ae5abf765b58b255a2799760289.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: api.spec.ts >> Auth API Endpoints >> POST /auth/login returns 401 for invalid credentials
-- Location: tests/e2e/api.spec.ts:202:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 58
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/b983a333ccc99199f8dc359e5f80e14e55037db0.md b/playwright-reports/html/data/b983a333ccc99199f8dc359e5f80e14e55037db0.md
deleted file mode 100644
index 138fc8c..0000000
--- a/playwright-reports/html/data/b983a333ccc99199f8dc359e5f80e14e55037db0.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: api.spec.ts >> Gateway API >> Gateway returns 404 for unknown routes
-- Location: tests/e2e/api.spec.ts:255:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/nonexistent-route
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/bf1a7466d9c8bde95533d72167cb2b1c6f95818e.md b/playwright-reports/html/data/bf1a7466d9c8bde95533d72167cb2b1c6f95818e.md
deleted file mode 100644
index 6cb1ecc..0000000
--- a/playwright-reports/html/data/bf1a7466d9c8bde95533d72167cb2b1c6f95818e.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Input Validation >> Very long input is handled without crash
-- Location: tests/e2e/security.spec.ts:175:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 20038
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/c24b23b0edd98a62d69666129d18c11f70d2a8ef.md b/playwright-reports/html/data/c24b23b0edd98a62d69666129d18c11f70d2a8ef.md
deleted file mode 100644
index cf75666..0000000
--- a/playwright-reports/html/data/c24b23b0edd98a62d69666129d18c11f70d2a8ef.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Token Handling >> Expired token is rejected
-- Location: tests/e2e/security.spec.ts:262:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/ai/usage
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoxNjAwMDAwMDAwfQ.dummysignature
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/c61f20f315397a51e6d5ef9e7fe853d01daf5740.webm b/playwright-reports/html/data/c61f20f315397a51e6d5ef9e7fe853d01daf5740.webm
deleted file mode 100644
index 2fb4edc..0000000
Binary files a/playwright-reports/html/data/c61f20f315397a51e6d5ef9e7fe853d01daf5740.webm and /dev/null differ
diff --git a/playwright-reports/html/data/d1c052738280b98b85a822b1c24e89000f917263.md b/playwright-reports/html/data/d1c052738280b98b85a822b1c24e89000f917263.md
deleted file mode 100644
index 1b8fd5f..0000000
--- a/playwright-reports/html/data/d1c052738280b98b85a822b1c24e89000f917263.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Rate Limiting >> AI endpoints rate limit after daily quota exceeded
-- Location: tests/e2e/security.spec.ts:108:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 65
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/d3a2b8d6fd240feebf8dd288c7ab54480c4f6c3c.md b/playwright-reports/html/data/d3a2b8d6fd240feebf8dd288c7ab54480c4f6c3c.md
deleted file mode 100644
index dd94e67..0000000
--- a/playwright-reports/html/data/d3a2b8d6fd240feebf8dd288c7ab54480c4f6c3c.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Authentication >> Protected endpoint rejects request without token
-- Location: tests/e2e/security.spec.ts:42:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/ai/usage
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/d4abb98a0fa953a2a1c0a620b282a6b485b14ffa.png b/playwright-reports/html/data/d4abb98a0fa953a2a1c0a620b282a6b485b14ffa.png
deleted file mode 100644
index bdb2da6..0000000
Binary files a/playwright-reports/html/data/d4abb98a0fa953a2a1c0a620b282a6b485b14ffa.png and /dev/null differ
diff --git a/playwright-reports/html/data/d5e2143fadeeb80a2ba6a13b44dc2f885bed52e8.webm b/playwright-reports/html/data/d5e2143fadeeb80a2ba6a13b44dc2f885bed52e8.webm
deleted file mode 100644
index efb3991..0000000
Binary files a/playwright-reports/html/data/d5e2143fadeeb80a2ba6a13b44dc2f885bed52e8.webm and /dev/null differ
diff --git a/playwright-reports/html/data/dbe7713931491e0cd0e13e87e054626096f94b42.md b/playwright-reports/html/data/dbe7713931491e0cd0e13e87e054626096f94b42.md
deleted file mode 100644
index f31f6b3..0000000
--- a/playwright-reports/html/data/dbe7713931491e0cd0e13e87e054626096f94b42.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Response Headers >> API error responses do not expose server internals
-- Location: tests/e2e/security.spec.ts:248:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/api/nonexistent
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/e2b61759d862ca7a398cda1bc6334e81853f0226.md b/playwright-reports/html/data/e2b61759d862ca7a398cda1bc6334e81853f0226.md
deleted file mode 100644
index 9de40b9..0000000
--- a/playwright-reports/html/data/e2b61759d862ca7a398cda1bc6334e81853f0226.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Input Validation >> SQL injection in login email is handled safely
-- Location: tests/e2e/security.spec.ts:149:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 45
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/ed24922c377dc98065c87c05ed7096fb5e1a16c2.md b/playwright-reports/html/data/ed24922c377dc98065c87c05ed7096fb5e1a16c2.md
deleted file mode 100644
index 738028b..0000000
--- a/playwright-reports/html/data/ed24922c377dc98065c87c05ed7096fb5e1a16c2.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: api.spec.ts >> Auth API Endpoints >> POST /auth/login rate limits after too many attempts
-- Location: tests/e2e/api.spec.ts:215:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 65
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/edc2a5b47bfb1d71845769f50bcef7543b186b6a.md b/playwright-reports/html/data/edc2a5b47bfb1d71845769f50bcef7543b186b6a.md
deleted file mode 100644
index ce9f3f8..0000000
--- a/playwright-reports/html/data/edc2a5b47bfb1d71845769f50bcef7543b186b6a.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: api.spec.ts >> Gateway API >> Gateway routes /api/ai/* to users service
-- Location: tests/e2e/api.spec.ts:240:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/ai/usage
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - Authorization: Bearer dummy
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/edf29051f2ade785c8a5d06183288d2a993f4662.md b/playwright-reports/html/data/edf29051f2ade785c8a5d06183288d2a993f4662.md
deleted file mode 100644
index 1b2d773..0000000
--- a/playwright-reports/html/data/edf29051f2ade785c8a5d06183288d2a993f4662.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: api.spec.ts >> AI API Endpoints >> Company AI - Generate Job Field >> POST /ai/generate-job-field rate limits after daily quota
-- Location: tests/e2e/api.spec.ts:91:5
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 65
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/eebce2ea34e3cc55eb49412e03b7d1eae96af1de.md b/playwright-reports/html/data/eebce2ea34e3cc55eb49412e03b7d1eae96af1de.md
deleted file mode 100644
index c75aabc..0000000
--- a/playwright-reports/html/data/eebce2ea34e3cc55eb49412e03b7d1eae96af1de.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Authentication >> Login without email returns proper error
-- Location: tests/e2e/security.spec.ts:20:3
-
-# Error details
-
-```
-Error: apiRequestContext.post: connect ECONNREFUSED ::1:3000
-Call log:
- - → POST http://localhost:3000/api/auth/login
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - content-type: application/json
- - content-length: 27
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/f393c21e7b2fbfd80c17dbe7eac9aa52f30a10a7.md b/playwright-reports/html/data/f393c21e7b2fbfd80c17dbe7eac9aa52f30a10a7.md
deleted file mode 100644
index f9e8ffb..0000000
--- a/playwright-reports/html/data/f393c21e7b2fbfd80c17dbe7eac9aa52f30a10a7.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Authentication >> Protected endpoint rejects request with empty Bearer token
-- Location: tests/e2e/security.spec.ts:60:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/ai/usage
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - Authorization: Bearer
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/fa1a5cb77246728c47633b88397169ac791b4b2c.md b/playwright-reports/html/data/fa1a5cb77246728c47633b88397169ac791b4b2c.md
deleted file mode 100644
index e204c9f..0000000
--- a/playwright-reports/html/data/fa1a5cb77246728c47633b88397169ac791b4b2c.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Authentication >> Protected endpoint rejects request with empty Bearer token
-- Location: tests/e2e/security.spec.ts:60:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/ai/usage
- - user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
- - Authorization: Bearer
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/fbca0efdf5b201e7993e209c4d950fd21c7d2865.md b/playwright-reports/html/data/fbca0efdf5b201e7993e209c4d950fd21c7d2865.md
deleted file mode 100644
index d764e3c..0000000
--- a/playwright-reports/html/data/fbca0efdf5b201e7993e209c4d950fd21c7d2865.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: security.spec.ts >> Security - Response Headers >> API error responses do not expose server internals
-- Location: tests/e2e/security.spec.ts:248:3
-
-# Error details
-
-```
-Error: apiRequestContext.get: connect ECONNREFUSED ::1:3000
-Call log:
- - → GET http://localhost:3000/api/api/nonexistent
- - user-agent: Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.7778.96 Mobile Safari/537.36
- - accept: */*
- - accept-encoding: gzip,deflate,br
-
-```
\ No newline at end of file
diff --git a/playwright-reports/html/data/fe5c78020a2eaf74673c87f209438ff9cd840f1f.md b/playwright-reports/html/data/fe5c78020a2eaf74673c87f209438ff9cd840f1f.md
deleted file mode 100644
index 6467157..0000000
--- a/playwright-reports/html/data/fe5c78020a2eaf74673c87f209438ff9cd840f1f.md
+++ /dev/null
@@ -1,442 +0,0 @@
-# Instructions
-
-- Following Playwright test failed.
-- Explain why, be concise, respect Playwright best practices.
-- Provide a snippet of code with the fix, if possible.
-
-# Test info
-
-- Name: accessibility.spec.ts >> Public Frontend Accessibility >> homepage should have no accessibility violations
-- Location: tests/e2e/accessibility.spec.ts:5:3
-
-# Error details
-
-```
-Error: expect(received).toEqual(expected) // deep equality
-
-- Expected - 1
-+ Received + 58
-
-- Array []
-+ Array [
-+ Object {
-+ "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",
-+ "help": "Elements must meet minimum color contrast ratio thresholds",
-+ "helpUrl": "https://dequeuniversity.com/rules/axe/4.11/color-contrast?application=playwright",
-+ "id": "color-contrast",
-+ "impact": "serious",
-+ "nodes": Array [
-+ Object {
-+ "all": Array [],
-+ "any": Array [
-+ Object {
-+ "data": Object {
-+ "bgColor": "#fd6116",
-+ "contrastRatio": 3.04,
-+ "expectedContrastRatio": "4.5:1",
-+ "fgColor": "#ffffff",
-+ "fontSize": "10.5pt (14px)",
-+ "fontWeight": "normal",
-+ "messageKey": null,
-+ },
-+ "id": "color-contrast",
-+ "impact": "serious",
-+ "message": "Element has insufficient color contrast of 3.04 (foreground color: #ffffff, background color: #fd6116, font size: 10.5pt (14px), font weight: normal). Expected contrast ratio of 4.5:1",
-+ "relatedNodes": Array [
-+ Object {
-+ "html": "",
-+ "target": Array [
-+ "button[data-hk=\"00000001000001000000000a1080\"]",
-+ ],
-+ },
-+ ],
-+ },
-+ ],
-+ "failureSummary": "Fix any of the following:
-+ Element has insufficient color contrast of 3.04 (foreground color: #ffffff, background color: #fd6116, font size: 10.5pt (14px), font weight: normal). Expected contrast ratio of 4.5:1",
-+ "html": "",
-+ "impact": "serious",
-+ "none": Array [],
-+ "target": Array [
-+ "button[data-hk=\"00000001000001000000000a1080\"]",
-+ ],
-+ },
-+ ],
-+ "tags": Array [
-+ "cat.color",
-+ "wcag2aa",
-+ "wcag143",
-+ "TTv5",
-+ "TT13.c",
-+ "EN-301-549",
-+ "EN-9.1.4.3",
-+ "ACT",
-+ "RGAAv4",
-+ "RGAA-3.2.1",
-+ ],
-+ },
-+ ]
-```
-
-# Page snapshot
-
-```yaml
-- generic [ref=e2]:
- - main [ref=e3]:
- - generic:
- - generic:
- - generic:
- - img
- - generic:
- - img
- - generic:
- - img
- - generic:
- - img
- - generic:
- - img
- - generic [ref=e4]:
- - navigation [ref=e6]:
- - link "Nxtgauge home" [ref=e7] [cursor=pointer]:
- - /url: /
- - img "NXTGAUGE" [ref=e8]
- - generic [ref=e9]:
- - link "Home" [ref=e10] [cursor=pointer]:
- - /url: /
- - link "Professionals" [ref=e11] [cursor=pointer]:
- - /url: /professionals
- - link "About Us" [ref=e12] [cursor=pointer]:
- - /url: /about
- - link "Help Center" [ref=e13] [cursor=pointer]:
- - /url: /help-center
- - link "Contact Us" [ref=e14] [cursor=pointer]:
- - /url: /contact
- - link "Login" [ref=e16] [cursor=pointer]:
- - /url: /login
- - generic [ref=e18]:
- - generic [ref=e19]:
- - heading "Trusted hiring and opportunities, verified." [level=1] [ref=e20]
- - paragraph [ref=e21]: Hire trusted professionals, post verified jobs, and apply faster in one platform.
- - generic [ref=e22]:
- - link "Get Started" [ref=e23] [cursor=pointer]:
- - /url: /signup/customer
- - link "How It Works" [ref=e24] [cursor=pointer]:
- - /url: /#how-it-works
- - paragraph [ref=e25]: Most profile and listing reviews are completed within 24-48 hours.
- - region "Nxtgauge opportunity graph preview" [ref=e27]:
- - generic [ref=e28]:
- - generic [ref=e29]:
- - img [ref=e30]
- - generic [ref=e47]: Developer
- - generic [ref=e48]:
- - generic [ref=e49]:
- - generic [ref=e50]: ✓
- - generic [ref=e51]: Everything from Nxtgauge
- - paragraph [ref=e52]: Opportunity Workspace
- - generic [ref=e53]:
- - strong [ref=e54]: Verified Profiles
- - generic [ref=e55]: Trust layer ready
- - generic [ref=e56]:
- - strong [ref=e57]: Matched Opportunities
- - generic [ref=e58]: Priority queue
- - generic [ref=e59]:
- - strong [ref=e60]: Responses
- - generic [ref=e61]: Tracked in one flow
- - generic [ref=e62]:
- - strong [ref=e63]: Updates
- - generic [ref=e64]: Live status signals
- - paragraph [ref=e66]: Opportunities start scattered
- - generic [ref=e68]:
- - generic [ref=e70]:
- - heading "Who Nxtgauge is for" [level=2] [ref=e71]
- - paragraph [ref=e72]: Start with one account, then activate the journey you need.
- - generic [ref=e73]:
- - article [ref=e74]:
- - img "Post a Job" [ref=e76]
- - generic [ref=e78]:
- - generic [ref=e79]:
- - img [ref=e81]
- - generic [ref=e85]:
- - img [ref=e86]
- - text: Company
- - heading "Post a Job" [level=3] [ref=e89]
- - paragraph [ref=e90]: Create verified job openings and find the right talent faster.
- - link "Explore Company" [ref=e91] [cursor=pointer]:
- - /url: /roles/company
- - article [ref=e92]:
- - img "Apply for Jobs" [ref=e94]
- - generic [ref=e96]:
- - generic [ref=e97]:
- - img [ref=e99]
- - generic [ref=e102]:
- - img [ref=e103]
- - text: Job Seeker
- - heading "Apply for Jobs" [level=3] [ref=e106]
- - paragraph [ref=e107]: Build your profile and apply to approved opportunities quickly.
- - link "Explore Job Seeker" [ref=e108] [cursor=pointer]:
- - /url: /roles/job-seeker
- - article [ref=e109]:
- - img "Hire a Professional" [ref=e111]
- - generic [ref=e113]:
- - generic [ref=e114]:
- - img [ref=e116]
- - generic [ref=e121]:
- - img [ref=e122]
- - text: Customer
- - heading "Hire a Professional" [level=3] [ref=e125]
- - paragraph [ref=e126]: Post your requirement and discover trusted specialists.
- - link "Explore Customer" [ref=e127] [cursor=pointer]:
- - /url: /roles/customer
- - article [ref=e128]:
- - img "Join as Professional" [ref=e130]
- - generic [ref=e132]:
- - generic [ref=e133]:
- - img [ref=e135]
- - generic [ref=e139]:
- - img [ref=e140]
- - text: Professional
- - heading "Join as Professional" [level=3] [ref=e143]
- - paragraph [ref=e144]: Create a trusted profile and grow through verified demand.
- - link "Explore Professionals" [ref=e145] [cursor=pointer]:
- - /url: /professionals
- - generic [ref=e147]:
- - generic [ref=e148]:
- - heading "Explore professional categories" [level=2] [ref=e150]
- - link "View all professionals" [ref=e151] [cursor=pointer]:
- - /url: /professionals
- - generic [ref=e152]:
- - button "All" [ref=e153] [cursor=pointer]
- - button "Creative" [ref=e154] [cursor=pointer]
- - button "Tech" [ref=e155] [cursor=pointer]
- - button "Education" [ref=e156] [cursor=pointer]
- - button "Wellness" [ref=e157] [cursor=pointer]
- - button "Events" [ref=e158] [cursor=pointer]
- - button "Marketing" [ref=e159] [cursor=pointer]
- - generic [ref=e160]:
- - article [ref=e161]:
- - img "Developer" [ref=e163]
- - generic [ref=e165]:
- - generic [ref=e167]: tech
- - heading "Developer" [level=3] [ref=e168]
- - paragraph [ref=e169]: Join Nxtgauge as a Developer and connect with customers and companies through a trust-first workflow.
- - generic [ref=e170]:
- - link "Explore Developer" [ref=e171] [cursor=pointer]:
- - /url: /professionals/developer
- - link "Join Developer" [ref=e172] [cursor=pointer]:
- - /url: /signup?intent=professional&role=DEVELOPER
- - article [ref=e173]:
- - img "Photographer" [ref=e175]
- - generic [ref=e177]:
- - generic [ref=e179]: creative
- - heading "Photographer" [level=3] [ref=e180]
- - paragraph [ref=e181]: Join as a Photographer and receive opportunities where trust and profile quality matter.
- - generic [ref=e182]:
- - link "Explore Photographer" [ref=e183] [cursor=pointer]:
- - /url: /professionals/photographer
- - link "Join Photographer" [ref=e184] [cursor=pointer]:
- - /url: /signup?intent=professional&role=PHOTOGRAPHER
- - article [ref=e185]:
- - img "Makeup Artist" [ref=e187]
- - generic [ref=e189]:
- - generic [ref=e191]: creative
- - heading "Makeup Artist" [level=3] [ref=e192]
- - paragraph [ref=e193]: Join Nxtgauge as a Makeup Artist and connect with verified requirements from customers and event planners.
- - generic [ref=e194]:
- - link "Explore Makeup Artist" [ref=e195] [cursor=pointer]:
- - /url: /professionals/makeup-artist
- - link "Join Makeup Artist" [ref=e196] [cursor=pointer]:
- - /url: /signup?intent=professional&role=MAKEUP_ARTIST
- - article [ref=e197]:
- - img "Tutor" [ref=e199]
- - generic [ref=e201]:
- - generic [ref=e203]: education
- - heading "Tutor" [level=3] [ref=e204]
- - paragraph [ref=e205]: Join as a Tutor to receive verified educational requirements and present your profile with confidence.
- - generic [ref=e206]:
- - link "Explore Tutor" [ref=e207] [cursor=pointer]:
- - /url: /professionals/tutor
- - link "Join Tutor" [ref=e208] [cursor=pointer]:
- - /url: /signup?intent=professional&role=TUTOR
- - article [ref=e209]:
- - img "Video Editor" [ref=e211]
- - generic [ref=e213]:
- - generic [ref=e215]: creative
- - heading "Video Editor" [level=3] [ref=e216]
- - paragraph [ref=e217]: Join as a Video Editor and connect with customers and companies through a review-based trust model.
- - generic [ref=e218]:
- - link "Explore Video Editor" [ref=e219] [cursor=pointer]:
- - /url: /professionals/video-editor
- - link "Join Video Editor" [ref=e220] [cursor=pointer]:
- - /url: /signup?intent=professional&role=VIDEO_EDITOR
- - article [ref=e221]:
- - img "Graphic Designer" [ref=e223]
- - generic [ref=e225]:
- - generic [ref=e227]: creative
- - heading "Graphic Designer" [level=3] [ref=e228]
- - paragraph [ref=e229]: Join as a Graphic Designer to connect with verified requirements and role-relevant opportunities.
- - generic [ref=e230]:
- - link "Explore Graphic Designer" [ref=e231] [cursor=pointer]:
- - /url: /professionals/graphic-designer
- - link "Join Graphic Designer" [ref=e232] [cursor=pointer]:
- - /url: /signup?intent=professional&role=GRAPHIC_DESIGNER
- - article [ref=e233]:
- - img "Social Media Manager" [ref=e235]
- - generic [ref=e237]:
- - generic [ref=e239]: marketing
- - heading "Social Media Manager" [level=3] [ref=e240]
- - paragraph [ref=e241]: Join as a Social Media Manager and grow with trusted customer and company requirements.
- - generic [ref=e242]:
- - link "Explore Social Media Manager" [ref=e243] [cursor=pointer]:
- - /url: /professionals/social-media-manager
- - link "Join Social Media Manager" [ref=e244] [cursor=pointer]:
- - /url: /signup?intent=professional&role=SOCIAL_MEDIA_MANAGER
- - article [ref=e245]:
- - img "Fitness Trainer" [ref=e247]
- - generic [ref=e249]:
- - generic [ref=e251]: wellness
- - heading "Fitness Trainer" [level=3] [ref=e252]
- - paragraph [ref=e253]: Join as a Fitness Trainer and grow through profile-led visibility in a trust-first marketplace.
- - generic [ref=e254]:
- - link "Explore Fitness Trainer" [ref=e255] [cursor=pointer]:
- - /url: /professionals/fitness-trainer
- - link "Join Fitness Trainer" [ref=e256] [cursor=pointer]:
- - /url: /signup?intent=professional&role=FITNESS_TRAINER
- - generic [ref=e258]:
- - generic [ref=e259]:
- - paragraph [ref=e260]: Why Nxtgauge
- - heading "Trust, approvals, and better matching in one flow." [level=2] [ref=e261]
- - generic [ref=e262]:
- - article [ref=e263]:
- - generic [ref=e265]:
- - paragraph [ref=e266]: Capability 1 of 6
- - img [ref=e268]
- - heading "Verified profiles & businesses" [level=3] [ref=e271]
- - paragraph [ref=e272]: Identity and profile checks reduce fake submissions and spam.
- - generic [ref=e274]:
- - button "Show Verified profiles & businesses" [ref=e275]:
- - img [ref=e276]
- - button "Show Approval-based quality (24-48 hours)" [ref=e279]:
- - img [ref=e280]
- - button "Show Smart matching using tags/skills" [ref=e282]:
- - img [ref=e283]
- - button "Show Focused discovery with filters" [ref=e285]:
- - img [ref=e286]
- - button "Show Controlled contact visibility" [ref=e289]:
- - img [ref=e290]
- - button "Show Notifications & updates" [ref=e293]:
- - img [ref=e294]
- - generic [ref=e297]:
- - button "Previous slide" [ref=e298]: ◀
- - button "Next slide" [ref=e299]: ▶
- - generic [ref=e301]:
- - generic [ref=e302]:
- - paragraph [ref=e303]: How it works
- - heading "Clear journey, zero confusion" [level=2] [ref=e304]
- - article [ref=e305]:
- - generic [ref=e306]:
- - img "Customers role" [ref=e308]
- - generic [ref=e309]:
- - paragraph [ref=e310]: Customers
- - heading "Hire trusted professionals with less noise" [level=3] [ref=e311]
- - paragraph [ref=e312]: Post requirements, pass review checks, and receive better-fit responses.
- - generic [ref=e313]:
- - paragraph [ref=e314]: Step Flow
- - generic [ref=e315]:
- - generic [ref=e316]:
- - generic [ref=e317]: "1"
- - generic [ref=e318]:
- - heading "Create account" [level=4] [ref=e319]
- - paragraph [ref=e320]: Sign up quickly with customer intent.
- - generic [ref=e321]:
- - generic [ref=e322]: "2"
- - generic [ref=e323]:
- - heading "Share requirement" [level=4] [ref=e324]
- - paragraph [ref=e325]: Add budget, scope, and timing.
- - generic [ref=e326]:
- - generic [ref=e327]: "3"
- - generic [ref=e328]:
- - heading "Review and verify" [level=4] [ref=e329]
- - paragraph [ref=e330]: Quality checks run before visibility.
- - generic [ref=e331]:
- - generic [ref=e332]: "4"
- - generic [ref=e333]:
- - heading "Track responses" [level=4] [ref=e334]
- - paragraph [ref=e335]: Monitor replies and status updates.
- - generic [ref=e337]:
- - button "Previous role" [ref=e338]: ←
- - button "Next role" [ref=e339]: →
- - generic [ref=e346]:
- - heading "Frequently asked questions" [level=2] [ref=e347]
- - paragraph [ref=e348]: Quick answers before you create your account.
- - generic [ref=e349]:
- - article [ref=e350]:
- - button "What is Nxtgauge? ⌄" [ref=e351] [cursor=pointer]:
- - generic [ref=e352]: What is Nxtgauge?
- - generic [ref=e353]: ⌄
- - paragraph [ref=e354]: Nxtgauge connects customers, professionals, companies, and job seekers in one trusted platform.
- - article [ref=e355]:
- - button "Who can join Nxtgauge? ⌄" [ref=e356] [cursor=pointer]:
- - generic [ref=e357]: Who can join Nxtgauge?
- - generic [ref=e358]: ⌄
- - article [ref=e359]:
- - button "Why does Nxtgauge require verification? ⌄" [ref=e360] [cursor=pointer]:
- - generic [ref=e361]: Why does Nxtgauge require verification?
- - generic [ref=e362]: ⌄
- - article [ref=e363]:
- - button "How long does account approval take? ⌄" [ref=e364] [cursor=pointer]:
- - generic [ref=e365]: How long does account approval take?
- - generic [ref=e366]: ⌄
- - article [ref=e367]:
- - button "Do I need to choose my role during signup? ⌄" [ref=e368] [cursor=pointer]:
- - generic [ref=e369]: Do I need to choose my role during signup?
- - generic [ref=e370]: ⌄
- - generic [ref=e372]:
- - generic [ref=e373]:
- - paragraph [ref=e374]: Quick Actions
- - heading "Start with one account. Choose your path after." [level=2] [ref=e375]
- - paragraph [ref=e376]: Hire, post, apply, or join as a professional from stable role-specific pages.
- - generic [ref=e377]:
- - link "Get Started" [ref=e378] [cursor=pointer]:
- - /url: /signup/customer
- - link "Explore Professionals" [ref=e379] [cursor=pointer]:
- - /url: /professionals
- - link "Post a Job" [ref=e380] [cursor=pointer]:
- - /url: /signup/company
- - generic [ref=e382]:
- - img "NXTGAUGE" [ref=e383]
- - paragraph [ref=e384]: © 2026 Nxtgauge. All rights reserved.
- - generic [ref=e385]:
- - link "Professionals" [ref=e386] [cursor=pointer]:
- - /url: /professionals
- - link "Terms" [ref=e387] [cursor=pointer]:
- - /url: /terms
- - link "Privacy" [ref=e388] [cursor=pointer]:
- - /url: /privacy
- - link "Help Center" [ref=e389] [cursor=pointer]:
- - /url: /help-center
- - button "AI Assistant" [ref=e390] [cursor=pointer]:
- - img [ref=e391]
-```
-
-# Test source
-
-```ts
- 1 | import { test, expect } from "@playwright/test";
- 2 | import AxeBuilder from "@axe-core/playwright";
- 3 |
- 4 | test.describe("Public Frontend Accessibility", () => {
- 5 | test("homepage should have no accessibility violations", async ({ page }) => {
- 6 | await page.goto("/");
- 7 | const results = await new AxeBuilder({ page }).analyze();
-> 8 | expect(results.violations).toEqual([]);
- | ^ Error: expect(received).toEqual(expected) // deep equality
- 9 | });
- 10 |
- 11 | test("professionals listing page should be accessible", async ({ page }) => {
- 12 | await page.goto("/professionals");
- 13 | const results = await new AxeBuilder({ page }).analyze();
- 14 | expect(results.violations).toEqual([]);
- 15 | });
- 16 | });
- 17 |
-```
\ No newline at end of file
diff --git a/playwright-reports/html/index.html b/playwright-reports/html/index.html
deleted file mode 100644
index a312dce..0000000
--- a/playwright-reports/html/index.html
+++ /dev/null
@@ -1,90 +0,0 @@
-
-
-
-
-
-
-
-
- Playwright Test Report
-
-
-
-
-
-
-
-data:application/zip;base64,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
\ No newline at end of file
diff --git a/tests/e2e/api.spec.ts b/tests/e2e/api.spec.ts
index f706c01..e638fa8 100644
--- a/tests/e2e/api.spec.ts
+++ b/tests/e2e/api.spec.ts
@@ -1,15 +1,19 @@
import { test, expect, request } from "@playwright/test";
+import { solveCaptcha } from "./helpers/captcha";
-const API_BASE = process.env.TEST_ENV === 'production'
- ? "https://test111.nxtgauge.com/api"
+const API_BASE = process.env.TEST_ENV === 'production'
+ ? "https://test111.nxtgauge.com/api"
: "http://localhost:3000/api";
async function getAuthToken(): Promise {
const ctx = await request.newContext();
+ const { captcha_id, captcha_answer } = await solveCaptcha();
const res = await ctx.post(`${API_BASE}/auth/login`, {
data: {
email: "testtutora2026@example.com",
password: "Test1234!",
+ captcha_id,
+ captcha_answer,
},
});
if (!res.ok()) return null;
@@ -19,10 +23,13 @@ async function getAuthToken(): Promise {
async function getCompanyAuthToken(): Promise {
const ctx = await request.newContext();
+ const { captcha_id, captcha_answer } = await solveCaptcha();
const res = await ctx.post(`${API_BASE}/auth/login`, {
data: {
email: "testcompany@example.com",
password: "TestPassword123!",
+ captcha_id,
+ captcha_answer,
},
});
if (!res.ok()) return null;
@@ -183,10 +190,13 @@ test.describe("AI API Endpoints", () => {
test.describe("Auth API Endpoints", () => {
test("POST /auth/login returns token for valid credentials", async () => {
const ctx = await request.newContext();
+ const { captcha_id, captcha_answer } = await solveCaptcha();
const res = await ctx.post(`${API_BASE}/auth/login`, {
data: {
email: "testtutora2026@example.com",
password: "Test1234!",
+ captcha_id,
+ captcha_answer,
},
});
@@ -203,10 +213,13 @@ test.describe("Auth API Endpoints", () => {
test("POST /auth/login returns 401 for invalid credentials", async () => {
const ctx = await request.newContext();
+ const { captcha_id, captcha_answer } = await solveCaptcha();
const res = await ctx.post(`${API_BASE}/auth/login`, {
data: {
email: "invalid@example.com",
password: "wrongpassword",
+ captcha_id,
+ captcha_answer,
},
});
@@ -217,18 +230,24 @@ test.describe("Auth API Endpoints", () => {
test("POST /auth/login rate limits after too many attempts", async () => {
const ctx = await request.newContext();
for (let i = 0; i < 6; i++) {
+ const { captcha_id, captcha_answer } = await solveCaptcha();
await ctx.post(`${API_BASE}/auth/login`, {
data: {
email: "testtutora2026@example.com",
password: "wrongpassword",
+ captcha_id,
+ captcha_answer,
},
});
}
+ const { captcha_id, captcha_answer } = await solveCaptcha();
const res = await ctx.post(`${API_BASE}/auth/login`, {
data: {
email: "testtutora2026@example.com",
password: "Test1234!",
+ captcha_id,
+ captcha_answer,
},
});
diff --git a/tests/e2e/company-admin-e2e.spec.ts b/tests/e2e/company-admin-e2e.spec.ts
index 89bdf2e..cb215c1 100644
--- a/tests/e2e/company-admin-e2e.spec.ts
+++ b/tests/e2e/company-admin-e2e.spec.ts
@@ -1,9 +1,11 @@
import { test, expect, chromium, BrowserContext, Page } from "@playwright/test";
import { randomUUID } from "crypto";
-import { execSync } from "child_process";
import * as fs from "fs";
+import * as path from "path";
+import { apiRegister, apiVerifyEmail, apiLogin } from "./helpers/auth-flow";
+import { APP_BASE_URL } from "./helpers/env";
-const SCREENSHOT_DIR = "/Users/ashwin/workspace/nxtgauge-frontend-solid/test-results/company-admin-e2e";
+const SCREENSHOT_DIR = path.join(__dirname, "..", "..", "test-results", "company-admin-e2e");
if (!fs.existsSync(SCREENSHOT_DIR)) {
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
@@ -20,63 +22,30 @@ interface TestUser {
companyName?: string;
}
-async function getOTPFromRedis(userId: string): Promise {
- try {
- const plainKey = `otp:plain:${userId}`;
- let otpCode = execSync(`redis-cli GET "${plainKey}"`, { encoding: "utf8" }).trim();
- if (otpCode && otpCode.length >= 4) return otpCode;
-
- const keys = execSync("redis-cli KEYS 'otp:code:*'", { encoding: "utf8" })
- .trim().split("\n").filter(Boolean);
- for (const k of keys) {
- const v = execSync(`redis-cli GET "${k}"`, { encoding: "utf8" }).trim();
- if (v === userId) {
- otpCode = k.replace("otp:code:", "");
- return otpCode;
- }
- }
- return null;
- } catch {
- return null;
- }
-}
-
async function registerUser(user: TestUser): Promise {
console.log(`\n📝 Registering ${user.intent} via API...`);
- const regResponse = await fetch("http://localhost:9100/api/auth/register", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(user),
+ const regData = await apiRegister({
+ email: user.email,
+ password: user.password,
+ first_name: user.firstName,
+ last_name: user.lastName,
+ intent: user.intent,
});
- const regData = await regResponse.json();
if (!regData.user_id) throw new Error(`Registration failed: ${JSON.stringify(regData)}`);
user.userId = regData.user_id;
console.log(` ✅ Registered, user_id: ${user.userId}`);
- // Get OTP from Redis
+ // Get OTP from Redis + verify via API
await new Promise(r => setTimeout(r, 500));
- const otpCode = await getOTPFromRedis(user.userId!);
- if (!otpCode) throw new Error("Could not get OTP from Redis");
- console.log(` ✅ OTP retrieved: ${otpCode}`);
-
- // Verify OTP
- const verifyResponse = await fetch("http://localhost:9100/api/auth/verify-email", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ user_id: user.userId, otp: otpCode })
- });
- if (!verifyResponse.ok) throw new Error("OTP verification failed");
+ const verified = await apiVerifyEmail(user.email, user.userId!);
+ if (!verified) throw new Error("OTP verification failed");
console.log(` ✅ OTP verified!`);
// Login
- const loginResponse = await fetch("http://localhost:9100/api/auth/login", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ email: user.email, password: user.password })
- });
- const loginData = await loginResponse.json();
- if (!loginData.access_token) throw new Error("Login failed");
- user.accessToken = loginData.access_token;
+ const loginData = await apiLogin(user.email, user.password);
+ const accessToken = loginData?.access_token || "";
+ if (!accessToken) throw new Error("Login failed");
+ user.accessToken = accessToken;
console.log(` ✅ Logged in, token length: ${user.accessToken!.length}`);
return user;
@@ -130,8 +99,8 @@ test.describe("Company E2E Flow with Admin Verification", () => {
await registerUser(companyUser);
// ==================== BROWSER SETUP ====================
- const browser = await chromium.launch({ headless: false, slowMo: 30 });
- const context = await browser.newContext({ viewport: { width: 1400, height: 900 } });
+ const browser = await chromium.launch({ headless: true });
+ const context = await browser.newContext({ baseURL: APP_BASE_URL, viewport: { width: 1400, height: 900 } });
// ==================== COMPANY FRONTEND FLOW ====================
console.log("\n" + "=".repeat(60));
@@ -142,7 +111,7 @@ test.describe("Company E2E Flow with Admin Verification", () => {
await setupFrontendAuth(companyPage, companyUser);
// Navigate to dashboard
- await companyPage.goto("http://localhost:3000/dashboard?role=COMPANY", { waitUntil: "networkidle", timeout: 15000 });
+ await companyPage.goto("/dashboard?role=COMPANY", { waitUntil: "networkidle", timeout: 15000 });
await new Promise(r => setTimeout(r, 3000));
await takeScreenshot(companyPage, "01_company_dashboard");
console.log(" ✅ Company dashboard loaded");
diff --git a/tests/e2e/company-complete-e2e-with-admin-approval.spec.ts b/tests/e2e/company-complete-e2e-with-admin-approval.spec.ts
index bd71187..caa669e 100644
--- a/tests/e2e/company-complete-e2e-with-admin-approval.spec.ts
+++ b/tests/e2e/company-complete-e2e-with-admin-approval.spec.ts
@@ -1,9 +1,11 @@
import { test, expect, chromium, BrowserContext, Page } from "@playwright/test";
import { randomUUID } from "crypto";
-import { execSync } from "child_process";
import * as fs from "fs";
+import * as path from "path";
+import { apiRegister, apiVerifyEmail, apiLogin } from "./helpers/auth-flow";
+import { APP_BASE_URL, API_BASE } from "./helpers/env";
-const SCREENSHOT_DIR = "/Users/ashwin/workspace/nxtgauge-frontend-solid/test-results/company-complete-e2e";
+const SCREENSHOT_DIR = path.join(__dirname, "..", "..", "test-results", "company-complete-e2e");
if (!fs.existsSync(SCREENSHOT_DIR)) {
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
@@ -20,63 +22,30 @@ interface TestUser {
companyName?: string;
}
-async function getOTPFromRedis(userId: string): Promise {
- try {
- const plainKey = `otp:plain:${userId}`;
- let otpCode = execSync(`redis-cli GET "${plainKey}"`, { encoding: "utf8" }).trim();
- if (otpCode && otpCode.length >= 4) return otpCode;
-
- const keys = execSync("redis-cli KEYS 'otp:code:*'", { encoding: "utf8" })
- .trim().split("\n").filter(Boolean);
- for (const k of keys) {
- const v = execSync(`redis-cli GET "${k}"`, { encoding: "utf8" }).trim();
- if (v === userId) {
- otpCode = k.replace("otp:code:", "");
- return otpCode;
- }
- }
- return null;
- } catch {
- return null;
- }
-}
-
async function registerUser(user: TestUser): Promise {
console.log(`\n📝 Registering ${user.intent} via API...`);
- const regResponse = await fetch("http://localhost:9100/api/auth/register", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(user),
+ const regData = await apiRegister({
+ email: user.email,
+ password: user.password,
+ first_name: user.firstName,
+ last_name: user.lastName,
+ intent: user.intent,
});
- const regData = await regResponse.json();
if (!regData.user_id) throw new Error(`Registration failed: ${JSON.stringify(regData)}`);
user.userId = regData.user_id;
console.log(` ✅ Registered, user_id: ${user.userId}`);
- // Get OTP from Redis
+ // Get OTP from Redis + verify via API
await new Promise(r => setTimeout(r, 500));
- const otpCode = await getOTPFromRedis(user.userId!);
- if (!otpCode) throw new Error("Could not get OTP from Redis");
- console.log(` ✅ OTP retrieved: ${otpCode}`);
-
- // Verify OTP
- const verifyResponse = await fetch("http://localhost:9100/api/auth/verify-email", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ user_id: user.userId, otp: otpCode })
- });
- if (!verifyResponse.ok) throw new Error("OTP verification failed");
+ const verified = await apiVerifyEmail(user.email, user.userId!);
+ if (!verified) throw new Error("OTP verification failed");
console.log(` ✅ OTP verified!`);
// Login
- const loginResponse = await fetch("http://localhost:9100/api/auth/login", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ email: user.email, password: user.password })
- });
- const loginData = await loginResponse.json();
- if (!loginData.access_token) throw new Error("Login failed");
- user.accessToken = loginData.access_token;
+ const loginData = await apiLogin(user.email, user.password);
+ const accessToken = loginData?.access_token || "";
+ if (!accessToken) throw new Error("Login failed");
+ user.accessToken = accessToken;
console.log(` ✅ Logged in, token length: ${user.accessToken!.length}`);
return user;
@@ -130,8 +99,8 @@ test.describe("Company Complete E2E with Admin Approval", () => {
await registerUser(companyUser);
// ==================== BROWSER SETUP ====================
- const browser = await chromium.launch({ headless: false, slowMo: 30 });
- const context = await browser.newContext({ viewport: { width: 1400, height: 900 } });
+ const browser = await chromium.launch({ headless: true });
+ const context = await browser.newContext({ baseURL: APP_BASE_URL, viewport: { width: 1400, height: 900 } });
// ==================== COMPANY FRONTEND FLOW ====================
console.log("\n" + "=".repeat(60));
@@ -142,7 +111,7 @@ test.describe("Company Complete E2E with Admin Approval", () => {
await setupFrontendAuth(companyPage, companyUser);
// Navigate to dashboard
- await companyPage.goto("http://localhost:3000/dashboard?role=COMPANY", { waitUntil: "networkidle", timeout: 15000 });
+ await companyPage.goto("/dashboard?role=COMPANY", { waitUntil: "networkidle", timeout: 15000 });
await new Promise(r => setTimeout(r, 3000));
await takeScreenshot(companyPage, "01_company_dashboard");
console.log(" ✅ Company dashboard loaded");
@@ -223,7 +192,7 @@ test.describe("Company Complete E2E with Admin Approval", () => {
// Click submit for verification via API
console.log(" ℹ️ Submit button enabled, submitting via API...");
- const submitResponse = await fetch("http://localhost:9100/api/profile/submit-for-verification", {
+ const submitResponse = await fetch(`${API_BASE}/profile/submit-for-verification`, {
method: "POST",
headers: {
"Content-Type": "application/json",
diff --git a/tests/e2e/company-e2e-complete-flow.spec.ts b/tests/e2e/company-e2e-complete-flow.spec.ts
index f9048ce..c8fbc23 100644
--- a/tests/e2e/company-e2e-complete-flow.spec.ts
+++ b/tests/e2e/company-e2e-complete-flow.spec.ts
@@ -7,11 +7,12 @@
*/
import { test, expect, chromium, BrowserContext, Page } from "@playwright/test";
import { randomUUID } from "crypto";
-import { execSync } from "child_process";
import * as fs from "fs";
import * as path from "path";
+import { apiRegister, apiVerifyEmail, apiLogin } from "./helpers/auth-flow";
+import { APP_BASE_URL, API_BASE } from "./helpers/env";
-const SCREENSHOT_DIR = "/Users/ashwin/workspace/nxtgauge-frontend-solid/test-results/company-e2e-complete";
+const SCREENSHOT_DIR = path.join(__dirname, "..", "..", "test-results", "company-e2e-complete");
if (!fs.existsSync(SCREENSHOT_DIR)) {
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
@@ -28,74 +29,30 @@ interface TestUser {
companyName?: string;
}
-async function getOTPFromRedis(userId: string): Promise {
- try {
- // Try to get OTP from Redis using multiple key patterns
- const plainKey = `otp:plain:${userId}`;
- let otpCode = execSync(`redis-cli GET "${plainKey}"`, { encoding: "utf8" }).trim();
- if (otpCode && otpCode.length >= 4) return otpCode;
-
- // Try otp:code:* pattern
- const keys = execSync("redis-cli KEYS 'otp:code:*'", { encoding: "utf8" })
- .trim().split("\n").filter(Boolean);
- for (const k of keys) {
- const v = execSync(`redis-cli GET "${k}"`, { encoding: "utf8" }).trim();
- if (v === userId) {
- otpCode = k.replace("otp:code:", "");
- return otpCode;
- }
- }
- // Fallback: try any otp key matching the userId
- const allKeys = execSync("redis-cli KEYS 'otp:*'", { encoding: "utf8" })
- .trim().split("\n").filter(Boolean);
- for (const k of allKeys) {
- const v = execSync(`redis-cli GET "${k}"`, { encoding: "utf8" }).trim();
- if (v === userId) {
- return k.split(":").pop() || null;
- }
- }
- return null;
- } catch {
- return null;
- }
-}
-
async function registerUser(user: TestUser): Promise {
console.log(`\n📝 Registering ${user.intent} via API...`);
- const regResponse = await fetch("http://localhost:9100/api/auth/register", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(user),
+ const regData = await apiRegister({
+ email: user.email,
+ password: user.password,
+ first_name: user.firstName,
+ last_name: user.lastName,
+ intent: user.intent,
});
- const regData = await regResponse.json();
if (!regData.user_id) throw new Error(`Registration failed: ${JSON.stringify(regData)}`);
user.userId = regData.user_id;
console.log(` ✅ Registered, user_id: ${user.userId}`);
- // Wait for OTP to be generated
+ // Wait for OTP to be generated, then verify via API
await new Promise(r => setTimeout(r, 1000));
- const otpCode = await getOTPFromRedis(user.userId!);
- if (!otpCode) throw new Error("Could not get OTP from Redis");
- console.log(` ✅ OTP retrieved: ${otpCode}`);
-
- // Verify OTP
- const verifyResponse = await fetch("http://localhost:9100/api/auth/verify-email", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ user_id: user.userId, otp: otpCode })
- });
- if (!verifyResponse.ok) throw new Error("OTP verification failed");
+ const verified = await apiVerifyEmail(user.email, user.userId!);
+ if (!verified) throw new Error("OTP verification failed");
console.log(` ✅ OTP verified!`);
// Login
- const loginResponse = await fetch("http://localhost:9100/api/auth/login", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ email: user.email, password: user.password })
- });
- const loginData = await loginResponse.json();
- if (!loginData.access_token) throw new Error("Login failed");
- user.accessToken = loginData.access_token;
+ const loginData = await apiLogin(user.email, user.password);
+ const accessToken = loginData?.access_token || "";
+ if (!accessToken) throw new Error("Login failed");
+ user.accessToken = accessToken;
console.log(` ✅ Logged in, token length: ${user.accessToken!.length}`);
return user;
@@ -149,8 +106,8 @@ test.describe("Company E2E Complete Flow", () => {
await registerUser(companyUser);
// ==================== BROWSER SETUP ====================
- const browser = await chromium.launch({ headless: false, slowMo: 30 });
- const context = await browser.newContext({ viewport: { width: 1400, height: 900 } });
+ const browser = await chromium.launch({ headless: true });
+ const context = await browser.newContext({ baseURL: APP_BASE_URL, viewport: { width: 1400, height: 900 } });
// ==================== DASHBOARD FLOW ====================
console.log("\n" + "=".repeat(60));
@@ -161,7 +118,7 @@ test.describe("Company E2E Complete Flow", () => {
await setupFrontendAuth(companyPage, companyUser);
// Navigate to dashboard
- await companyPage.goto("http://localhost:3000/dashboard?role=COMPANY", { waitUntil: "networkidle", timeout: 15000 });
+ await companyPage.goto("/dashboard?role=COMPANY", { waitUntil: "networkidle", timeout: 15000 });
await new Promise(r => setTimeout(r, 3000));
await takeScreenshot(companyPage, "01_dashboard_loaded");
console.log(" ✅ Company dashboard loaded");
@@ -287,7 +244,7 @@ test.describe("Company E2E Complete Flow", () => {
// Try to submit verification via API first to ensure it works
console.log(" ℹ️ Attempting verification submission via API...");
- const submitResponse = await fetch("http://localhost:9100/api/profile/submit-for-verification", {
+ const submitResponse = await fetch(`${API_BASE}/profile/submit-for-verification`, {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -331,7 +288,7 @@ test.describe("Company E2E Complete Flow", () => {
console.log(" ⚠️ Submit button still disabled - checking missing fields...");
// Check what fields are missing via API
- const profileRes = await fetch("http://localhost:9100/api/companies/profile/me", {
+ const profileRes = await fetch(`${API_BASE}/companies/profile/me`, {
headers: { "Authorization": `Bearer ${companyUser.accessToken}` }
});
if (profileRes.ok) {
diff --git a/tests/e2e/company-e2e-full.spec.ts b/tests/e2e/company-e2e-full.spec.ts
index f973d8d..b533f20 100644
--- a/tests/e2e/company-e2e-full.spec.ts
+++ b/tests/e2e/company-e2e-full.spec.ts
@@ -1,35 +1,16 @@
import { test, expect, chromium } from "@playwright/test";
import { randomUUID } from "crypto";
-import { execSync } from "child_process";
import * as fs from "fs";
+import * as path from "path";
+import { apiRegister, apiVerifyEmail, apiLogin } from "./helpers/auth-flow";
+import { APP_BASE_URL } from "./helpers/env";
-const SCREENSHOT_DIR = "/Users/ashwin/workspace/nxtgauge-frontend-solid/test-results/company-e2e";
+const SCREENSHOT_DIR = path.join(__dirname, "..", "..", "test-results", "company-e2e");
if (!fs.existsSync(SCREENSHOT_DIR)) {
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
}
-async function getOTPFromRedis(userId: string): Promise {
- try {
- const plainKey = `otp:plain:${userId}`;
- let otpCode = execSync(`redis-cli GET "${plainKey}"`, { encoding: "utf8" }).trim();
- if (otpCode && otpCode.length >= 4) return otpCode;
-
- const keys = execSync("redis-cli KEYS 'otp:code:*'", { encoding: "utf8" })
- .trim().split("\n").filter(Boolean);
- for (const k of keys) {
- const v = execSync(`redis-cli GET "${k}"`, { encoding: "utf8" }).trim();
- if (v === userId) {
- otpCode = k.replace("otp:code:", "");
- return otpCode;
- }
- }
- return null;
- } catch {
- return null;
- }
-}
-
test.describe("Company E2E Full Flow", () => {
test("complete company registration → OTP → login → dashboard → profile → verification", async () => {
const testEmail = `e2ecompany${randomUUID().slice(0, 8)}@test.com`;
@@ -39,19 +20,14 @@ test.describe("Company E2E Full Flow", () => {
console.log("📧 Email:", testEmail);
console.log("🏢 Company:", testCompanyName);
- const browser = await chromium.launch({ headless: false, slowMo: 30 });
- const context = await browser.newContext({ viewport: { width: 1400, height: 900 } });
+ const browser = await chromium.launch({ headless: true });
+ const context = await browser.newContext({ baseURL: APP_BASE_URL, viewport: { width: 1400, height: 900 } });
// ==================== STEP 1: REGISTER VIA API ====================
console.log("\n📝 STEP 1: Register via API");
let regData: any;
try {
- const regResponse = await fetch("http://localhost:9100/api/auth/register", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ email: testEmail, first_name: "John", last_name: "Doe", password: testPassword, intent: "company" })
- });
- regData = await regResponse.json();
+ regData = await apiRegister({ email: testEmail, first_name: "John", last_name: "Doe", password: testPassword, intent: "company" });
expect(regData.user_id).toBeTruthy();
console.log(" ✅ PASS: Registration successful, user_id:", regData.user_id);
} catch (e: any) {
@@ -59,30 +35,13 @@ test.describe("Company E2E Full Flow", () => {
throw e;
}
- // ==================== STEP 2: OTP VIA REDIS ====================
- console.log("\n🔐 STEP 2: OTP via Redis");
+ // ==================== STEP 2+3: OTP VIA REDIS + VERIFY VIA API ====================
+ console.log("\n🔐 STEP 2+3: OTP via Redis, verify via API");
await new Promise(r => setTimeout(r, 500));
- let otpCode: string | null = null;
try {
- otpCode = await getOTPFromRedis(regData.user_id);
- expect(otpCode).toBeTruthy();
- console.log(" ✅ PASS: OTP retrieved from Redis:", otpCode);
- } catch (e: any) {
- console.log(" ❌ FAIL: Could not get OTP -", e.message);
- throw e;
- }
-
- // ==================== STEP 3: VERIFY OTP VIA API ====================
- console.log("\n✅ STEP 3: Verify OTP via API");
- try {
- const verifyResponse = await fetch("http://localhost:9100/api/auth/verify-email", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ user_id: regData.user_id, otp: otpCode })
- });
- const verifyData = await verifyResponse.json();
- expect(verifyResponse.ok).toBe(true);
- console.log(" ✅ PASS: OTP verified! Response:", JSON.stringify(verifyData));
+ const verified = await apiVerifyEmail(testEmail, regData.user_id);
+ expect(verified).toBe(true);
+ console.log(" ✅ PASS: OTP retrieved and verified!");
} catch (e: any) {
console.log(" ❌ FAIL: OTP verification failed -", e.message);
throw e;
@@ -92,13 +51,8 @@ test.describe("Company E2E Full Flow", () => {
console.log("\n🔑 STEP 4: Login via API");
let accessToken = "";
try {
- const loginResponse = await fetch("http://localhost:9100/api/auth/login", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ email: testEmail, password: testPassword })
- });
- const loginData = await loginResponse.json();
- accessToken = loginData.access_token || "";
+ const loginData = await apiLogin(testEmail, testPassword);
+ accessToken = loginData?.access_token || "";
expect(accessToken).toBeTruthy();
console.log(" ✅ PASS: Login successful, token length:", accessToken.length);
} catch (e: any) {
@@ -125,7 +79,7 @@ test.describe("Company E2E Full Flow", () => {
}, { token: accessToken, email: testEmail, userId: regData.user_id });
try {
- await page.goto("http://localhost:3000/dashboard?role=COMPANY", { waitUntil: "networkidle", timeout: 15000 });
+ await page.goto("/dashboard?role=COMPANY", { waitUntil: "networkidle", timeout: 15000 });
await page.waitForTimeout(3000);
await page.screenshot({ path: `${SCREENSHOT_DIR}/step05_dashboard.png`, fullPage: true });
console.log(" ✅ PASS: Dashboard loaded");
diff --git a/tests/e2e/company-jobs.spec.ts b/tests/e2e/company-jobs.spec.ts
index e1077c5..a4f2ba5 100644
--- a/tests/e2e/company-jobs.spec.ts
+++ b/tests/e2e/company-jobs.spec.ts
@@ -1,17 +1,12 @@
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
+import { apiLogin } from "./helpers/auth-flow";
async function setupAuth(page: any): Promise {
- const res = await page.request.post("http://localhost:3000/api/auth/login", {
- data: {
- email: "testcompany@example.com",
- password: "TestPassword123!",
- },
- });
- if (!res.ok()) return false;
- const data = await res.json();
- const token = data.access_token;
- await page.goto("http://localhost:3000/dashboard");
+ const loginData = await apiLogin("testcompany@example.com", "TestPassword123!");
+ if (!loginData) return false;
+ const token = loginData.access_token;
+ await page.goto("/dashboard");
await page.evaluate((t: string) => {
window.sessionStorage.setItem("nxtgauge_access_token", t);
window.sessionStorage.setItem("nxtgauge_frontend_access_token", t);
diff --git a/tests/e2e/company-verification-flow.spec.ts b/tests/e2e/company-verification-flow.spec.ts
index b813c45..e564a32 100644
--- a/tests/e2e/company-verification-flow.spec.ts
+++ b/tests/e2e/company-verification-flow.spec.ts
@@ -1,5 +1,6 @@
import { test, expect, chromium } from "@playwright/test";
import { randomUUID } from "crypto";
+import { APP_BASE_URL } from "./helpers/env";
// Generate random test data
const testEmail = `testcompany${randomUUID().slice(0, 8)}@test.com`;
@@ -17,11 +18,11 @@ test.setTimeout(300000); // 5 minutes to allow manual CAPTCHA entry
test("Company signup -> verification flow", async () => {
// Launch browser with UI visible
const browser = await chromium.launch({
- headless: false,
- slowMo: 200,
+ headless: true,
});
const context = await browser.newContext({
+ baseURL: APP_BASE_URL,
viewport: { width: 1400, height: 900 },
recordVideo: {
dir: "./test-videos/",
@@ -34,7 +35,7 @@ test("Company signup -> verification flow", async () => {
try {
// Step 1: Navigate to public website signup
console.log("🌐 Step 1: Opening public website signup...");
- await page.goto("http://localhost:3001/signup?intent=company");
+ await page.goto("/signup?intent=company");
await page.waitForLoadState("networkidle");
await page.screenshot({ path: "./test-results/01-signup-page.png", fullPage: true });
@@ -126,7 +127,7 @@ test("Company signup -> verification flow", async () => {
// Should be redirected to login, or navigate there
if (!page.url().includes("/login")) {
- await page.goto("http://localhost:3001/login");
+ await page.goto("/login");
await page.waitForLoadState("networkidle");
}
@@ -196,7 +197,7 @@ test("Company signup -> verification flow", async () => {
// Step 7: Open Admin Panel
console.log("🔐 Step 7: Opening admin panel...");
const adminPage = await context.newPage();
- await adminPage.goto("http://localhost:3000/login");
+ await adminPage.goto("/login");
await adminPage.waitForLoadState("networkidle");
await adminPage.screenshot({ path: "./test-results/07-admin-login.png", fullPage: true });
diff --git a/tests/e2e/full-e2e-company-jobseeker-admin.spec.ts b/tests/e2e/full-e2e-company-jobseeker-admin.spec.ts
index c3ba8c6..4c46530 100644
--- a/tests/e2e/full-e2e-company-jobseeker-admin.spec.ts
+++ b/tests/e2e/full-e2e-company-jobseeker-admin.spec.ts
@@ -1,9 +1,11 @@
import { test, expect, chromium, BrowserContext, Page } from "@playwright/test";
import { randomUUID } from "crypto";
-import { execSync } from "child_process";
import * as fs from "fs";
+import * as path from "path";
+import { apiRegister, apiVerifyEmail, apiLogin } from "./helpers/auth-flow";
+import { APP_BASE_URL } from "./helpers/env";
-const SCREENSHOT_DIR = "/Users/ashwin/workspace/nxtgauge-frontend-solid/test-results/full-e2e";
+const SCREENSHOT_DIR = path.join(__dirname, "..", "..", "test-results", "full-e2e");
if (!fs.existsSync(SCREENSHOT_DIR)) {
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
@@ -20,63 +22,30 @@ interface TestUser {
companyName?: string;
}
-async function getOTPFromRedis(userId: string): Promise {
- try {
- const plainKey = `otp:plain:${userId}`;
- let otpCode = execSync(`redis-cli GET "${plainKey}"`, { encoding: "utf8" }).trim();
- if (otpCode && otpCode.length >= 4) return otpCode;
-
- const keys = execSync("redis-cli KEYS 'otp:code:*'", { encoding: "utf8" })
- .trim().split("\n").filter(Boolean);
- for (const k of keys) {
- const v = execSync(`redis-cli GET "${k}"`, { encoding: "utf8" }).trim();
- if (v === userId) {
- otpCode = k.replace("otp:code:", "");
- return otpCode;
- }
- }
- return null;
- } catch {
- return null;
- }
-}
-
async function registerUser(user: TestUser): Promise {
console.log(`\n📝 Registering ${user.intent} via API...`);
- const regResponse = await fetch("http://localhost:9100/api/auth/register", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(user),
+ const regData = await apiRegister({
+ email: user.email,
+ password: user.password,
+ first_name: user.firstName,
+ last_name: user.lastName,
+ intent: user.intent,
});
- const regData = await regResponse.json();
if (!regData.user_id) throw new Error(`Registration failed: ${JSON.stringify(regData)}`);
user.userId = regData.user_id;
console.log(` ✅ Registered, user_id: ${user.userId}`);
- // Get OTP from Redis
+ // Get OTP from Redis + verify via API
await new Promise(r => setTimeout(r, 500));
- const otpCode = await getOTPFromRedis(user.userId!);
- if (!otpCode) throw new Error("Could not get OTP from Redis");
- console.log(` ✅ OTP retrieved: ${otpCode}`);
-
- // Verify OTP
- const verifyResponse = await fetch("http://localhost:9100/api/auth/verify-email", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ user_id: user.userId, otp: otpCode })
- });
- if (!verifyResponse.ok) throw new Error("OTP verification failed");
+ const verified = await apiVerifyEmail(user.email, user.userId!);
+ if (!verified) throw new Error("OTP verification failed");
console.log(` ✅ OTP verified!`);
// Login
- const loginResponse = await fetch("http://localhost:9100/api/auth/login", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ email: user.email, password: user.password })
- });
- const loginData = await loginResponse.json();
- if (!loginData.access_token) throw new Error("Login failed");
- user.accessToken = loginData.access_token;
+ const loginData = await apiLogin(user.email, user.password);
+ const accessToken = loginData?.access_token || "";
+ if (!accessToken) throw new Error("Login failed");
+ user.accessToken = accessToken;
console.log(` ✅ Logged in, token length: ${user.accessToken!.length}`);
return user;
@@ -139,8 +108,8 @@ test.describe("Full Company + Job Seeker E2E with Admin Verification", () => {
await registerUser(jobSeekerUser);
// ==================== BROWSER SETUP ====================
- const browser = await chromium.launch({ headless: false, slowMo: 30 });
- const context = await browser.newContext({ viewport: { width: 1400, height: 900 } });
+ const browser = await chromium.launch({ headless: true });
+ const context = await browser.newContext({ baseURL: APP_BASE_URL, viewport: { width: 1400, height: 900 } });
// ==================== COMPANY FRONTEND FLOW ====================
console.log("\n" + "=".repeat(60));
@@ -150,7 +119,7 @@ test.describe("Full Company + Job Seeker E2E with Admin Verification", () => {
const companyPage = await context.newPage();
await setupFrontendAuth(companyPage, companyUser);
- await companyPage.goto("http://localhost:3000/dashboard?role=COMPANY", { waitUntil: "networkidle", timeout: 15000 });
+ await companyPage.goto("/dashboard?role=COMPANY", { waitUntil: "networkidle", timeout: 15000 });
await new Promise(r => setTimeout(r, 3000));
await takeScreenshot(companyPage, "01_company_dashboard");
console.log(" ✅ Company dashboard loaded");
@@ -204,7 +173,7 @@ test.describe("Full Company + Job Seeker E2E with Admin Verification", () => {
const jsPage = await context.newPage();
await setupFrontendAuth(jsPage, jobSeekerUser);
- await jsPage.goto("http://localhost:3000/dashboard?role=JOB_SEEKER", { waitUntil: "networkidle", timeout: 15000 });
+ await jsPage.goto("/dashboard?role=JOB_SEEKER", { waitUntil: "networkidle", timeout: 15000 });
await new Promise(r => setTimeout(r, 3000));
await takeScreenshot(jsPage, "04_jobseeker_dashboard");
console.log(" ✅ Job seeker dashboard loaded");
diff --git a/tests/e2e/guardrails.spec.ts b/tests/e2e/guardrails.spec.ts
index 325f91c..1006700 100644
--- a/tests/e2e/guardrails.spec.ts
+++ b/tests/e2e/guardrails.spec.ts
@@ -1,6 +1,6 @@
import { test, expect, request } from "@playwright/test";
-
-const API_BASE = "http://localhost:3000/api";
+import { apiLogin } from "./helpers/auth-flow";
+import { API_BASE } from "./helpers/env";
const PHONE_PATTERNS = [
/\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/,
@@ -33,29 +33,13 @@ function hasContactInfo(text: string): boolean {
}
async function getCompanyToken(): Promise {
- const ctx = await request.newContext();
- const res = await ctx.post(`${API_BASE}/auth/login`, {
- data: {
- email: "testcompany@example.com",
- password: "TestPassword123!",
- },
- });
- if (!res.ok()) return null;
- const data = await res.json();
- return data.access_token || null;
+ const loginData = await apiLogin("testcompany@example.com", "TestPassword123!");
+ return loginData?.access_token || null;
}
async function getJobSeekerToken(): Promise {
- const ctx = await request.newContext();
- const res = await ctx.post(`${API_BASE}/auth/login`, {
- data: {
- email: "testtutora2026@example.com",
- password: "Test1234!",
- },
- });
- if (!res.ok()) return null;
- const data = await res.json();
- return data.access_token || null;
+ const loginData = await apiLogin("testtutora2026@example.com", "Test1234!");
+ return loginData?.access_token || null;
}
test.describe("Guard Rails - AI Content Safety", () => {
diff --git a/tests/e2e/helpers/auth-flow.ts b/tests/e2e/helpers/auth-flow.ts
new file mode 100644
index 0000000..403fd03
--- /dev/null
+++ b/tests/e2e/helpers/auth-flow.ts
@@ -0,0 +1,65 @@
+import { API_BASE } from "./env";
+import { solveCaptcha } from "./captcha";
+import { getOtpFromRedis } from "./otp";
+
+export interface RegisterInput {
+ email: string;
+ password: string;
+ first_name?: string;
+ last_name?: string;
+ phone?: string;
+ intent?: string;
+ profession?: string;
+}
+
+/** POST /api/auth/register (captcha-solved automatically). */
+export async function apiRegister(input: RegisterInput): Promise<{ user_id: string; [k: string]: any }> {
+ const { captcha_id, captcha_answer } = await solveCaptcha();
+ const res = await fetch(`${API_BASE}/auth/register`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ ...input, captcha_id, captcha_answer }),
+ });
+ const data = await res.json();
+ if (!res.ok) {
+ throw new Error(`Register failed (${res.status}): ${JSON.stringify(data)}`);
+ }
+ return data;
+}
+
+/** POST /api/auth/verify-email, sourcing the OTP straight from Redis. */
+export async function apiVerifyEmail(email: string, userId: string): Promise {
+ const otp = await getOtpFromRedis(userId);
+ if (!otp) {
+ console.log(`⚠️ Could not retrieve OTP from Redis for user ${userId}`);
+ return false;
+ }
+ const res = await fetch(`${API_BASE}/auth/verify-email`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ otp, email }),
+ });
+ return res.ok;
+}
+
+/** POST /api/auth/login (captcha-solved automatically). Returns null on non-2xx. */
+export async function apiLogin(email: string, password: string): Promise<{ access_token: string; [k: string]: any } | null> {
+ const { captcha_id, captcha_answer } = await solveCaptcha();
+ const res = await fetch(`${API_BASE}/auth/login`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ email, password, captcha_id, captcha_answer }),
+ });
+ if (!res.ok) return null;
+ return res.json();
+}
+
+/** Full register → verify → login convenience flow, returning the access token. */
+export async function registerVerifyLogin(input: RegisterInput): Promise<{ user_id: string; access_token: string }> {
+ const regData = await apiRegister(input);
+ const verified = await apiVerifyEmail(input.email, regData.user_id);
+ if (!verified) throw new Error(`Email verification failed for ${input.email}`);
+ const loginData = await apiLogin(input.email, input.password);
+ if (!loginData) throw new Error(`Login failed for ${input.email} after verification`);
+ return { user_id: regData.user_id, access_token: loginData.access_token };
+}
diff --git a/tests/e2e/helpers/captcha.ts b/tests/e2e/helpers/captcha.ts
new file mode 100644
index 0000000..734cd2f
--- /dev/null
+++ b/tests/e2e/helpers/captcha.ts
@@ -0,0 +1,28 @@
+import { API_BASE } from "./env";
+
+/**
+ * Fetches a fresh math captcha from the API and solves it.
+ * Captchas are single-use (Redis-backed) — call this immediately before the
+ * request that needs it, never cache/reuse a solved captcha_id.
+ */
+export async function solveCaptcha(): Promise<{ captcha_id: string; captcha_answer: string }> {
+ const res = await fetch(`${API_BASE}/auth/captcha`, { method: "POST" });
+ if (!res.ok) {
+ throw new Error(`Failed to fetch captcha: ${res.status} ${await res.text()}`);
+ }
+ const { captcha_id, challenge } = await res.json();
+
+ // Challenge format observed from the API: "N + N = ?" / "N - N = ?" (also
+ // tolerate "*" just in case). Keep this in sync with
+ // apps/users/src/handlers/auth.rs's captcha generator.
+ const match = String(challenge).match(/(-?\d+)\s*([+\-*])\s*(-?\d+)/);
+ if (!match) {
+ throw new Error(`Unrecognized captcha challenge format: "${challenge}"`);
+ }
+ const [, aStr, op, bStr] = match;
+ const a = Number(aStr);
+ const b = Number(bStr);
+ const answer = op === "+" ? a + b : op === "-" ? a - b : a * b;
+
+ return { captcha_id, captcha_answer: String(answer) };
+}
diff --git a/tests/e2e/helpers/env.ts b/tests/e2e/helpers/env.ts
new file mode 100644
index 0000000..cf379a2
--- /dev/null
+++ b/tests/e2e/helpers/env.ts
@@ -0,0 +1,11 @@
+/**
+ * Single source of truth for which environment the e2e suite targets.
+ * Mirrors the same TEST_ENV=production condition used in playwright.config.ts's
+ * `use.baseURL` — keep these in sync.
+ */
+export const APP_BASE_URL =
+ process.env.TEST_ENV === "production"
+ ? "https://test111.nxtgauge.com"
+ : "http://localhost:3000";
+
+export const API_BASE = `${APP_BASE_URL}/api`;
diff --git a/tests/e2e/helpers/otp.ts b/tests/e2e/helpers/otp.ts
new file mode 100644
index 0000000..dbf57df
--- /dev/null
+++ b/tests/e2e/helpers/otp.ts
@@ -0,0 +1,42 @@
+import { execFileSync } from "child_process";
+
+/**
+ * Reads a registration OTP straight out of Redis, the same way a human would
+ * never get to (the API deliberately never returns it — see
+ * apps/users/src/handlers/auth.rs's RegisterResponse.otp, which is only
+ * populated for the DEMO_ACCOUNT_EMAILS allowlist, unset in this environment).
+ *
+ * This only works when the machine running the tests has `kubectl` access to
+ * the cluster (redis-master-0 in the `data` namespace) — i.e. run from the
+ * same host used for the rest of the live-server runbook, not arbitrary CI.
+ * Auths via the redis pod's own mounted password file rather than a secret
+ * value passed on our end, matching how docs/LIVE_SERVER_RUNBOOK.md's manual
+ * verification steps do it.
+ */
+export async function getOtpFromRedis(userId: string): Promise {
+ try {
+ const out = execFileSync(
+ "kubectl",
+ [
+ "-n",
+ "data",
+ "exec",
+ "redis-master-0",
+ "--",
+ "sh",
+ "-c",
+ 'redis-cli -a "$(cat /opt/bitnami/redis/secrets/redis-password)" GET "otp:plain:$1"',
+ "--", // end of sh -c's own options, "$1" below binds to this
+ userId,
+ ],
+ { encoding: "utf8" }
+ ).trim();
+
+ // redis-cli prints "(nil)" for a missing key rather than exiting non-zero.
+ if (!out || out === "(nil)" || !/^\d{4,}$/.test(out)) return null;
+ return out;
+ } catch (e: any) {
+ console.log("⚠️ Could not read OTP from Redis via kubectl:", e.message);
+ return null;
+ }
+}
diff --git a/tests/e2e/job-seeker-complete.spec.ts b/tests/e2e/job-seeker-complete.spec.ts
index f93b67b..1c1a676 100644
--- a/tests/e2e/job-seeker-complete.spec.ts
+++ b/tests/e2e/job-seeker-complete.spec.ts
@@ -1,29 +1,9 @@
-import { test, expect, chromium } from "@playwright/test";
+import { test, expect } from "@playwright/test";
import { randomUUID } from "crypto";
-import { execSync } from "child_process";
+import * as path from "path";
+import { apiRegister, apiVerifyEmail, apiLogin } from "./helpers/auth-flow";
-const SCREENSHOT_DIR = "/Users/ashwin/workspace/nxtgauge-frontend-solid/test-results/job-seeker-complete";
-
-async function getOTPFromRedis(userId: string): Promise {
- try {
- const plainKey = `otp:plain:${userId}`;
- let otpCode = execSync(`redis-cli GET "${plainKey}"`, { encoding: "utf8" }).trim();
- if (otpCode && otpCode.length >= 4) return otpCode;
-
- const keys = execSync("redis-cli KEYS 'otp:code:*'", { encoding: "utf8" })
- .trim().split("\n").filter(Boolean);
- for (const k of keys) {
- const v = execSync(`redis-cli GET "${k}"`, { encoding: "utf8" }).trim();
- if (v === userId) {
- otpCode = k.replace("otp:code:", "");
- return otpCode;
- }
- }
- return null;
- } catch {
- return null;
- }
-}
+const SCREENSHOT_DIR = path.join(__dirname, "..", "..", "test-results", "job-seeker-complete");
test.describe("Job Seeker E2E Complete Flow", () => {
test.beforeEach(async ({ page }) => {
@@ -40,95 +20,74 @@ test.describe("Job Seeker E2E Complete Flow", () => {
// ==================== STEP 1: REGISTER VIA API ====================
console.log("\n📝 STEP 1: Register via API");
- let regData: any;
- const regResponse = await fetch("http://localhost:9100/api/auth/register", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- email: testEmail,
- first_name: "Jane",
- last_name: "Smith",
- password: testPassword,
- intent: "job_seeker"
- })
+ const regData = await apiRegister({
+ email: testEmail,
+ first_name: "Jane",
+ last_name: "Smith",
+ password: testPassword,
+ intent: "job_seeker"
});
- regData = await regResponse.json();
expect(regData.user_id).toBeTruthy();
console.log(" ✅ Registration successful, user_id:", regData.user_id);
- // ==================== STEP 2: OTP VIA REDIS ====================
- console.log("\n🔐 STEP 2: Get OTP via Redis");
+ // ==================== STEP 2+3: OTP VIA REDIS + VERIFY VIA API ====================
+ console.log("\n🔐 STEP 2+3: Get OTP via Redis and verify via API");
await new Promise(r => setTimeout(r, 500));
- let otpCode = await getOTPFromRedis(regData.user_id);
- expect(otpCode).toBeTruthy();
- console.log(" ✅ OTP retrieved:", otpCode);
-
- // ==================== STEP 3: VERIFY OTP VIA API ====================
- console.log("\n✅ STEP 3: Verify OTP via API");
- const verifyResponse = await fetch("http://localhost:9100/api/auth/verify-email", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ user_id: regData.user_id, otp: otpCode })
- });
- expect(verifyResponse.ok).toBe(true);
+ const verified = await apiVerifyEmail(testEmail, regData.user_id);
+ expect(verified).toBe(true);
console.log(" ✅ OTP verified!");
// ==================== STEP 4: LOGIN VIA API ====================
console.log("\n🔑 STEP 4: Login via API");
- const loginResponse = await fetch("http://localhost:9100/api/auth/login", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ email: testEmail, password: testPassword })
- });
- const loginData = await loginResponse.json();
- const accessToken = loginData.access_token;
+ const loginData = await apiLogin(testEmail, testPassword);
+ const accessToken = loginData?.access_token || "";
expect(accessToken).toBeTruthy();
console.log(" ✅ Login successful, token length:", accessToken.length);
// ==================== STEP 5: DASHBOARD ====================
console.log("\n🌐 STEP 5: Navigate to dashboard");
-
+
// Seed sessionStorage and localStorage with auth data (auth.tsx uses sessionStorage for token)
await page.addInitScript(({ token, email, userId }) => {
// auth.tsx getToken() reads from sessionStorage
sessionStorage.setItem("nxtgauge_access_token", token);
-
+
// localStorage for user data (used by various components)
localStorage.setItem("nxtgauge_user", JSON.stringify({
- email,
- roleKey: "JOB_SEEKER",
- role: "JOB_SEEKER",
+ email,
+ roleKey: "JOB_SEEKER",
+ role: "JOB_SEEKER",
active_role: "JOB_SEEKER",
- selectedProfessionalRole: "JOB_SEEKER",
- name: "Jane Smith",
- fullName: "Jane Smith",
+ selectedProfessionalRole: "JOB_SEEKER",
+ name: "Jane Smith",
+ fullName: "Jane Smith",
id: userId
}));
localStorage.setItem("nxtgauge_auth_user", JSON.stringify({
- email,
- roleKey: "JOB_SEEKER",
- role: "JOB_SEEKER",
+ email,
+ roleKey: "JOB_SEEKER",
+ role: "JOB_SEEKER",
active_role: "JOB_SEEKER",
- selectedProfessionalRole: "JOB_SEEKER",
- name: "Jane Smith",
- fullName: "Jane Smith",
+ selectedProfessionalRole: "JOB_SEEKER",
+ name: "Jane Smith",
+ fullName: "Jane Smith",
id: userId
}));
localStorage.setItem("nxtgauge_signup_profile_v1", JSON.stringify({
- email,
- roleKey: "JOB_SEEKER",
- role: "JOB_SEEKER",
+ email,
+ roleKey: "JOB_SEEKER",
+ role: "JOB_SEEKER",
active_role: "JOB_SEEKER",
- selectedProfessionalRole: "JOB_SEEKER",
- name: "Jane Smith",
- fullName: "Jane Smith",
+ selectedProfessionalRole: "JOB_SEEKER",
+ name: "Jane Smith",
+ fullName: "Jane Smith",
id: userId
}));
}, { token: accessToken, email: testEmail, userId: regData.user_id });
- await page.goto("http://localhost:3000/dashboard?role=JOB_SEEKER", { waitUntil: "networkidle", timeout: 15000 });
+ await page.goto("/dashboard?role=JOB_SEEKER", { waitUntil: "networkidle", timeout: 15000 });
await page.waitForTimeout(2000);
-
+
// Check dashboard loaded - URL should not redirect to login
const currentUrl = page.url();
expect(currentUrl).not.toContain("/login");
@@ -136,21 +95,21 @@ test.describe("Job Seeker E2E Complete Flow", () => {
// ==================== STEP 6: PROFILE FORM ====================
console.log("\n📋 STEP 6: Navigate to profile");
-
+
// Click My Profile button
const profileBtn = page.getByRole("button", { name: /my profile/i });
if (await profileBtn.isVisible().catch(() => false)) {
await profileBtn.click();
await page.waitForTimeout(2000);
} else {
- await page.goto("http://localhost:3000/dashboard/profile?role=JOB_SEEKER", { waitUntil: "networkidle" });
+ await page.goto("/dashboard/profile?role=JOB_SEEKER", { waitUntil: "networkidle" });
await page.waitForTimeout(2000);
}
console.log(" ✅ Profile page displayed");
// ==================== STEP 6b: FILL PROFILE FORM ====================
console.log("\n📝 STEP 6b: Fill job seeker profile");
-
+
// Fill basic fields using label selectors since inputs have no name/id
const fieldMappings: Record = {
"First Name": "Jane",
@@ -187,7 +146,7 @@ test.describe("Job Seeker E2E Complete Flow", () => {
// ==================== STEP 7: DOCUMENTS TAB ====================
console.log("\n📄 STEP 7: Upload documents");
-
+
// Switch to Documents tab
const docsTab = page.getByRole("button", { name: /documents/i });
if (await docsTab.isVisible().catch(() => false)) {
@@ -201,15 +160,15 @@ test.describe("Job Seeker E2E Complete Flow", () => {
// ==================== STEP 8: SUBMIT FOR VERIFICATION ====================
console.log("\n📤 STEP 8: Submit for verification");
-
+
const submitBtn = page.getByRole("button", { name: /submit for verification/i });
-
+
if (await submitBtn.isVisible({ timeout: 2000 }).catch(() => false)) {
const isDisabled = await submitBtn.isDisabled().catch(() => true);
-
+
if (isDisabled) {
console.log(" ⚠️ Submit button disabled - checking what's missing");
-
+
// Check for missing fields message
const bodyText = await page.locator("body").innerText();
if (bodyText.includes("required") || bodyText.includes("missing")) {
@@ -218,7 +177,7 @@ test.describe("Job Seeker E2E Complete Flow", () => {
} else {
await submitBtn.click();
await page.waitForTimeout(3000);
-
+
// Check for success message
const bodyText = await page.locator("body").innerText();
if (bodyText.includes("Submitted") || bodyText.includes("success")) {
diff --git a/tests/e2e/job-seeker-e2e-full.spec.ts b/tests/e2e/job-seeker-e2e-full.spec.ts
index 472e6f9..beb4be3 100644
--- a/tests/e2e/job-seeker-e2e-full.spec.ts
+++ b/tests/e2e/job-seeker-e2e-full.spec.ts
@@ -1,35 +1,16 @@
import { test, expect, chromium } from "@playwright/test";
import { randomUUID } from "crypto";
-import { execSync } from "child_process";
import * as fs from "fs";
+import * as path from "path";
+import { apiRegister, apiVerifyEmail, apiLogin } from "./helpers/auth-flow";
+import { APP_BASE_URL } from "./helpers/env";
-const SCREENSHOT_DIR = "/Users/ashwin/workspace/nxtgauge-frontend-solid/test-results/job-seeker-e2e";
+const SCREENSHOT_DIR = path.join(__dirname, "..", "..", "test-results", "job-seeker-e2e");
if (!fs.existsSync(SCREENSHOT_DIR)) {
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
}
-async function getOTPFromRedis(userId: string): Promise {
- try {
- const plainKey = `otp:plain:${userId}`;
- let otpCode = execSync(`redis-cli GET "${plainKey}"`, { encoding: "utf8" }).trim();
- if (otpCode && otpCode.length >= 4) return otpCode;
-
- const keys = execSync("redis-cli KEYS 'otp:code:*'", { encoding: "utf8" })
- .trim().split("\n").filter(Boolean);
- for (const k of keys) {
- const v = execSync(`redis-cli GET "${k}"`, { encoding: "utf8" }).trim();
- if (v === userId) {
- otpCode = k.replace("otp:code:", "");
- return otpCode;
- }
- }
- return null;
- } catch {
- return null;
- }
-}
-
test.describe("Job Seeker E2E Full Flow", () => {
test("complete job seeker registration → OTP → login → dashboard → profile → verification", async () => {
const testEmail = `e2ejobseeker${randomUUID().slice(0, 8)}@test.com`;
@@ -37,19 +18,14 @@ test.describe("Job Seeker E2E Full Flow", () => {
console.log("📧 Email:", testEmail);
- const browser = await chromium.launch({ headless: false, slowMo: 30 });
- const context = await browser.newContext({ viewport: { width: 1400, height: 900 } });
+ const browser = await chromium.launch({ headless: true });
+ const context = await browser.newContext({ baseURL: APP_BASE_URL, viewport: { width: 1400, height: 900 } });
// ==================== STEP 1: REGISTER VIA API ====================
console.log("\n📝 STEP 1: Register via API");
let regData: any;
try {
- const regResponse = await fetch("http://localhost:9100/api/auth/register", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ email: testEmail, first_name: "Jane", last_name: "Smith", password: testPassword, intent: "job_seeker" })
- });
- regData = await regResponse.json();
+ regData = await apiRegister({ email: testEmail, first_name: "Jane", last_name: "Smith", password: testPassword, intent: "job_seeker" });
expect(regData.user_id).toBeTruthy();
console.log(" ✅ PASS: Registration successful, user_id:", regData.user_id);
} catch (e: any) {
@@ -57,30 +33,13 @@ test.describe("Job Seeker E2E Full Flow", () => {
throw e;
}
- // ==================== STEP 2: OTP VIA REDIS ====================
- console.log("\n🔐 STEP 2: OTP via Redis");
+ // ==================== STEP 2+3: OTP VIA REDIS + VERIFY VIA API ====================
+ console.log("\n🔐 STEP 2+3: OTP via Redis, verify via API");
await new Promise(r => setTimeout(r, 500));
- let otpCode: string | null = null;
try {
- otpCode = await getOTPFromRedis(regData.user_id);
- expect(otpCode).toBeTruthy();
- console.log(" ✅ PASS: OTP retrieved from Redis:", otpCode);
- } catch (e: any) {
- console.log(" ❌ FAIL: Could not get OTP -", e.message);
- throw e;
- }
-
- // ==================== STEP 3: VERIFY OTP VIA API ====================
- console.log("\n✅ STEP 3: Verify OTP via API");
- try {
- const verifyResponse = await fetch("http://localhost:9100/api/auth/verify-email", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ user_id: regData.user_id, otp: otpCode })
- });
- const verifyData = await verifyResponse.json();
- expect(verifyResponse.ok).toBe(true);
- console.log(" ✅ PASS: OTP verified! Response:", JSON.stringify(verifyData));
+ const verified = await apiVerifyEmail(testEmail, regData.user_id);
+ expect(verified).toBe(true);
+ console.log(" ✅ PASS: OTP retrieved and verified!");
} catch (e: any) {
console.log(" ❌ FAIL: OTP verification failed -", e.message);
throw e;
@@ -90,13 +49,8 @@ test.describe("Job Seeker E2E Full Flow", () => {
console.log("\n🔑 STEP 4: Login via API");
let accessToken = "";
try {
- const loginResponse = await fetch("http://localhost:9100/api/auth/login", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ email: testEmail, password: testPassword })
- });
- const loginData = await loginResponse.json();
- accessToken = loginData.access_token || "";
+ const loginData = await apiLogin(testEmail, testPassword);
+ accessToken = loginData?.access_token || "";
expect(accessToken).toBeTruthy();
console.log(" ✅ PASS: Login successful, token length:", accessToken.length);
} catch (e: any) {
@@ -107,7 +61,7 @@ test.describe("Job Seeker E2E Full Flow", () => {
// ==================== STEP 5: DASHBOARD ====================
console.log("\n🌐 STEP 5: Navigate to dashboard?role=JOB_SEEKER");
const page = await context.newPage();
-
+
await page.addInitScript(({ token, email, userId }) => {
localStorage.setItem("nxtgauge_access_token", token);
localStorage.setItem("nxtgauge_user", JSON.stringify({
@@ -122,7 +76,7 @@ test.describe("Job Seeker E2E Full Flow", () => {
}, { token: accessToken, email: testEmail, userId: regData.user_id });
try {
- await page.goto("http://localhost:3000/dashboard?role=JOB_SEEKER", { waitUntil: "networkidle", timeout: 15000 });
+ await page.goto("/dashboard?role=JOB_SEEKER", { waitUntil: "networkidle", timeout: 15000 });
await page.waitForTimeout(3000);
await page.screenshot({ path: `${SCREENSHOT_DIR}/step05_dashboard.png`, fullPage: true });
console.log(" ✅ PASS: Dashboard loaded");
@@ -142,7 +96,7 @@ test.describe("Job Seeker E2E Full Flow", () => {
await page.waitForTimeout(3000);
} else {
console.log(" ⚠️ My Profile button not visible, trying direct navigation");
- await page.goto("http://localhost:3000/dashboard/profile?role=JOB_SEEKER", { waitUntil: "networkidle" });
+ await page.goto("/dashboard/profile?role=JOB_SEEKER", { waitUntil: "networkidle" });
await page.waitForTimeout(3000);
}
await page.screenshot({ path: `${SCREENSHOT_DIR}/step06_profile_form.png`, fullPage: true });
@@ -158,7 +112,7 @@ test.describe("Job Seeker E2E Full Flow", () => {
const inputs = page.locator("input");
const count = await inputs.count();
console.log(" Total inputs found:", count);
-
+
// Log all inputs with their attributes
for (let i = 0; i < Math.min(count, 15); i++) {
const input = inputs.nth(i);
@@ -172,30 +126,30 @@ test.describe("Job Seeker E2E Full Flow", () => {
console.log(` Input ${i}: type="${type}" placeholder="${placeholder}" id="${id}" name="${name}"`);
}
}
-
+
// Try to find and fill common job seeker fields
const nameFields = ["first_name", "firstName", "First Name", "first-name"];
const lastNameFields = ["last_name", "lastName", "Last Name", "last-name"];
const emailField = page.locator('input[name="email"], input[id="email"]').first();
const phoneField = page.locator('input[name="phone"], input[id="phone"], input[placeholder*="phone" i]').first();
const locationField = page.locator('input[name="location"], input[id="location"], input[placeholder*="location" i]').first();
-
+
if (await emailField.isVisible().catch(() => false)) {
console.log(" ℹ️ Email field already has:", await emailField.inputValue().catch(() => ""));
}
-
+
// Fill phone
if (await phoneField.isVisible().catch(() => false)) {
await phoneField.fill("9876543210");
console.log(" ✅ Filled phone");
}
-
+
// Fill location
if (await locationField.isVisible().catch(() => false)) {
await locationField.fill("Chennai");
console.log(" ✅ Filled location");
}
-
+
// Try textareas too
const textareas = page.locator("textarea");
const textareaCount = await textareas.count();
@@ -209,7 +163,7 @@ test.describe("Job Seeker E2E Full Flow", () => {
console.log(` Textarea ${i}: placeholder="${placeholder}" name="${name}"`);
}
}
-
+
await page.screenshot({ path: `${SCREENSHOT_DIR}/step06b_profile_inputs.png`, fullPage: true });
console.log(" ✅ Profile form analyzed");
} catch (e: any) {
@@ -222,13 +176,13 @@ test.describe("Job Seeker E2E Full Flow", () => {
try {
const submitBtn = page.getByRole("button", { name: /submit for verification/i });
const btnVisible = await submitBtn.isVisible().catch(() => false);
-
+
if (btnVisible) {
const isDisabled = await submitBtn.isDisabled().catch(() => true);
if (isDisabled) {
console.log(" ⚠️ INFO: Submit button disabled - profile needs more fields filled");
await page.screenshot({ path: `${SCREENSHOT_DIR}/step07_submit_disabled.png`, fullPage: true });
-
+
// Try Documents tab
const docsTab = page.getByRole("tab", { name: /documents/i }).first();
if (await docsTab.isVisible().catch(() => false)) {
diff --git a/tests/e2e/security.spec.ts b/tests/e2e/security.spec.ts
index d0b4fc2..52afb88 100644
--- a/tests/e2e/security.spec.ts
+++ b/tests/e2e/security.spec.ts
@@ -1,14 +1,18 @@
import { test, expect, request } from "@playwright/test";
+import { solveCaptcha } from "./helpers/captcha";
const API_BASE = "https://test111.nxtgauge.com/api";
test.describe("Security - Authentication", () => {
test("JWT token is not returned for invalid credentials", async () => {
const ctx = await request.newContext();
+ const { captcha_id, captcha_answer } = await solveCaptcha();
const res = await ctx.post(`${API_BASE}/auth/login`, {
data: {
email: "nonexistent@example.com",
password: "wrongpassword",
+ captcha_id,
+ captcha_answer,
},
});
@@ -19,9 +23,12 @@ test.describe("Security - Authentication", () => {
test("Login without email returns proper error", async () => {
const ctx = await request.newContext();
+ const { captcha_id, captcha_answer } = await solveCaptcha();
const res = await ctx.post(`${API_BASE}/auth/login`, {
data: {
password: "somepassword",
+ captcha_id,
+ captcha_answer,
},
});
@@ -30,9 +37,12 @@ test.describe("Security - Authentication", () => {
test("Login without password returns proper error", async () => {
const ctx = await request.newContext();
+ const { captcha_id, captcha_answer } = await solveCaptcha();
const res = await ctx.post(`${API_BASE}/auth/login`, {
data: {
email: "test@example.com",
+ captcha_id,
+ captcha_answer,
},
});
@@ -76,10 +86,13 @@ test.describe("Security - Rate Limiting", () => {
let rateLimited = false;
for (let i = 0; i < 5; i++) {
+ const { captcha_id, captcha_answer } = await solveCaptcha();
const res = await ctx.post(`${API_BASE}/auth/login`, {
data: {
email: uniqueEmail,
password: "wrongpassword",
+ captcha_id,
+ captcha_answer,
},
});
if (res.status() === 429) {
@@ -89,10 +102,13 @@ test.describe("Security - Rate Limiting", () => {
}
if (!rateLimited) {
+ const { captcha_id, captcha_answer } = await solveCaptcha();
const res = await ctx.post(`${API_BASE}/auth/login`, {
data: {
email: uniqueEmail,
password: "anypassword",
+ captcha_id,
+ captcha_answer,
},
});
if (res.status() === 429) {
@@ -107,10 +123,13 @@ test.describe("Security - Rate Limiting", () => {
test("AI endpoints rate limit after daily quota exceeded", async () => {
const ctx = await request.newContext();
+ const { captcha_id, captcha_answer } = await solveCaptcha();
const loginRes = await ctx.post(`${API_BASE}/auth/login`, {
data: {
email: "testcompany@example.com",
password: "TestPassword123!",
+ captcha_id,
+ captcha_answer,
},
});
@@ -148,10 +167,13 @@ test.describe("Security - Rate Limiting", () => {
test.describe("Security - Input Validation", () => {
test("SQL injection in login email is handled safely", async () => {
const ctx = await request.newContext();
+ const { captcha_id, captcha_answer } = await solveCaptcha();
const res = await ctx.post(`${API_BASE}/auth/login`, {
data: {
email: "' OR '1'='1",
password: "anything",
+ captcha_id,
+ captcha_answer,
},
});
@@ -162,10 +184,13 @@ test.describe("Security - Input Validation", () => {
test("XSS attempt in login email is handled safely", async () => {
const ctx = await request.newContext();
+ const { captcha_id, captcha_answer } = await solveCaptcha();
const res = await ctx.post(`${API_BASE}/auth/login`, {
data: {
email: "@example.com",
password: "password",
+ captcha_id,
+ captcha_answer,
},
});
@@ -175,11 +200,14 @@ test.describe("Security - Input Validation", () => {
test("Very long input is handled without crash", async () => {
const ctx = await request.newContext();
const longString = "a".repeat(10000);
+ const { captcha_id, captcha_answer } = await solveCaptcha();
const res = await ctx.post(`${API_BASE}/auth/login`, {
data: {
email: `${longString}@example.com`,
password: longString,
+ captcha_id,
+ captcha_answer,
},
});
@@ -199,10 +227,13 @@ test.describe("Security - CORS Headers", () => {
test.describe("Security - Authorization", () => {
test("User cannot access admin endpoints with regular user token", async () => {
const ctx = await request.newContext();
+ const { captcha_id, captcha_answer } = await solveCaptcha();
const loginRes = await ctx.post(`${API_BASE}/auth/login`, {
data: {
email: "testtutora2026@example.com",
password: "Test1234!",
+ captcha_id,
+ captcha_answer,
},
});
@@ -229,11 +260,14 @@ test.describe("Security - Authorization", () => {
test.describe("Security - Response Headers", () => {
test("API does not leak sensitive information in error responses", async () => {
const ctx = await request.newContext();
+ const { captcha_id, captcha_answer } = await solveCaptcha();
const res = await ctx.post(`${API_BASE}/auth/login`, {
data: {
email: "test@example.com",
password: "wrongpassword",
+ captcha_id,
+ captcha_answer,
},
});
diff --git a/tests/e2e/signup-verification-submission.spec.ts b/tests/e2e/signup-verification-submission.spec.ts
index 889adef..9f239aa 100644
--- a/tests/e2e/signup-verification-submission.spec.ts
+++ b/tests/e2e/signup-verification-submission.spec.ts
@@ -183,7 +183,7 @@ async function wireApiMock(
}
async function runSignup(page: any, scenario: SignupScenario, email: string) {
- await page.goto(`http://localhost:3000${scenario.signupUrl}`);
+ await page.goto(scenario.signupUrl);
await page.fill("#first-name", "Ari");
await page.fill("#last-name", "Tester");
@@ -271,7 +271,7 @@ test.describe("Signup and verification submission by role", () => {
await runSignup(page, scenario, email);
await seedSession(page, scenario, email);
- await page.goto(`http://localhost:3000/dashboard?role=${scenario.dashboardRole}`);
+ await page.goto(`/dashboard?role=${scenario.dashboardRole}`);
await page.getByRole("button", { name: /my profile/i }).click();
const submitButton = page.getByRole("button", { name: /submit for verification/i });
@@ -292,7 +292,7 @@ test.describe("Signup and verification submission by role", () => {
await runSignup(page, scenario, email);
await seedSession(page, scenario, email);
- await page.goto(`http://localhost:3000/dashboard?role=${scenario.dashboardRole}`);
+ await page.goto(`/dashboard?role=${scenario.dashboardRole}`);
await page.getByRole("button", { name: /my profile/i }).click();
const submitButton = page.getByRole("button", { name: /submit for verification/i });
diff --git a/tests/e2e/visual/dashboard-role.spec.ts b/tests/e2e/visual/dashboard-role.spec.ts
index 259e54a..9818a80 100644
--- a/tests/e2e/visual/dashboard-role.spec.ts
+++ b/tests/e2e/visual/dashboard-role.spec.ts
@@ -1,7 +1,7 @@
import { test, expect, Page } from "@playwright/test";
async function loginViaApi(page: Page, email: string, password: string): Promise<{ access_token: string; active_role: string }> {
- const res = await page.request.post("http://localhost:3000/api/auth/login", {
+ const res = await page.request.post("/api/auth/login", {
data: { email, password },
headers: { "Content-Type": "application/json", Accept: "application/json" },
});
@@ -17,7 +17,7 @@ async function loginViaApi(page: Page, email: string, password: string): Promise
}
async function injectAuth(page: Page, email: string, token: string, role: string) {
- await page.goto("http://localhost:3000/");
+ await page.goto("/");
await page.evaluate(
({ email, token, role }) => {
const payload = {
@@ -54,7 +54,7 @@ test.describe("Dashboard Role Resolution", () => {
await injectAuth(page, email, access_token, active_role);
// Step 3: Navigate to dashboard with role param
- await page.goto(`http://localhost:3000/dashboard?role=${active_role}`);
+ await page.goto(`/dashboard?role=${active_role}`);
await page.waitForLoadState("networkidle");
await page.waitForTimeout(3000); // Wait for effects and bundle load
@@ -87,7 +87,7 @@ test.describe("Dashboard Role Resolution", () => {
const password = "Test1234!";
// First check if this email has PHOTOGRAPHER role
- const checkRes = await page.request.post("http://localhost:3000/api/auth/check-email", {
+ const checkRes = await page.request.post("/api/auth/check-email", {
data: { email },
headers: { "Content-Type": "application/json", Accept: "application/json" },
});
@@ -112,14 +112,14 @@ test.describe("Dashboard Role Resolution", () => {
} catch {
console.log(`[Login] ${email} login failed, trying API login with different approach`);
// Try without password for discovery
- const checkData = await (await page.request.post("http://localhost:3000/api/auth/check-email", {
+ const checkData = await (await page.request.post("/api/auth/check-email", {
data: { email },
headers: { "Content-Type": "application/json", Accept: "application/json" },
})).json();
console.log(`[Check Email Result]:`, checkData);
// If we can't login, at least verify the role detection
- await page.goto("http://localhost:3000/login");
+ await page.goto("/login");
await page.fill("#login-email", email);
await page.waitForTimeout(500);
await page.screenshot({ path: "test-results/login-email-check.png" });
@@ -130,7 +130,7 @@ test.describe("Dashboard Role Resolution", () => {
await injectAuth(page, email, token, targetRole);
- await page.goto(`http://localhost:3000/dashboard?role=${targetRole}`);
+ await page.goto(`/dashboard?role=${targetRole}`);
await page.waitForLoadState("networkidle");
await page.waitForTimeout(3000);
@@ -155,7 +155,7 @@ test.describe("Dashboard Role Resolution", () => {
test("direct dashboard navigation with role param should show correct role", async ({ page }) => {
// This test just verifies the URL param is respected
- await page.goto("http://localhost:3000/dashboard?role=PHOTOGRAPHER");
+ await page.goto("/dashboard?role=PHOTOGRAPHER");
await page.waitForLoadState("networkidle");
await page.waitForTimeout(2000);
diff --git a/tests/e2e/visual/debug-dashboard.spec.ts b/tests/e2e/visual/debug-dashboard.spec.ts
index 393533c..06825a8 100644
--- a/tests/e2e/visual/debug-dashboard.spec.ts
+++ b/tests/e2e/visual/debug-dashboard.spec.ts
@@ -5,7 +5,7 @@ test("API login shows correct TUTOR dashboard", async ({ page }) => {
page.on("pageerror", (err) => errors.push(err.message));
// Login via API
- const loginRes = await page.request.post("http://localhost:3000/api/auth/login", {
+ const loginRes = await page.request.post("/api/auth/login", {
data: { email: "testtutora2026@example.com", password: "Test1234!" },
headers: { "Content-Type": "application/json", Accept: "application/json" },
});
@@ -14,7 +14,7 @@ test("API login shows correct TUTOR dashboard", async ({ page }) => {
const role = loginData.user?.active_role || "TUTOR";
// Inject auth
- await page.goto("http://localhost:3000/");
+ await page.goto("/");
await page.waitForLoadState("networkidle");
await page.evaluate(
({ token, role }) => {
@@ -37,7 +37,7 @@ test("API login shows correct TUTOR dashboard", async ({ page }) => {
);
// Navigate to dashboard
- await page.goto(`http://localhost:3000/dashboard?role=${role}`);
+ await page.goto(`/dashboard?role=${role}`);
await page.waitForLoadState("domcontentloaded");
await page.waitForTimeout(3000);
await page.screenshot({ path: "test-results/dashboard-tutor-check.png", fullPage: true });