forked from Azure-Samples/azure-search-openai-demo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathe2e.py
466 lines (377 loc) · 19.1 KB
/
e2e.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
import json
import os
import socket
import time
from contextlib import closing
from multiprocessing import Process
from typing import Generator
from unittest import mock
import pytest
import requests
import uvicorn
from playwright.sync_api import Page, Route, expect
import 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):
with mock.patch.dict(
os.environ,
{
"AZURE_STORAGE_ACCOUNT": "test-storage-account",
"AZURE_STORAGE_CONTAINER": "test-storage-container",
"AZURE_STORAGE_RESOURCE_GROUP": "test-storage-rg",
"AZURE_SUBSCRIPTION_ID": "test-storage-subid",
"ENABLE_LANGUAGE_PICKER": "false",
"USE_SPEECH_INPUT_BROWSER": "false",
"USE_SPEECH_OUTPUT_AZURE": "false",
"AZURE_SEARCH_INDEX": "test-search-index",
"AZURE_SEARCH_SERVICE": "test-search-service",
"AZURE_SPEECH_SERVICE_ID": "test-id",
"AZURE_SPEECH_SERVICE_LOCATION": "eastus",
"AZURE_OPENAI_SERVICE": "test-openai-service",
"AZURE_OPENAI_CHATGPT_MODEL": "gpt-35-turbo",
},
clear=True,
):
uvicorn.run(app.create_app(), port=port)
@pytest.fixture()
def live_server_url(mock_env, mock_acs_search, 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)])
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("Azure OpenAI + AI Search")
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)
session_state = route.request.post_data_json["session_state"]
assert session_state is None
# Read the JSONL from our snapshot results and return as the response
f = open("tests/snapshots/test_app/test_chat_stream_text/client0/result.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("Azure OpenAI + AI Search")
expect(page.get_by_role("heading", name="Chat with your data")).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="Submit question").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 citation document
page.get_by_text("1. Benefit_Options-2.pdf").click()
expect(page.get_by_role("tab", name="Citation")).to_be_visible()
expect(page.get_by_title("Citation")).to_be_visible()
# 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("Generated search query")).to_be_visible()
# Show the supporting content
page.get_by_label("Show supporting content").click()
expect(page.get_by_title("Supporting content")).to_be_visible()
expect(page.get_by_role("heading", name="Benefit_Options-2.pdf")).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):
overrides = route.request.post_data_json["context"]["overrides"]
assert overrides["temperature"] == 0.5
assert overrides["seed"] == 123
assert overrides["minimum_search_score"] == 0.5
assert overrides["minimum_reranker_score"] == 0.5
assert overrides["retrieval_mode"] == "vectors"
assert overrides["semantic_ranker"] is False
assert overrides["semantic_captions"] is True
assert overrides["top"] == 1
assert overrides["prompt_template"] == "You are a cat and only talk about tuna."
assert overrides["exclude_category"] == "dogs"
assert overrides["suggest_followup_questions"] is True
assert overrides["use_oid_security_filter"] is False
assert overrides["use_groups_security_filter"] is False
# Read the JSON from our snapshot results and return as the response
f = open("tests/snapshots/test_app/test_chat_text/client0/result.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("Azure OpenAI + AI Search")
# Customize all the settings
page.get_by_role("button", name="Developer settings").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_label("Temperature").click()
page.get_by_label("Temperature").fill("0.5")
page.get_by_label("Seed").click()
page.get_by_label("Seed").fill("123")
page.get_by_label("Minimum search score").click()
page.get_by_label("Minimum search score").fill("0.5")
page.get_by_label("Minimum reranker score").click()
page.get_by_label("Minimum reranker score").fill("0.5")
page.get_by_label("Retrieve this many search results:").click()
page.get_by_label("Retrieve this many search results:").fill("1")
page.get_by_label("Include category").click()
page.get_by_role("option", name="All", exact=True).click()
page.get_by_label("Exclude category").click()
page.get_by_label("Exclude category").fill("dogs")
page.get_by_text("Use semantic captions").click()
page.get_by_text("Use semantic ranker for retrieval").click()
page.get_by_text("Suggest follow-up questions").click()
page.get_by_text("Vectors + Text (Hybrid)").click()
page.get_by_role("option", name="Vectors", exact=True).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_role("button", name="Submit question").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_customization_gpt4v(page: Page, live_server_url: str):
# Set up a mock route to the /chat endpoint
def handle_chat(route: Route):
overrides = route.request.post_data_json["context"]["overrides"]
assert overrides["gpt4v_input"] == "images"
assert overrides["use_gpt4v"] is True
assert overrides["vector_fields"] == ["imageEmbedding"]
# Read the JSON from our snapshot results and return as the response
f = open("tests/snapshots/test_app/test_chat_text/client0/result.json")
json = f.read()
f.close()
route.fulfill(body=json, status=200)
def handle_config(route: Route):
route.fulfill(
body=json.dumps(
{
"showGPT4VOptions": True,
"showSemanticRankerOption": True,
"showUserUpload": False,
"showVectorOption": True,
}
),
status=200,
)
page.route("*/**/config", handle_config)
page.route("*/**/chat", handle_chat)
# Check initial page state
page.goto(live_server_url)
expect(page).to_have_title("Azure OpenAI + AI Search")
# Customize the GPT-4-vision settings
page.get_by_role("button", name="Developer settings").click()
page.get_by_text("Use GPT vision model").click()
page.get_by_text("Images and text").click()
page.get_by_role("option", name="Images", exact=True).click()
page.get_by_text("Text and Image embeddings").click()
page.get_by_role("option", name="Image Embeddings", exact=True).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("Submit question").click()
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_app/test_chat_text/client0/result.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("Azure OpenAI + AI Search")
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("Submit question").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_followup_streaming(page: Page, live_server_url: str):
# Set up a mock route to the /chat_stream endpoint
def handle(route: Route):
overrides = route.request.post_data_json["context"]["overrides"]
assert overrides["suggest_followup_questions"] is True
# Read the JSONL from our snapshot results and return as the response
f = open("tests/snapshots/test_app/test_chat_stream_followup/client0/result.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("Azure OpenAI + AI Search")
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("Suggest follow-up questions").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("Submit question").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()
# There should be a follow-up question and it should be clickable:
expect(page.get_by_text("What is the capital of Spain?")).to_be_visible()
page.get_by_text("What is the capital of Spain?").click()
# Now there should be a follow-up answer (same, since we're using same test data)
expect(page.get_by_text("The capital of France is Paris.")).to_have_count(2)
def test_chat_followup_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_app/test_chat_followup/client0/result.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("Azure OpenAI + AI Search")
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.get_by_text("Suggest follow-up questions").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("Submit question").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()
# There should be a follow-up question and it should be clickable:
expect(page.get_by_text("What is the capital of Spain?")).to_be_visible()
page.get_by_text("What is the capital of Spain?").click()
# Now there should be a follow-up answer (same, since we're using same test data)
expect(page.get_by_text("The capital of France is Paris.")).to_have_count(2)
def test_ask(sized_page: Page, live_server_url: str):
page = sized_page
# Set up a mock route to the /ask endpoint
def handle(route: Route):
# Assert that session_state is specified in the request (None for now)
session_state = route.request.post_data_json["session_state"]
assert session_state is None
# Read the JSON from our snapshot results and return as the response
f = open("tests/snapshots/test_app/test_ask_rtr_hybrid/client0/result.json")
json = f.read()
f.close()
route.fulfill(body=json, status=200)
page.route("*/**/ask", handle)
page.goto(live_server_url)
expect(page).to_have_title("Azure OpenAI + AI Search")
# The burger menu only exists at smaller viewport sizes
if page.get_by_role("button", name="Toggle menu").is_visible():
page.get_by_role("button", name="Toggle menu").click()
page.get_by_role("link", name="Ask a question").click()
page.get_by_placeholder("Example: Does my plan cover annual eye exams?").click()
page.get_by_placeholder("Example: Does my plan cover annual eye exams?").fill("Whats the dental plan?")
page.get_by_placeholder("Example: Does my plan cover annual eye exams?").click()
page.get_by_label("Submit question").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()
def test_upload_hidden(page: Page, live_server_url: str):
def handle_auth_setup(route: Route):
with open("tests/snapshots/test_authenticationhelper/test_auth_setup/result.json") as f:
auth_setup = json.load(f)
route.fulfill(body=json.dumps(auth_setup), status=200)
page.route("*/**/auth_setup", handle_auth_setup)
def handle_config(route: Route):
route.fulfill(
body=json.dumps(
{
"showGPT4VOptions": False,
"showSemanticRankerOption": True,
"showUserUpload": False,
"showVectorOption": True,
}
),
status=200,
)
page.route("*/**/config", handle_config)
page.goto(live_server_url)
expect(page).to_have_title("Azure OpenAI + AI Search")
expect(page.get_by_role("button", name="Clear chat")).to_be_visible()
expect(page.get_by_role("button", name="Manage file uploads")).not_to_be_visible()
def test_upload_disabled(page: Page, live_server_url: str):
def handle_auth_setup(route: Route):
with open("tests/snapshots/test_authenticationhelper/test_auth_setup/result.json") as f:
auth_setup = json.load(f)
route.fulfill(body=json.dumps(auth_setup), status=200)
page.route("*/**/auth_setup", handle_auth_setup)
def handle_config(route: Route):
route.fulfill(
body=json.dumps(
{
"showGPT4VOptions": False,
"showSemanticRankerOption": True,
"showUserUpload": True,
"showVectorOption": True,
}
),
status=200,
)
page.route("*/**/config", handle_config)
page.goto(live_server_url)
expect(page).to_have_title("Azure OpenAI + AI Search")
expect(page.get_by_role("button", name="Manage file uploads")).to_be_visible()
expect(page.get_by_role("button", name="Manage file uploads")).to_be_disabled()
# We can't test actual file upload as we don't currently have isLoggedIn(client) mocked out