-
Notifications
You must be signed in to change notification settings - Fork 433
Add Playwright Tests #66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
pamelafox
merged 17 commits into
Azure-Samples:main
from
john0isaac:add-playwright-tests
Aug 1, 2024
Merged
Changes from 5 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
8ea0abf
add playwright and fix db port for running tests outside container
b67dbc9
change 'session_state' to 'sessionState'
367a86d
add playwright tests and workflows
8f27e23
add types for mypy
4c7bcaa
Update .devcontainer/docker-compose.yaml
pamelafox 8d6abfd
apply feedback from PR review
5c708e0
Try on windows
pamelafox 76d1899
add playwright and fix db port for running tests outside container
2dda914
change 'session_state' to 'sessionState'
69b1741
add playwright tests and workflows
b1e2e6b
add types for mypy
be1cc15
Update .devcontainer/docker-compose.yaml
pamelafox 1eddb92
apply feedback from PR review
c26d3f2
Try on windows
pamelafox 0ac27bc
Get Ollama working
pamelafox 0cf5e35
Merge branch 'add-playwright-tests' of https://github.com/john0isaac/…
pamelafox 2093fd9
Disable Windows
pamelafox File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -146,3 +146,5 @@ npm-debug.log* | |
node_modules | ||
static/ | ||
|
||
# Playwright test trace | ||
test-results/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,14 @@ | ||
-r src/backend/requirements.txt | ||
ruff | ||
mypy | ||
types-requests | ||
pre-commit | ||
pip-tools | ||
pip-compile-cross-platform | ||
playwright | ||
pytest | ||
pytest-cov | ||
pytest-asyncio | ||
pytest-cov | ||
pytest-playwright | ||
pytest-snapshot | ||
mypy | ||
locust |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
import socket | ||
import time | ||
from collections.abc import Generator | ||
from contextlib import closing | ||
from multiprocessing import Process | ||
|
||
import pytest | ||
import requests | ||
import uvicorn | ||
from playwright.sync_api import Page, Route, expect | ||
|
||
import fastapi_app as app | ||
|
||
expect.set_options(timeout=10_000) | ||
|
||
|
||
def wait_for_server_ready(url: str, timeout: float = 10.0, check_interval: float = 0.5) -> bool: | ||
"""Make requests to provided url until it responds without error.""" | ||
conn_error = None | ||
for _ in range(int(timeout / check_interval)): | ||
try: | ||
requests.get(url) | ||
except requests.ConnectionError as exc: | ||
time.sleep(check_interval) | ||
conn_error = str(exc) | ||
else: | ||
return True | ||
raise RuntimeError(conn_error) | ||
|
||
|
||
@pytest.fixture(scope="session") | ||
def free_port() -> int: | ||
"""Returns a free port for the test server to bind.""" | ||
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: | ||
s.bind(("", 0)) | ||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | ||
return s.getsockname()[1] | ||
|
||
|
||
def run_server(port: int): | ||
uvicorn.run(app.create_app(testing=True), port=port) | ||
|
||
|
||
@pytest.fixture() | ||
def live_server_url(mock_session_env, free_port: int) -> Generator[str, None, None]: | ||
proc = Process(target=run_server, args=(free_port,), daemon=True) | ||
proc.start() | ||
url = f"http://localhost:{free_port}/" | ||
wait_for_server_ready(url, timeout=10.0, check_interval=0.5) | ||
yield url | ||
proc.kill() | ||
|
||
|
||
@pytest.fixture(params=[(480, 800), (600, 1024), (768, 1024), (992, 1024), (1024, 768)]) | ||
john0isaac marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def sized_page(page: Page, request): | ||
size = request.param | ||
page.set_viewport_size({"width": size[0], "height": size[1]}) | ||
yield page | ||
|
||
|
||
def test_home(page: Page, live_server_url: str): | ||
page.goto(live_server_url) | ||
expect(page).to_have_title("RAG on PostgreSQL") | ||
|
||
|
||
def test_chat(sized_page: Page, live_server_url: str): | ||
page = sized_page | ||
|
||
# Set up a mock route to the /chat endpoint with streaming results | ||
def handle(route: Route): | ||
# Assert that session_state is specified in the request (None for now) | ||
if route.request.post_data_json: | ||
session_state = route.request.post_data_json["sessionState"] | ||
assert session_state is None | ||
# Read the JSONL from our snapshot results and return as the response | ||
f = open( | ||
"tests/snapshots/test_api_routes/test_advanced_chat_streaming_flow/advanced_chat_streaming_flow_response.jsonlines" | ||
) | ||
jsonl = f.read() | ||
f.close() | ||
route.fulfill(body=jsonl, status=200, headers={"Transfer-encoding": "Chunked"}) | ||
|
||
page.route("*/**/chat/stream", handle) | ||
|
||
# Check initial page state | ||
page.goto(live_server_url) | ||
expect(page).to_have_title("RAG on PostgreSQL") | ||
expect(page.get_by_role("heading", name="Product chat")).to_be_visible() | ||
expect(page.get_by_role("button", name="Clear chat")).to_be_disabled() | ||
expect(page.get_by_role("button", name="Developer settings")).to_be_enabled() | ||
|
||
# Ask a question and wait for the message to appear | ||
page.get_by_placeholder("Type a new question (e.g. does my plan cover annual eye exams?)").click() | ||
page.get_by_placeholder("Type a new question (e.g. does my plan cover annual eye exams?)").fill( | ||
"Whats the dental plan?" | ||
) | ||
page.get_by_role("button", name="Ask question button").click() | ||
|
||
expect(page.get_by_text("Whats the dental plan?")).to_be_visible() | ||
expect(page.get_by_text("The capital of France is Paris.")).to_be_visible() | ||
expect(page.get_by_role("button", name="Clear chat")).to_be_enabled() | ||
|
||
# Show the thought process | ||
page.get_by_label("Show thought process").click() | ||
expect(page.get_by_title("Thought process")).to_be_visible() | ||
expect(page.get_by_text("Prompt to generate search arguments")).to_be_visible() | ||
|
||
# Clear the chat | ||
page.get_by_role("button", name="Clear chat").click() | ||
expect(page.get_by_text("Whats the dental plan?")).not_to_be_visible() | ||
expect(page.get_by_text("The capital of France is Paris.")).not_to_be_visible() | ||
expect(page.get_by_role("button", name="Clear chat")).to_be_disabled() | ||
|
||
|
||
def test_chat_customization(page: Page, live_server_url: str): | ||
# Set up a mock route to the /chat endpoint | ||
def handle(route: Route): | ||
if route.request.post_data_json: | ||
overrides = route.request.post_data_json["context"]["overrides"] | ||
assert overrides["use_advanced_flow"] is False | ||
assert overrides["retrieval_mode"] == "vectors" | ||
assert overrides["top"] == 1 | ||
assert overrides["prompt_template"] == "You are a cat and only talk about tuna." | ||
|
||
# Read the JSON from our snapshot results and return as the response | ||
f = open("tests/snapshots/test_api_routes/test_simple_chat_flow/simple_chat_flow_response.json") | ||
json = f.read() | ||
f.close() | ||
route.fulfill(body=json, status=200) | ||
|
||
page.route("*/**/chat", handle) | ||
|
||
# Check initial page state | ||
page.goto(live_server_url) | ||
expect(page).to_have_title("RAG on PostgreSQL") | ||
|
||
# Customize all the settings | ||
page.get_by_role("button", name="Developer settings").click() | ||
page.get_by_text( | ||
"Use advanced flow with query rewriting and filter formulation. Not compatible with Ollama models." | ||
).click() | ||
page.get_by_label("Retrieve this many matching rows:").click() | ||
page.get_by_label("Retrieve this many matching rows:").fill("1") | ||
page.get_by_text("Vectors + Text (Hybrid)").click() | ||
page.get_by_role("option", name="Vectors", exact=True).click() | ||
page.get_by_label("Override prompt template").click() | ||
page.get_by_label("Override prompt template").fill("You are a cat and only talk about tuna.") | ||
|
||
page.get_by_text("Stream chat completion responses").click() | ||
page.locator("button").filter(has_text="Close").click() | ||
|
||
# Ask a question and wait for the message to appear | ||
page.get_by_placeholder("Type a new question (e.g. does my plan cover annual eye exams?)").click() | ||
page.get_by_placeholder("Type a new question (e.g. does my plan cover annual eye exams?)").fill( | ||
"Whats the dental plan?" | ||
) | ||
page.get_by_role("button", name="Ask question button").click() | ||
|
||
expect(page.get_by_text("Whats the dental plan?")).to_be_visible() | ||
expect(page.get_by_text("The capital of France is Paris.")).to_be_visible() | ||
expect(page.get_by_role("button", name="Clear chat")).to_be_enabled() | ||
|
||
|
||
def test_chat_nonstreaming(page: Page, live_server_url: str): | ||
# Set up a mock route to the /chat_stream endpoint | ||
def handle(route: Route): | ||
# Read the JSON from our snapshot results and return as the response | ||
f = open("tests/snapshots/test_api_routes/test_advanced_chat_flow/advanced_chat_flow_response.json") | ||
json = f.read() | ||
f.close() | ||
route.fulfill(body=json, status=200) | ||
|
||
page.route("*/**/chat", handle) | ||
|
||
# Check initial page state | ||
page.goto(live_server_url) | ||
expect(page).to_have_title("RAG on PostgreSQL") | ||
expect(page.get_by_role("button", name="Developer settings")).to_be_enabled() | ||
page.get_by_role("button", name="Developer settings").click() | ||
page.get_by_text("Stream chat completion responses").click() | ||
page.locator("button").filter(has_text="Close").click() | ||
|
||
# Ask a question and wait for the message to appear | ||
page.get_by_placeholder("Type a new question (e.g. does my plan cover annual eye exams?)").click() | ||
page.get_by_placeholder("Type a new question (e.g. does my plan cover annual eye exams?)").fill( | ||
"Whats the dental plan?" | ||
) | ||
page.get_by_label("Ask question button").click() | ||
|
||
expect(page.get_by_text("Whats the dental plan?")).to_be_visible() | ||
expect(page.get_by_text("The capital of France is Paris.")).to_be_visible() | ||
expect(page.get_by_role("button", name="Clear chat")).to_be_enabled() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 2 additions & 2 deletions
4
..._routes/test_advanced_chat_streaming_flow/advanced_chat_streaming_flow_response.jsonlines
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
{"delta":null,"context":{"data_points":{"1":{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}},"thoughts":[{"title":"Prompt to generate search arguments","description":["{'role': 'system', 'content': 'Below is a history of the conversation so far, and a new question asked by the user that needs to be answered by searching database rows.\\nYou have access to an Azure PostgreSQL database with an items table that has columns for title, description, brand, price, and type.\\nGenerate a search query based on the conversation and the new question.\\nIf the question is not in English, translate the question to English before generating the search query.\\nIf you cannot generate a search query, return the original user question.\\nDO NOT return anything besides the query.'}","{'role': 'user', 'content': 'What is the capital of France?'}"],"props":{"model":"gpt-35-turbo","deployment":"gpt-35-turbo"}},{"title":"Search using generated search arguments","description":"The capital of France is Paris. [Benefit_Options-2.pdf].","props":{"top":1,"vector_search":true,"text_search":true,"filters":[]}},{"title":"Search results","description":[{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}],"props":{}},{"title":"Prompt to generate answer","description":["{'role': 'system', 'content': \"Assistant helps customers with questions about products.\\nRespond as if you are a salesperson helping a customer in a store. Do NOT respond with tables.\\nAnswer ONLY with the product details listed in the products.\\nIf there isn't enough information below, say you don't know.\\nDo not generate answers that don't use the sources below.\\nEach product has an ID in brackets followed by colon and the product details.\\nAlways include the product ID for each product you use in the response.\\nUse square brackets to reference the source, for example [52].\\nDon't combine citations, list each product separately, for example [27][51].\"}","{'role': 'user', 'content': \"What is the capital of France?\\n\\nSources:\\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear\\n\\n\"}"],"props":{"model":"gpt-35-turbo","deployment":"gpt-35-turbo"}}],"followup_questions":null},"session_state":null} | ||
{"delta":{"content":"The capital of France is Paris. [Benefit_Options-2.pdf].","role":"assistant"},"context":null,"session_state":null} | ||
{"delta":null,"context":{"data_points":{"1":{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}},"thoughts":[{"title":"Prompt to generate search arguments","description":["{'role': 'system', 'content': 'Below is a history of the conversation so far, and a new question asked by the user that needs to be answered by searching database rows.\\nYou have access to an Azure PostgreSQL database with an items table that has columns for title, description, brand, price, and type.\\nGenerate a search query based on the conversation and the new question.\\nIf the question is not in English, translate the question to English before generating the search query.\\nIf you cannot generate a search query, return the original user question.\\nDO NOT return anything besides the query.'}","{'role': 'user', 'content': 'What is the capital of France?'}"],"props":{"model":"gpt-35-turbo","deployment":"gpt-35-turbo"}},{"title":"Search using generated search arguments","description":"The capital of France is Paris. [Benefit_Options-2.pdf].","props":{"top":1,"vector_search":true,"text_search":true,"filters":[]}},{"title":"Search results","description":[{"id":1,"type":"Footwear","brand":"Daybird","name":"Wanderer Black Hiking Boots","description":"Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long.","price":109.99}],"props":{}},{"title":"Prompt to generate answer","description":["{'role': 'system', 'content': \"Assistant helps customers with questions about products.\\nRespond as if you are a salesperson helping a customer in a store. Do NOT respond with tables.\\nAnswer ONLY with the product details listed in the products.\\nIf there isn't enough information below, say you don't know.\\nDo not generate answers that don't use the sources below.\\nEach product has an ID in brackets followed by colon and the product details.\\nAlways include the product ID for each product you use in the response.\\nUse square brackets to reference the source, for example [52].\\nDon't combine citations, list each product separately, for example [27][51].\"}","{'role': 'user', 'content': \"What is the capital of France?\\n\\nSources:\\n[1]:Name:Wanderer Black Hiking Boots Description:Daybird's Wanderer Hiking Boots in sleek black are perfect for all your outdoor adventures. These boots are made with a waterproof leather upper and a durable rubber sole for superior traction. With their cushioned insole and padded collar, these boots will keep you comfortable all day long. Price:109.99 Brand:Daybird Type:Footwear\\n\\n\"}"],"props":{"model":"gpt-35-turbo","deployment":"gpt-35-turbo"}}],"followup_questions":null},"sessionState":null} | ||
{"delta":{"content":"The capital of France is Paris. [Benefit_Options-2.pdf].","role":"assistant"},"context":null,"sessionState":null} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,5 +52,5 @@ | |
], | ||
"followup_questions": null | ||
}, | ||
"session_state": null | ||
"sessionState": null | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.