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_modulesclient/disttest-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:
- tells Playwright where your test files are
- builds the frontend
- starts the backend
- 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:
.gitignore- project runs normally
- test tools installed
- test commands work
- 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:
- success case
Example:GET /api/itemsreturns a list - failure / invalid case
Example:POST /api/itemsrejects missing title - error handling case
Example: route returns500if database call fails
Backend example
Example route test idea:
- When
GET /api/paperssucceeds, it should return status200 - 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:
- loading state
Example: page shows “Loading…” - success state
Example: fetched records appear in the table - 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:
- page loads
- main table/list/heading appears
- 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:
- compare it to your real code
- make sure names, routes, and labels actually exist
- run it immediately
- fix one error at a time
- 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
fetchcorrectly? - 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.jsin 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.ymlexists- 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.
| # | Area | Criteria | Score |
|---|---|---|---|
| 1 | Backend Tests | 3 working tests (success, failure, validation) | /20 |
| 2 | Frontend Tests | 3 working tests (loading, data render, error/interaction) | /20 |
| 3 | E2E Tests | 3 Playwright tests (page load + basic interaction) | /20 |
| 4 | CI (GitHub) | Workflow runs all tests and completes successfully | /20 |
| 5 | Organization | Clean structure, correct files, proper .gitignore | /10 |
| 6 | Understanding | Clear explanation of tests, setup, and one issue resolved | /10 |
| Total | /100 |
