Follow-Up Assignment: Add Testing and CI to Your Project

Goal

By Friday, you will add a basic but real testing and CI setup to your own project.

Your project should include:

  • 3 backend test routines
  • 3 frontend test routines
  • 3 whole-app test routines
  • a GitHub Actions workflow that runs them

You may use AI to help, but you must understand what was generated and be able to explain it during your demo.

This is your first time doing this, so the point is not to produce an enormous professional test suite. The point is to learn:

  • what kinds of behaviors should be tested
  • what shape each kind of test takes
  • how to use AI productively to help generate correct tests

We will assume your project has at least:

  • one database table
  • a CRUD interface for that table
  • a React frontend
  • a backend API

That is enough to produce meaningful tests.


First: Protect Your Repository

Before you add tests, configs, or CI files, you must make sure Git is not going to track generated files.

Create a .gitignore file in the project root.

Use at least this:

node_modules/
client/node_modules/
server/node_modules/

client/dist/

test-results/
playwright-report/

.env
server/.env

npm-debug.log*

Now run:

git status

Look carefully at the output.

You should not see:

  • node_modules
  • client/dist
  • test-results
  • .env

If you do see those, stop and fix .gitignore before continuing.

This matters. If you stage files too early, Git will happily commit thousands of generated files that do not belong in your repository.


Next: Prepare Your System for Testing

Your project is a single-port system.

That means:

  • your backend runs on one port: 41xx
  • your built React frontend is served by the backend
  • Playwright should test that same single running app

Do not set this up as a separate frontend server plus backend server for the final testing workflow.

We are testing the app the way it is really deployed.


Step 1: Make Sure the App Works Normally

Before adding tests, confirm that your project already works as a normal application.

Backend

Make sure your backend starts on your assigned 41xx port.

Frontend

Make sure your frontend builds successfully.

Full app

Make sure that after building the frontend and starting the backend, you can open the app in the browser and use it.

If the project does not already run correctly, fix that first.


Step 2: Confirm the Single-Port Structure

Your backend should be the thing that serves the built frontend.

That means your app should work like this:

Browser
  ↓
http://10.192.145.179:41xx
  ↓
Express backend
  ├─ serves API
  └─ serves built React frontend

Your React app should not depend on a second frontend server for the final smoke test.

Your frontend code should normally use API calls like:

fetch('/api/your-route')

not hardcoded URLs like:

fetch('http://localhost:4000/api/your-route')

because this assignment assumes one deployed app on one port.


Step 3: Install the Testing Tools

Backend tools

In your backend folder, install:

npm install -D vitest supertest

Frontend tools

In your frontend folder, install:

npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom 

Whole-app testing tools

From the project root, install:

npm install -D @playwright/test
npx playwright install

Step 4: Add Test Scripts

Make sure your backend package.json has:

"scripts": {
  "start": "node index.js",
  "test": "vitest run"
}

Make sure your frontend package.json has:

"scripts": {
  "build": "vite build",
  "test": "vitest run"
}

In the project root, create or update package.json so you can run everything from one command:

{
  "scripts": {
    "test:backend": "cd server && npm test",
    "test:frontend": "cd client && npm test",
    "test:e2e": "playwright test",
    "test": "npm run test:backend && npm run test:frontend && npm run test:e2e"
  }
}

Use your actual folder names if they differ.


Step 5: Prepare the Frontend Test Environment

Create this file:

client/src/test/setup.js

with:

import ‘@testing-library/jest-dom/vitest’;

Then update client/vite.config.js to include frontend test support.

If your app runs on backend port 41xx, your dev proxy should point there:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/api': 'http://localhost:41xx'
    }
  },
  test: {
    environment: 'jsdom',
    setupFiles: './src/test/setup.js'
  }
});

Replace 41xx with your actual project port.


Step 6: Prepare the Whole-App Test Environment

Create this file in the project root:

playwright.config.js

Use this pattern for a single-port app:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',

  use: {
    baseURL: 'http://127.0.0.1:41xx'
  },

  webServer: {
    command: 'cd client && npm run build && cd ../server && npm start',
    url: 'http://127.0.0.1:41xx',
    reuseExistingServer: true,
    timeout: 120 * 1000
  }
});

Replace 41xx with your actual port.

This does four things:

  1. tells Playwright where your test files are
  2. builds the frontend
  3. starts the backend
  4. runs browser tests against the real single-port app

Step 7: Check the Test Infrastructure Before Writing Lots of Tests

Before writing your real tests, make sure the infrastructure itself is working.

You should be able to run these separately:

cd server && npm test
cd client && npm test
npx playwright test

Then you should be able to run:

npm test

If these commands do not work, do not move on yet.

Fix the setup first.


Important Rule

Do not try to write a large number of tests before the infrastructure works.

The correct order is:

  1. .gitignore
  2. project runs normally
  3. test tools installed
  4. test commands work
  5. then write actual tests

Level 1: Backend Tests

Common things to test in the backend

If your backend supports CRUD on a database table, common backend test targets include:

  • validation of required fields
  • rejection of invalid input
  • successful GET route
  • successful POST route
  • update route behavior
  • delete route behavior
  • correct error response when something fails
  • helper or normalization functions

Good backend test ideas

Pick three routines such as:

  1. success case
    Example: GET /api/items returns a list
  2. failure / invalid case
    Example: POST /api/items rejects missing title
  3. error handling case
    Example: route returns 500 if database call fails

Backend example

Example route test idea:

  • When GET /api/papers succeeds, it should return status 200
  • When the database throws an error, it should return 500

Example pattern:

import { describe, it, expect, vi, beforeEach } from 'vitest';
import request from 'supertest';

vi.mock('../db.js', () => {
  return {
    default: {
      query: vi.fn()
    }
  };
});

import app from '../app.js';
import pool from '../db.js';

describe('GET /api/papers', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('returns papers', async () => {
    pool.query.mockResolvedValueOnce([[
      { id: 1, title: 'Paper A' }
    ]]);

    const response = await request(app).get('/api/papers');

    expect(response.status).toBe(200);
    expect(response.body).toHaveLength(1);
  });

  it('returns 500 if the database fails', async () => {
    pool.query.mockRejectedValueOnce(new Error('DB failed'));

    const response = await request(app).get('/api/papers');

    expect(response.status).toBe(500);
  });
});

How to use AI for backend tests

Do not ask:

“Write all my backend tests.”

That is too vague.

Instead, give AI:

  • the relevant file
  • what behavior you want tested
  • the testing tools you are using
  • the constraint that it should not invent behavior

Good backend AI prompt

Here is my backend route file and any helper files it depends on.

I need a Vitest test file for this route.
Write one small test routine that matches the current code exactly.
Use mocked database behavior if needed.
Do not invent new features.
Include:
1. one success case
2. one failure or invalid-input case
Explain briefly what the test is checking.

Another good backend AI prompt

Here is my backend helper or validation file.
Write 3 small Vitest test routines for it:
- one valid input case
- one invalid input case
- one edge case

Do not rewrite the source file.
Return only the test file.

Backend requirement for this assignment

Create 3 backend test routines.

They may all be in one file or spread across files.

Good examples:

  • GET returns data
  • POST rejects bad input
  • route handles DB failure

Level 2: Frontend Tests

Common things to test in the frontend

For a React CRUD interface, common frontend test targets include:

  • loading state appears
  • fetched data appears
  • error state appears
  • button click opens form
  • form fields appear
  • table rows render correctly
  • delete button appears
  • validation message appears

Good frontend test ideas

Pick three routines such as:

  1. loading state
    Example: page shows “Loading…”
  2. success state
    Example: fetched records appear in the table
  3. error state or interaction
    Example: error alert appears when fetch fails
    or
    Example: clicking “Add” opens a form

Frontend example

import { describe, it, expect, vi, afterEach } from 'vitest';
import { render, screen, waitFor, cleanup } from '@testing-library/react';
import App from './App';

afterEach(() => {
  cleanup();
  vi.unstubAllGlobals();
});

describe('App', () => {
  it('shows loading initially', () => {
    vi.stubGlobal('fetch', vi.fn(() => new Promise(() => {})));

    render(<App />);
    expect(screen.getByText(/loading/i)).toBeInTheDocument();
  });

  it('renders papers after fetch succeeds', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn(() =>
        Promise.resolve({
          ok: true,
          json: () =>
            Promise.resolve([
              {
                id: 1,
                title: 'Testing Paper'
              }
            ])
        })
      )
    );

    render(<App />);

    await waitFor(() => {
      expect(screen.getByText('Testing Paper')).toBeInTheDocument();
    });
  });

  it('shows error if fetch fails', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn(() => Promise.reject(new Error('Network error')))
    );

    render(<App />);

    await waitFor(() => {
      expect(screen.getByRole('alert')).toBeInTheDocument();
    });
  });
});

How to use AI for frontend tests

Give AI:

  • the component file
  • the expected visible behavior
  • the requirement to use React Testing Library + Vitest
  • the instruction not to rewrite the component

Good frontend AI prompt

Here is my React component.

Write a Vitest + React Testing Library test file for it.
I need 3 small test routines:
1. loading or initial render state
2. successful data render or main visible behavior
3. error or interaction case

Do not rewrite the component.
Mock fetch if needed.
Use visible UI behavior, not internal implementation details.

Another good frontend AI prompt

Here is my component.
I need one test routine for the behavior: clicking the Add button should show the form.

Use React Testing Library and Vitest.
Do not rewrite the component.
Return only the test code.

Frontend requirement for this assignment

Create 3 frontend test routines.

Good examples:

  • loading appears
  • data renders
  • error alert or form interaction works

Level 3: Whole-App Tests (Playwright)

Common things to test end-to-end

These are not unit tests. These test the full app as a user sees it.

Common whole-app targets for a CRUD app include:

  • main page loads
  • heading appears
  • table or list appears
  • click Add button and form opens
  • create item flow works
  • edit flow works
  • delete flow works

For this first assignment, keep it simple.

Good whole-app test ideas

Pick three routines such as:

  1. page loads
  2. main table/list/heading appears
  3. one basic interaction works
    • click Add button
    • form opens
    • or navigate to a page

Whole-app example

import { test, expect } from '@playwright/test';

test('main page loads', async ({ page }) => {
  await page.goto('/');
  await expect(page.locator('body')).toContainText(/papers/i);
});

A second example:

import { test, expect } from '@playwright/test';

test('Add button is visible', async ({ page }) => {
  await page.goto('/');
  await expect(page.getByRole('button', { name: /add/i })).toBeVisible();
});

How to use AI for whole-app tests

Give AI:

  • your app architecture
  • the base page behavior
  • the exact interaction you want tested
  • the instruction to keep it small

Good Playwright AI prompt

My project is a React frontend with an Express backend.
It runs as a single-port app on port 41xx, with the backend serving the built frontend.
I am using Playwright.
Write 3 small Playwright test routines for a CRUD app:
1. main page loads
2. main heading or table appears
3. clicking Add opens the form

Keep them simple and realistic.
Do not invent routes or labels that are not in my code.

Another good Playwright AI prompt

Here is my React page code.
Write one Playwright smoke test and two simple interaction tests that match the actual visible UI text and buttons.
Return only the test file.

Whole-app requirement for this assignment

Create 3 Playwright test routines.

Good examples:

  • page loads
  • heading/table appears
  • Add button click opens form

CI Requirement

After the three levels of testing are working locally, add GitHub Actions so they run automatically.

Your GitHub workflow must run:

  • backend tests
  • frontend tests
  • Playwright tests

You do not need advanced deployment or database containers yet.


GitHub Actions example

Create:

.github/workflows/ci.yml

Use a workflow like this:

name: CI

on:
  push:
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Check out repo
        uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install root dependencies
        run: |
          if [ -f package.json ]; then npm install; fi

      - name: Install client dependencies
        run: |
          cd client
          npm install

      - name: Install server dependencies
        run: |
          cd server
          npm install

      - name: Run server tests
        run: |
          cd server
          npm test

      - name: Run client tests
        run: |
          cd client
          npm test

      - name: Build client
        run: |
          cd client
          npm run build

      - name: Install Playwright browsers
        run: |
          npx playwright install --with-deps

      - name: Run whole-app tests
        run: |
          npm run test:e2e

What to do when AI gives you something wrong

This will happen.

When AI generates a test:

  1. compare it to your real code
  2. make sure names, routes, and labels actually exist
  3. run it immediately
  4. fix one error at a time
  5. do not accept invented behavior

A pretty test file that doesn’t match your code is useless.


What to do when things go bad

If backend tests fail

Check:

  • are imports correct?
  • are mocked dependencies correct?
  • does the route/helper actually export what the test assumes?
  • did AI invent fields or routes that do not exist?

If frontend tests fail

Check:

  • is the component rendering what the test expects?
  • did AI look for the wrong text?
  • are you mocking fetch correctly?
  • are you waiting for async updates?

If whole-app tests fail

Check:

  • does the app run manually?
  • is the server port correct?
  • is playwright.config.js in the project root?
  • is the test in the right tests/ folder?
  • is the browser dependency installed?

If GitHub Actions does not run

Check:

  • .github/workflows/ci.yml exists
  • YAML indentation is correct
  • the workflow is committed
  • GitHub Actions is enabled

What You Must Show on Friday

You should be able to show:

  • 3 backend test routines
  • 3 frontend test routines
  • 3 whole-app test routines
  • GitHub Actions running
  • one example of AI helping you generate a test
  • one bug or problem you had to fix

What You Will Turn In

Submit either:

  • a zip of your full project, or
  • the full project in GitHub

Your project must include:

  • backend test files
  • frontend test files
  • whole-app test files
  • playwright.config.js
  • .github/workflows/ci.yml

What I Will Be Looking For

I am not looking for huge sophistication.

I am looking for:

  • correct use of the testing environment
  • meaningful test choices
  • appropriate use of AI
  • working CI
  • understanding of what your tests are doing

One Final Rule

Keep it small, real, and explainable.

Do not try to impress me with quantity.
Impress me with correctness.

#AreaCriteriaScore
1Backend Tests3 working tests (success, failure, validation)/20
2Frontend Tests3 working tests (loading, data render, error/interaction)/20
3E2E Tests3 Playwright tests (page load + basic interaction)/20
4CI (GitHub)Workflow runs all tests and completes successfully/20
5OrganizationClean structure, correct files, proper .gitignore/10
6UnderstandingClear explanation of tests, setup, and one issue resolved/10
Total/100
Scroll to Top