Skip to content

Commit 655f662

Browse files
authored
add e2e tests for the quiz app (#32)
* add e2e tests for the quiz app * remove unnecessary parts * tests working now hopefully * add e2e workflow * fix workflow * upload artifact in case of failure * run on schedule and workflow dispatch as well
1 parent 8a665b3 commit 655f662

File tree

3 files changed

+315
-0
lines changed

3 files changed

+315
-0
lines changed

.github/workflows/e2e.yml

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
name: End-to-End Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
schedule:
11+
# “At 00:00 on Sunday.”
12+
- cron: "0 0 * * 0"
13+
workflow_dispatch:
14+
15+
jobs:
16+
e2e-tests:
17+
name: Run E2E Tests
18+
runs-on: ubuntu-latest
19+
steps:
20+
- uses: actions/checkout@v4
21+
22+
- name: Set up Python
23+
uses: actions/setup-python@v5
24+
with:
25+
python-version: '3.11'
26+
27+
- name: Set up Node.js
28+
uses: actions/setup-node@v4
29+
with:
30+
node-version: '22'
31+
32+
- name: Start LocalStack
33+
uses: LocalStack/setup-localstack@main
34+
with:
35+
image-tag: 'latest'
36+
use-pro: 'true'
37+
configuration: LS_LOG=trace
38+
install-awslocal: 'true'
39+
env:
40+
LOCALSTACK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }}
41+
42+
- name: Deploy infrastructure
43+
run: |
44+
./bin/deploy.sh
45+
46+
- name: Install Playwright
47+
run: |
48+
cd tests/e2e
49+
pip install -r requirements.txt
50+
playwright install chromium
51+
playwright install-deps chromium
52+
53+
- name: Run E2E Tests
54+
run: |
55+
cd tests/e2e
56+
pytest -s test_quiz_flow.py
57+
env:
58+
AWS_DEFAULT_REGION: us-east-1
59+
AWS_ACCESS_KEY_ID: test
60+
AWS_SECRET_ACCESS_KEY: test
61+
62+
- name: Show localstack logs
63+
if: always()
64+
run: |
65+
localstack logs
66+
67+
- name: Send a Slack notification
68+
if: failure() || github.event_name != 'pull_request'
69+
uses: ravsamhq/notify-slack-action@v2
70+
with:
71+
status: ${{ job.status }}
72+
token: ${{ secrets.GITHUB_TOKEN }}
73+
notification_title: "{workflow} has {status_message}"
74+
message_format: "{emoji} *{workflow}* {status_message} in <{repo_url}|{repo}>"
75+
footer: "Linked Repo <{repo_url}|{repo}> | <{run_url}|View Workflow run>"
76+
notify_when: "failure"
77+
env:
78+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
79+
80+
- name: Generate a Diagnostic Report
81+
if: failure()
82+
run: |
83+
curl -s localhost:4566/_localstack/diagnose | gzip -cf > diagnose.json.gz
84+
85+
- name: Upload the Diagnostic Report
86+
if: failure()
87+
uses: actions/upload-artifact@v4
88+
with:
89+
name: diagnose.json.gz
90+
path: ./diagnose.json.gz

tests/e2e/requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pytest
2+
playwright
3+
boto3
4+
pytest-playwright

tests/e2e/test_quiz_flow.py

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import os
2+
import pytest
3+
import boto3
4+
from playwright.sync_api import sync_playwright, expect
5+
import time
6+
import random
7+
import string
8+
9+
@pytest.fixture(scope="session")
10+
def browser():
11+
with sync_playwright() as p:
12+
browser = p.chromium.launch(headless=True, slow_mo=1000)
13+
yield browser
14+
browser.close()
15+
16+
@pytest.fixture(scope="session")
17+
def page(browser):
18+
context = browser.new_context()
19+
page = context.new_page()
20+
yield page
21+
context.close()
22+
23+
@pytest.fixture(scope="session")
24+
def app_url():
25+
cloudfront = boto3.client('cloudfront', endpoint_url='http://localhost:4566')
26+
response = cloudfront.list_distributions()
27+
distribution_id = response['DistributionList']['Items'][0]['Id']
28+
return f"https://{distribution_id}.cloudfront.localhost.localstack.cloud"
29+
30+
def test_quiz_flow(page, app_url):
31+
# Enable verbose logging
32+
# page.set_viewport_size({"width": 1280, "height": 720})
33+
34+
# Navigate to home page
35+
print("\nNavigating to home page...")
36+
page.goto(app_url)
37+
expect(page.get_by_text("Welcome")).to_be_visible()
38+
39+
# Select AWS Quiz from public quizzes
40+
print("Selecting AWS Quiz...")
41+
quiz_select = page.get_by_label("Select a Public Quiz")
42+
quiz_select.click()
43+
page.get_by_text("AWS Quiz").click()
44+
45+
# Fill in user details
46+
username = ''.join(random.choices(string.ascii_letters + string.digits, k=8))
47+
print(f"Username: {username}")
48+
email = f"{username}@example.com"
49+
print(f"Email: {email}")
50+
print("Filling in user details...")
51+
username_input = page.get_by_label("Username")
52+
username_input.fill(username)
53+
email_input = page.get_by_label("Email (Optional)")
54+
email_input.fill(email)
55+
56+
# Start the quiz
57+
print("Starting quiz...")
58+
start_button = page.get_by_text("Start Playing")
59+
expect(start_button).to_be_enabled()
60+
start_button.click()
61+
62+
# Wait for first question to load
63+
print("Waiting for first question...")
64+
expect(page.get_by_text("Question 1 / 5")).to_be_visible()
65+
66+
# Answer all questions
67+
answers = [
68+
"B. Amazon EC2", # What is the primary compute service in AWS?
69+
"A. Simple Storage Service", # What does S3 stand for in AWS?
70+
"A. Amazon DynamoDB", # Which AWS service is a NoSQL database?
71+
"B. AWS Lambda", # Which AWS service lets you run code without provisioning servers?
72+
"B. Identity and Access Management" # What is IAM used for?
73+
]
74+
75+
for i, answer in enumerate(answers):
76+
print(f"Answering question {i + 1}...")
77+
# Wait for question to be visible
78+
expect(page.get_by_text(f"Question {i + 1} / 5")).to_be_visible()
79+
80+
# Select answer
81+
answer_radio = page.get_by_label(answer)
82+
expect(answer_radio).to_be_visible()
83+
answer_radio.click()
84+
85+
# Wait for next question or submit button
86+
time.sleep(2)
87+
88+
# Try to find score text with a more flexible approach
89+
score_element = page.get_by_text("Score", exact=False)
90+
if score_element:
91+
print(f"Found score element: {score_element.text_content()}")
92+
93+
# Click View Leaderboard
94+
print("Clicking leaderboard button...")
95+
leaderboard_button = page.get_by_text("View Leaderboard")
96+
expect(leaderboard_button).to_be_visible()
97+
leaderboard_button.click()
98+
time.sleep(10)
99+
100+
# Verify leaderboard loaded
101+
print("Verifying leaderboard...")
102+
expect(page.get_by_text("LEADERBOARD")).to_be_visible()
103+
104+
# Check both podium and list views for the username
105+
print("Checking leaderboard entries...")
106+
found_user = False
107+
108+
# Check podium entries
109+
podium_entries = page.locator('.podium h5').all()
110+
for entry in podium_entries:
111+
content = entry.text_content()
112+
print(f"Podium entry: {content}")
113+
if username in content:
114+
found_user = True
115+
break
116+
117+
# If not found in podium, check list entries
118+
if not found_user:
119+
list_entries = page.locator('.MuiListItemText-primary').all()
120+
for entry in list_entries:
121+
content = entry.text_content()
122+
print(f"List entry: {content}")
123+
if username in content:
124+
found_user = True
125+
break
126+
127+
assert found_user, "User not found in leaderboard"
128+
129+
def test_quiz_creation(page, app_url):
130+
print("\nStarting quiz creation test...")
131+
132+
# Navigate to home page
133+
print("Navigating to home page...")
134+
page.goto(app_url)
135+
136+
# Click Create a New Quiz
137+
print("Clicking Create a New Quiz...")
138+
page.get_by_text("Create a New Quiz").click()
139+
140+
# Fill in quiz details
141+
print("Filling quiz title...")
142+
page.get_by_label("Quiz Title").fill("Test Quiz")
143+
144+
# Add a question
145+
print("Adding question text...")
146+
question_input = page.get_by_role("textbox", name="Question Text")
147+
question_input.fill("What is LocalStack?")
148+
149+
# Fill options
150+
options = [
151+
"A. A cloud service emulator",
152+
"B. A database system",
153+
"C. A web framework",
154+
"D. A programming language"
155+
]
156+
157+
print("Filling options...")
158+
for i, option in enumerate(options):
159+
# Use role selector for textbox with specific name
160+
option_input = page.get_by_role("textbox", name=f"Option {i + 1}")
161+
option_input.fill(option)
162+
163+
# Add trivia
164+
print("Adding trivia...")
165+
trivia_input = page.get_by_role("textbox", name="Trivia")
166+
trivia_input.fill("LocalStack is a cloud service emulator that runs in a single container on your laptop.")
167+
168+
# Select correct answer using radio group
169+
print("Selecting correct answer...")
170+
radio_group = page.get_by_role("radiogroup")
171+
expect(radio_group).to_be_visible()
172+
173+
# Select the first option (A) as correct answer
174+
correct_answer = page.locator('input[type="radio"]').first
175+
expect(correct_answer).to_be_visible()
176+
correct_answer.check()
177+
178+
# Add question
179+
print("Adding question...")
180+
add_question_button = page.get_by_role("button", name="Add Question")
181+
expect(add_question_button).to_be_enabled()
182+
add_question_button.click()
183+
184+
# Submit quiz
185+
print("Submitting quiz...")
186+
submit_button = page.get_by_role("button", name="Submit Quiz")
187+
expect(submit_button).to_be_enabled()
188+
submit_button.click()
189+
190+
# Verify quiz was created
191+
print("Verifying quiz creation...")
192+
success_message = page.get_by_text("Quiz created successfully!")
193+
expect(success_message).to_be_visible()
194+
195+
# Get Quiz ID from success message
196+
success_text = success_message.text_content()
197+
quiz_id = success_text.split("Quiz ID: ")[1]
198+
print(f"Quiz created with ID: {quiz_id}")
199+
200+
# Go back to home
201+
print("Going back to home...")
202+
page.get_by_text("Go to Home").click()
203+
204+
# Verify we can find and start the new quiz
205+
print("Starting the created quiz...")
206+
quiz_id_input = page.get_by_label("Quiz ID")
207+
quiz_id_input.fill(quiz_id)
208+
209+
username = ''.join(random.choices(string.ascii_letters + string.digits, k=8))
210+
print(f"Using username: {username}")
211+
username_input = page.get_by_label("Username")
212+
username_input.fill(username)
213+
214+
start_button = page.get_by_text("Start Playing")
215+
expect(start_button).to_be_enabled()
216+
start_button.click()
217+
218+
# Verify question appears
219+
print("Verifying question appears...")
220+
expect(page.get_by_text("What is LocalStack?")).to_be_visible()
221+
print("Quiz creation test completed successfully!")

0 commit comments

Comments
 (0)