A Flask web application that demonstrates A/B testing with session stickiness and click tracking. When a user logs in, they are randomly assigned to either Version A or Version B of the home page. Their button clicks are logged to a JSON file along with the IP address, timestamp, and which version they saw.
- A static intro page introduces the project and links to the login feature
- The login form takes any username (no real password required for this demo)
- On login, a random 50/50 coin flip assigns the user to Version A or Version B
- The assignment is stored in a session cookie so the user keeps seeing the same version on every visit
- Each home page has tracked buttons that log clicks to
data/clicks.json - A
/statspage shows all the captured click data
- Python 3.8+ with Flask for the backend
- HTML and CSS for the frontend
- Vanilla JavaScript (
fetch) for the click tracking calls - JSON file for storing click data (no database setup needed)
ab-testing-webapp/
├── app.py # Main Flask application
├── requirements.txt # Python dependencies
├── README.md # This file
├── data/
│ └── clicks.json # Click event storage
├── templates/
│ ├── intro.html # Static intro page
│ ├── login.html # Login form
│ ├── home_a.html # Version A home page
│ ├── home_b.html # Version B home page
│ └── stats.html # View captured click data
└── static/
├── style_main.css # Styles for intro, login, stats
├── style_a.css # Version A styling (light theme, serif body, blue accent, top-nav)
└── style_b.css # Version B styling (dark mode, sans-serif, amber accent, sidebar nav)
git clone <your-repo-url>
cd ab-testing-webapppython -m venv venv
source venv/bin/activate # On macOS/Linux
venv\Scripts\activate # On Windowspip install -r requirements.txtpython app.pyGo to http://127.0.0.1:5000
- Open
http://127.0.0.1:5000and click "Go to Login" - Enter any username and submit
- You will be randomly placed on Version A (light theme, serif body, top navigation) or Version B (dark mode, amber accent, sidebar navigation)
- Click some buttons and watch the status update
- Visit
http://127.0.0.1:5000/statsto see your tracked clicks - To get reassigned for testing, click "Log out" or clear your browser cookies, then log in again
- Try opening the app in a different browser or in private/incognito mode to land on the other version
In app.py, the assign_version() function uses Python's random.random() to flip a virtual coin:
def assign_version():
return "A" if random.random() < 0.5 else "B"The result is stored in session["version"]. Flask's session is signed with the secret key and sent as a cookie to the browser. On every request, Flask reads the cookie and pulls out the version, so the user keeps seeing the same one. This is how stickiness works without a database.
Every button click triggers a POST to /track with the button's structural position ID and visible label. The server then logs:
- username (whatever was entered at login)
- version (A or B)
- position (structural ID like
nav_1,nav_2,nav_3,cta_primary,cta_secondary) - label (the visible button text, e.g. "Start Now" or "View Profile")
- ip_address (
request.remote_addrfrom Flask) - timestamp (ISO format date and time)
These all go into data/clicks.json as a list of objects.
Both versions of the home page have the same five buttons in the same structural roles (3 nav buttons + 2 CTA buttons). The position field captures the role (cta_primary, etc.) so you can compare, for example, "primary CTA clicks on Version A" vs "primary CTA clicks on Version B" directly — even if the button labels were different. The label field captures what the user actually saw, so you keep the human-readable context.
| Route | Method | What It Does |
|---|---|---|
/ |
GET | Static intro page |
/login |
GET | Show login form |
/login |
POST | Process login and assign A/B version |
/home |
GET | Render Version A or B based on session |
/track |
POST | Log a click event to the JSON file |
/stats |
GET | View all collected click data |
/logout |
GET | Clear session and return to intro |
- The
secret_keyinapp.pyis set to a placeholder. For a real deployment you would put it in an environment variable. - The JSON file approach is fine for a demo. For production you would swap this for SQLite or a real database.
- The login does not check passwords. This is by design for the demo. The focus is the A/B logic.
Author: CSP Student-M252