diff --git a/.github/workflows/e2e-without-release.yml b/.github/workflows/e2e-without-release.yml new file mode 100644 index 0000000..da82b71 --- /dev/null +++ b/.github/workflows/e2e-without-release.yml @@ -0,0 +1,88 @@ +name: CI/CD Workflow --> Deploy and Run E2E Tests + +on: + pull_request: + push: + branches: + - main + +jobs: + deploy-staging: + name: Deploy to Vercel Staging + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Pull Vercel Environment Information + run: | + vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN_MY_PROJECT }} + + - name: Build Project Artifacts + run: | + vercel build --token=${{ secrets.VERCEL_TOKEN_MY_PROJECT }} + + - name: Deploy to Staging + id: deploy + run: | + DEPLOY_URL=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN_MY_PROJECT }} | tail -1) + echo "DEPLOYMENT_URL=${DEPLOY_URL}" >> $GITHUB_ENV + echo "Deployed to: $DEPLOY_URL" + + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + + e2e-tests: + name: Run E2E Tests on Staging + runs-on: ubuntu-latest + needs: deploy-staging + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Install pnpm + run: npm install -g pnpm + + - name: Install Dependencies + run: pnpm install + + - name: Install Playwright + run: pnpm playwright install + + - name: Run Playwright Tests + run: pnpm playwright test + env: + BASE_URL: ${{ env.DEPLOYMENT_URL }} + + deploy-prod: + name: Deploy to Vercel Production + runs-on: ubuntu-latest + needs: e2e-tests + if: github.ref == 'refs/heads/main' + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Pull Vercel Environment Information + run: | + vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN_MY_PROJECT }} + + - name: Build Project Artifacts + run: | + vercel build --prod --token=${{ secrets.VERCEL_TOKEN_MY_PROJECT }} + + - name: Deploy to Production + run: | + vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN_MY_PROJECT }} + + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 572fdb9..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,125 +0,0 @@ -name: CI/CD Workflow --> Deploy and Run E2E Tests - -on: - pull_request: - push: - branches: - - main - release: - types: [created] - -jobs: - deploy: - name: Deploy to Vercel - runs-on: ubuntu-latest - - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Install Vercel CLI - run: npm install --global vercel@latest - - - name: Pull Vercel Environment Information - run: | - if [[ "${{ github.event_name }}" == "release" ]]; then - vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN_MY_PROJECT }} - else - vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN_MY_PROJECT }} - fi - - - name: Build Project Artifacts - run: | - if [[ "${{ github.event_name }}" == "release" ]]; then - vercel build --prod --token=${{ secrets.VERCEL_TOKEN_MY_PROJECT }} - else - vercel build --token=${{ secrets.VERCEL_TOKEN_MY_PROJECT }} - fi - - - name: Deploy to Vercel - id: deploy - run: | - if [[ "${{ github.event_name }}" == "release" ]]; then - DEPLOY_URL=$(vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN_MY_PROJECT }} | tail -1) - else - DEPLOY_URL=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN_MY_PROJECT }} | tail -1) - fi - echo "DEPLOYMENT_URL=${DEPLOY_URL}" >> $GITHUB_ENV - echo "Deployed to: $DEPLOY_URL" - - env: - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - - e2e: - name: Run E2E Tests - runs-on: ubuntu-latest - needs: deploy - - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Install pnpm - run: npm install -g pnpm - - - name: Install Dependencies - run: pnpm install - - - name: Install Playwright - run: pnpm playwright install - - - name: Run Playwright Tests - run: pnpm playwright test - env: - BASE_URL: ${{ env.DEPLOYMENT_URL }} - - - #notify: - # name: Notify Team on Failure - # runs-on: ubuntu-latest - # needs: e2e - # if: failure() - # steps: - # - name: Send Slack Notification - # uses: slackapi/slack-github-action@v1.24.0 - # with: - # payload: | - # { - # "text": "🚨 Production deployment failed! Check logs: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - # } - # env: - # SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} - -# rollback: - # name: Rollback on Failure - # runs-on: ubuntu-latest - # needs: e2e - # if: failure() - - # steps: - # - name: Checkout Code - # uses: actions/checkout@v3 - - # - name: Install Vercel CLI - # run: npm install --global vercel@latest - - # - name: Authenticate Vercel CLI - # run: vercel login --token=${{ secrets.VERCEL_TOKEN_MY_PROJECT }} - - # - name: Get Last Successful Production Deployment - # id: get_last_deployment - # run: | - # echo "Fetching last successful production deployment..." - # LAST_DEPLOYMENT=$(vercel list --prod --token=${{ secrets.VERCEL_TOKEN_MY_PROJECT }} | grep -Eo 'https://[a-zA-Z0-9.-]+\.vercel\.app' | tail -1) - # if [[ -z "$LAST_DEPLOYMENT" ]]; then - # echo "❌ No previous successful deployment found! Rollback failed." - # exit 1 - # fi - # echo "✅ Last successful deployment found: $LAST_DEPLOYMENT" - # echo "LAST_DEPLOYMENT=$LAST_DEPLOYMENT" >> $GITHUB_ENV - - # - name: Rollback to Last Working Deployment - # run: | - # echo "Rolling back to: $LAST_DEPLOYMENT" - # vercel promote $LAST_DEPLOYMENT --token=${{ secrets.VERCEL_TOKEN_MY_PROJECT }} - # echo "✅ Rollback successful! Restored $LAST_DEPLOYMENT" diff --git a/src/app/form/page.tsx b/src/app/form/page.tsx index ab6cf0e..20c067f 100644 --- a/src/app/form/page.tsx +++ b/src/app/form/page.tsx @@ -1,85 +1,16 @@ -// 'use client'; - -// import { useState } from 'react'; - -// export default function Form() { -// const [name, setName] = useState(''); -// const [email, setEmail] = useState(''); -// const [message, setMessage] = useState(''); -// const [loading, setLoading] = useState(false); -// const [error, setError] = useState(''); - -// const handleSubmit = async (e: React.FormEvent) => { -// e.preventDefault(); -// setLoading(true); -// setMessage(''); -// setError(''); - -// try { -// const res = await fetch('/api/submit', { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify({ name, email }), -// }); - -// if (!res.ok) throw new Error('Failed to submit'); -// const data = await res.json(); -// setMessage(data.message); -// } catch { -// setError('Submission failed. Please try again.'); -// } finally { -// setLoading(false); -// } -// }; - -// const handleReset = () => { -// setName(''); -// setEmail(''); -// setMessage(''); -// setError(''); -// }; - -// return ( -//
-//

Form Page

-//
-// setName(e.target.value)} -// /> -// setEmail(e.target.value)} -// /> -// -// -//
-// {message &&

{message}

} -// {error &&

{error}

} -//
-// ); -// } - - 'use client'; import { useState } from 'react'; export default function Form() { - const [formData, setFormData] = useState({ name: '', email: '' }); + const [formData, setFormData] = useState({ name: '', email: '', phone: '' }); const [message, setMessage] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const validateInputs = () => { - if (!formData.name.trim() || !formData.email.trim()) { - setError('Both Name and Email are required'); + if (!formData.name.trim() || !formData.email.trim() || !formData.phone.trim()) { + setError('All fields are required'); return false; } if (formData.name.length > 50) { @@ -90,6 +21,10 @@ export default function Form() { setError('Invalid email format'); return false; } + if (!/^\d{10}$/.test(formData.phone)) { + setError('Phone number must be exactly 10 digits'); + return false; + } return true; }; @@ -116,8 +51,8 @@ export default function Form() { }); if (!res.ok) throw new Error('Failed to submit'); - const data = await res.json(); - setMessage(data.message); + await res.json(); + setMessage(`✅ Hello ${formData.name}, your form has been submitted successfully!`); } catch { setError('Submission failed. Please try again.'); } finally { @@ -126,7 +61,7 @@ export default function Form() { }; const handleReset = () => { - setFormData({ name: '', email: '' }); + setFormData({ name: '', email: '', phone: '' }); setMessage(''); setError(''); }; @@ -135,25 +70,21 @@ export default function Form() {

Form Page

- - - + + + + + + + + + +
+

{error}

-

{message}

+

{message}

); } diff --git a/tests/form.spec.ts b/tests/form.spec.ts index dbd4356..2c209e7 100644 --- a/tests/form.spec.ts +++ b/tests/form.spec.ts @@ -1,34 +1,3 @@ -// import { test, expect } from '@playwright/test'; - -// test('Form submission works correctly', async ({ page }) => { -// await page.goto('/form'); - -// // Ensure form elements are present -// await expect(page.locator('input[placeholder="Enter your name"]')).toBeVisible(); -// await expect(page.locator('input[placeholder="Enter your email"]')).toBeVisible(); -// await expect(page.locator('button[type="submit"]')).toHaveText('Submit'); - -// // Fill out the form -// await page.fill('input[placeholder="Enter your name"]', 'John Doe'); -// await page.fill('input[placeholder="Enter your email"]', 'john@example.com'); - -// // Submit the form -// await page.click('button[type="submit"]'); - -// // Wait for success message -// await expect(page.locator('p')).toHaveText('Thank you, John Doe!'); -// }); - -// test('Shows validation error if fields are missing', async ({ page }) => { -// await page.goto('/form'); - -// // Try to submit without filling anything -// await page.click('button[type="submit"]'); - -// // Expect an error message -// await expect(page.locator('p')).toHaveText('Submission failed. Please try again.'); -// }); - import { test, expect } from '@playwright/test'; test.describe('Form Submission Tests', () => { @@ -38,17 +7,19 @@ test.describe('Form Submission Tests', () => { // Check form elements exist await expect(page.locator('input[name="name"]')).toBeVisible(); await expect(page.locator('input[name="email"]')).toBeVisible(); + await expect(page.locator('input[name="phone"]')).toBeVisible(); await expect(page.locator('button[type="submit"]')).toHaveText('Submit'); // Fill out the form await page.fill('input[name="name"]', 'John Doe'); await page.fill('input[name="email"]', 'john@example.com'); + await page.fill('input[name="phone"]', '1234567890'); // Submit the form await page.click('button[type="submit"]'); // Expect success message - await expect(page.locator('#message')).toHaveText(`Hello John Doe, your form has been submitted successfully!`); + await expect(page.locator('#message')).toHaveText(`✅ Hello John Doe, your form has been submitted successfully!`); }); test('❌ Shows validation error if fields are missing', async ({ page }) => { @@ -58,36 +29,44 @@ test.describe('Form Submission Tests', () => { await page.click('button[type="submit"]'); // Expect validation error - await expect(page.locator('#error')).toHaveText('Both Name and Email are required'); + await expect(page.locator('#error')).toHaveText('All fields are required'); }); test('❌ Shows validation error for invalid email', async ({ page }) => { await page.goto('/form'); - // Fill in the name field await page.fill('input[name="name"]', 'John Doe'); - // Enter an invalid email await page.fill('input[name="email"]', 'invalid-email'); + await page.fill('input[name="phone"]', '1234567890'); - // Submit the form await page.click('button[type="submit"]'); - // Expect email validation error await expect(page.locator('#error')).toHaveText('Invalid email format'); }); + test('❌ Shows validation error for invalid phone number', async ({ page }) => { + await page.goto('/form'); + + await page.fill('input[name="name"]', 'John Doe'); + await page.fill('input[name="email"]', 'john@example.com'); + await page.fill('input[name="phone"]', '98765'); + + await page.click('button[type="submit"]'); + + await expect(page.locator('#error')).toHaveText('Phone number must be exactly 10 digits'); + }); + test('🔄 Reset button clears the form', async ({ page }) => { await page.goto('/form'); - // Fill out the form await page.fill('input[name="name"]', 'John Doe'); await page.fill('input[name="email"]', 'john@example.com'); + await page.fill('input[name="phone"]', '1234567890'); - // Click reset await page.click('button[type="button"]'); - // Expect fields to be cleared await expect(page.locator('input[name="name"]')).toHaveValue(''); await expect(page.locator('input[name="email"]')).toHaveValue(''); + await expect(page.locator('input[name="phone"]')).toHaveValue(''); }); });