- 
                Notifications
    
You must be signed in to change notification settings  - Fork 573
 
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 all 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
    
  
  
    
              | 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
    
  
  
    
              
  
    
      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,183 @@ | ||
| 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() | ||
| 
     | 
||
| 
     | 
||
| 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(page: Page, live_server_url: str): | ||
| # 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.
        
    
  
  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.