mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-05-06 02:07:49 +08:00
Feat: UI testing automation with playwright (#12749)
### What problem does this PR solve? This PR helps automate the testing of the ui interface using pytest Playwright ### Type of change - [x] New Feature (non-breaking change which adds functionality) - [x] Other (please describe): test automation infrastructure --------- Co-authored-by: Liu An <asiro@qq.com>
This commit is contained in:
0
test/playwright/e2e/__init__.py
Normal file
0
test/playwright/e2e/__init__.py
Normal file
295
test/playwright/e2e/test_dataset_upload_parse.py
Normal file
295
test/playwright/e2e/test_dataset_upload_parse.py
Normal file
@ -0,0 +1,295 @@
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
from pathlib import Path
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import pytest
|
||||
from playwright.sync_api import expect
|
||||
|
||||
from test.playwright.helpers.flow_steps import flow_params, require
|
||||
from test.playwright.helpers.auth_selectors import EMAIL_INPUT, PASSWORD_INPUT, SUBMIT_BUTTON
|
||||
from test.playwright.helpers.auth_waits import wait_for_login_complete
|
||||
from test.playwright.helpers.response_capture import capture_response
|
||||
from test.playwright.helpers.datasets import (
|
||||
delete_uploaded_file,
|
||||
ensure_parse_on,
|
||||
ensure_upload_modal_open,
|
||||
open_create_dataset_modal,
|
||||
select_chunking_method_general,
|
||||
upload_file,
|
||||
wait_for_dataset_detail,
|
||||
wait_for_dataset_detail_ready,
|
||||
wait_for_success_dot,
|
||||
)
|
||||
|
||||
RESULT_TIMEOUT_MS = 15000
|
||||
|
||||
|
||||
def step_01_login(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
email, password = seeded_user_credentials
|
||||
|
||||
repo_root = Path(__file__).resolve().parents[3]
|
||||
file_paths = [
|
||||
repo_root / "test/benchmark/test_docs/Doc1.pdf",
|
||||
repo_root / "test/benchmark/test_docs/Doc2.pdf",
|
||||
repo_root / "test/benchmark/test_docs/Doc3.pdf",
|
||||
]
|
||||
for path in file_paths:
|
||||
if not path.is_file():
|
||||
pytest.fail(f"Missing upload fixture: {path}")
|
||||
flow_state["file_paths"] = [str(path) for path in file_paths]
|
||||
flow_state["filenames"] = [path.name for path in file_paths]
|
||||
|
||||
with step("open login page"):
|
||||
flow_page.goto(login_url, wait_until="domcontentloaded")
|
||||
|
||||
form, _ = active_auth_context()
|
||||
email_input = form.locator(EMAIL_INPUT)
|
||||
password_input = form.locator(PASSWORD_INPUT)
|
||||
with step("fill credentials"):
|
||||
expect(email_input).to_have_count(1)
|
||||
expect(password_input).to_have_count(1)
|
||||
email_input.fill(email)
|
||||
password_input.fill(password)
|
||||
password_input.blur()
|
||||
|
||||
with step("submit login"):
|
||||
submit_button = form.locator(SUBMIT_BUTTON)
|
||||
expect(submit_button).to_have_count(1)
|
||||
auth_click(submit_button, "submit_login")
|
||||
|
||||
with step("wait for login"):
|
||||
wait_for_login_complete(flow_page, timeout_ms=RESULT_TIMEOUT_MS)
|
||||
flow_state["logged_in"] = True
|
||||
snap("login_complete")
|
||||
|
||||
|
||||
def step_02_open_datasets(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "logged_in")
|
||||
page = flow_page
|
||||
with step("open datasets"):
|
||||
page.goto(urljoin(base_url.rstrip("/") + "/", "/"), wait_until="domcontentloaded")
|
||||
nav_button = page.locator("button", has_text=re.compile(r"^Dataset$", re.I))
|
||||
if nav_button.count() > 0:
|
||||
nav_button.first.click()
|
||||
else:
|
||||
page.goto(
|
||||
urljoin(base_url.rstrip("/") + "/", "/datasets"),
|
||||
wait_until="domcontentloaded",
|
||||
)
|
||||
snap("datasets_open")
|
||||
|
||||
|
||||
def step_03_create_dataset(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "logged_in")
|
||||
page = flow_page
|
||||
with step("open create dataset modal"):
|
||||
modal = open_create_dataset_modal(page, expect, RESULT_TIMEOUT_MS)
|
||||
snap("dataset_modal_open")
|
||||
|
||||
dataset_name = f"qa-dataset-{int(time.time() * 1000)}"
|
||||
with step("fill dataset form"):
|
||||
name_input = modal.locator("input[placeholder='Please input name.']").first
|
||||
expect(name_input).to_be_visible()
|
||||
name_input.fill(dataset_name)
|
||||
|
||||
try:
|
||||
select_chunking_method_general(page, expect, modal, RESULT_TIMEOUT_MS)
|
||||
except Exception:
|
||||
snap("failure_dataset_create")
|
||||
raise
|
||||
|
||||
save_button = None
|
||||
if hasattr(modal, "get_by_role"):
|
||||
save_button = modal.get_by_role("button", name=re.compile(r"^save$", re.I))
|
||||
if save_button is None or save_button.count() == 0:
|
||||
save_button = modal.locator("button", has_text=re.compile(r"^save$", re.I)).first
|
||||
expect(save_button).to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
save_button.click()
|
||||
expect(modal).not_to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
wait_for_dataset_detail(page, timeout_ms=RESULT_TIMEOUT_MS)
|
||||
wait_for_dataset_detail_ready(page, expect, timeout_ms=RESULT_TIMEOUT_MS)
|
||||
flow_state["dataset_name"] = dataset_name
|
||||
snap("dataset_created")
|
||||
snap("dataset_detail_ready")
|
||||
|
||||
|
||||
def step_04_upload_files(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "dataset_name", "file_paths")
|
||||
page = flow_page
|
||||
file_paths = [Path(path) for path in flow_state["file_paths"]]
|
||||
filenames = flow_state.get("filenames") or [path.name for path in file_paths]
|
||||
flow_state["filenames"] = filenames
|
||||
|
||||
for idx, file_path in enumerate(file_paths):
|
||||
filename = file_path.name
|
||||
with step(f"open upload modal for {filename}"):
|
||||
upload_modal = ensure_upload_modal_open(
|
||||
page, expect, auth_click, timeout_ms=RESULT_TIMEOUT_MS
|
||||
)
|
||||
if idx == 0:
|
||||
snap("upload_modal_open")
|
||||
|
||||
with step(f"enable parse on creation for {filename}"):
|
||||
ensure_parse_on(upload_modal, expect)
|
||||
if idx == 0:
|
||||
snap("parse_toggle_on")
|
||||
|
||||
with step(f"upload file {filename}"):
|
||||
upload_file(page, expect, upload_modal, str(file_path), RESULT_TIMEOUT_MS)
|
||||
expect(upload_modal.locator(f"text={filename}")).to_be_visible(
|
||||
timeout=RESULT_TIMEOUT_MS
|
||||
)
|
||||
|
||||
with step(f"submit upload {filename}"):
|
||||
save_button = upload_modal.locator(
|
||||
"button", has_text=re.compile("save", re.I)
|
||||
).first
|
||||
|
||||
def trigger():
|
||||
save_button.click()
|
||||
|
||||
capture_response(
|
||||
page,
|
||||
trigger,
|
||||
lambda resp: resp.request.method == "POST"
|
||||
and "/v1/document/upload" in resp.url,
|
||||
)
|
||||
expect(upload_modal).not_to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
snap(f"upload_{filename}_submitted")
|
||||
|
||||
row = page.locator(
|
||||
f"[data-testid='document-row'][data-doc-name={json.dumps(filename)}]"
|
||||
)
|
||||
expect(row).to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
|
||||
flow_state["uploads_done"] = True
|
||||
|
||||
|
||||
def step_05_wait_parse_success(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "uploads_done", "filenames")
|
||||
page = flow_page
|
||||
for filename in flow_state["filenames"]:
|
||||
with step(f"wait for parse success {filename}"):
|
||||
wait_for_success_dot(page, expect, filename, timeout_ms=RESULT_TIMEOUT_MS)
|
||||
snap(f"parse_{filename}_success")
|
||||
flow_state["parse_complete"] = True
|
||||
|
||||
|
||||
def step_06_delete_one_file(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "parse_complete", "filenames")
|
||||
page = flow_page
|
||||
delete_filename = "Doc3.pdf"
|
||||
with step(f"delete uploaded file {delete_filename}"):
|
||||
delete_uploaded_file(page, expect, delete_filename, timeout_ms=RESULT_TIMEOUT_MS)
|
||||
snap("file_deleted_doc3")
|
||||
expect(
|
||||
page.locator(
|
||||
f"[data-testid='document-row'][data-doc-name={json.dumps('Doc1.pdf')}]"
|
||||
)
|
||||
).to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
expect(
|
||||
page.locator(
|
||||
f"[data-testid='document-row'][data-doc-name={json.dumps('Doc2.pdf')}]"
|
||||
)
|
||||
).to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
snap("success")
|
||||
|
||||
|
||||
STEPS = [
|
||||
("01_login", step_01_login),
|
||||
("02_open_datasets", step_02_open_datasets),
|
||||
("03_create_dataset", step_03_create_dataset),
|
||||
("04_upload_files", step_04_upload_files),
|
||||
("05_wait_parse_success", step_05_wait_parse_success),
|
||||
("06_delete_one_file", step_06_delete_one_file),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.p1
|
||||
@pytest.mark.auth
|
||||
@pytest.mark.parametrize("step_fn", flow_params(STEPS))
|
||||
def test_dataset_upload_parse_and_delete_flow(
|
||||
step_fn,
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
step_fn(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
)
|
||||
327
test/playwright/e2e/test_model_providers_zhipu_ai_defaults.py
Normal file
327
test/playwright/e2e/test_model_providers_zhipu_ai_defaults.py
Normal file
@ -0,0 +1,327 @@
|
||||
import re
|
||||
import os
|
||||
import pytest
|
||||
from playwright.sync_api import expect
|
||||
|
||||
from test.playwright.helpers.flow_steps import flow_params, require
|
||||
from test.playwright.helpers.auth_selectors import EMAIL_INPUT, PASSWORD_INPUT, SUBMIT_BUTTON
|
||||
from test.playwright.helpers.auth_waits import wait_for_login_complete
|
||||
from test.playwright.helpers.response_capture import capture_response
|
||||
from test.playwright.helpers.model_providers import (
|
||||
open_user_settings,
|
||||
safe_close_modal,
|
||||
select_default_model,
|
||||
)
|
||||
|
||||
RESULT_TIMEOUT_MS = 15000
|
||||
|
||||
|
||||
def step_01_open_login(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
api_key = os.getenv("ZHIPU_AI_API_KEY")
|
||||
if not api_key:
|
||||
pytest.skip("ZHIPU_AI_API_KEY not set; skipping model providers test.")
|
||||
|
||||
email, password = seeded_user_credentials
|
||||
|
||||
flow_state["api_key"] = api_key
|
||||
flow_state["email"] = email
|
||||
flow_state["password"] = password
|
||||
|
||||
with step("open login page"):
|
||||
flow_page.goto(login_url, wait_until="domcontentloaded")
|
||||
flow_state["login_opened"] = True
|
||||
snap("login_opened")
|
||||
|
||||
|
||||
def step_02_login(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "login_opened", "email", "password")
|
||||
page = flow_page
|
||||
form, _ = active_auth_context()
|
||||
email_input = form.locator(EMAIL_INPUT)
|
||||
password_input = form.locator(PASSWORD_INPUT)
|
||||
with step("fill credentials"):
|
||||
expect(email_input).to_have_count(1)
|
||||
expect(password_input).to_have_count(1)
|
||||
email_input.fill(flow_state["email"])
|
||||
password_input.fill(flow_state["password"])
|
||||
password_input.blur()
|
||||
|
||||
with step("submit login"):
|
||||
submit_button = form.locator(SUBMIT_BUTTON)
|
||||
expect(submit_button).to_have_count(1)
|
||||
auth_click(submit_button, "submit_login")
|
||||
|
||||
with step("wait for login"):
|
||||
wait_for_login_complete(page, timeout_ms=RESULT_TIMEOUT_MS)
|
||||
|
||||
flow_state["logged_in"] = True
|
||||
snap("home_loaded")
|
||||
|
||||
|
||||
def step_03_open_settings(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "logged_in")
|
||||
page = flow_page
|
||||
with step("open settings"):
|
||||
open_user_settings(page, base_url)
|
||||
flow_state["settings_open"] = True
|
||||
snap("settings_opened")
|
||||
|
||||
|
||||
def step_04_open_model_providers(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "settings_open")
|
||||
page = flow_page
|
||||
with step("open model providers"):
|
||||
model_nav = page.locator("[data-testid='settings-nav-model-providers']")
|
||||
expect(model_nav).to_have_count(1)
|
||||
model_nav.first.click()
|
||||
expect(page.locator("text=Set default models")).to_be_visible()
|
||||
flow_state["model_providers_open"] = True
|
||||
snap("model_providers_open")
|
||||
|
||||
|
||||
def step_05_filter_zhipu(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "model_providers_open")
|
||||
page = flow_page
|
||||
with step("filter providers"):
|
||||
search_input = page.locator("[data-testid='model-providers-search']")
|
||||
expect(search_input).to_have_count(1)
|
||||
search_input.first.fill("zhipu")
|
||||
available_section = page.locator("[data-testid='available-models-section']")
|
||||
provider = available_section.locator(
|
||||
"[data-testid='available-model-card'][data-provider='ZHIPU-AI']"
|
||||
).first
|
||||
if provider.count() == 0:
|
||||
added_section = page.locator("[data-testid='added-models-section']")
|
||||
if (
|
||||
added_section.locator(
|
||||
"[data-testid='added-model-card'][data-provider='ZHIPU-AI']"
|
||||
).count()
|
||||
== 0
|
||||
):
|
||||
raise AssertionError("ZHIPU-AI provider not found in available or added models.")
|
||||
else:
|
||||
expect(provider).to_be_visible()
|
||||
flow_state["provider_filtered"] = True
|
||||
snap("provider_filtered")
|
||||
|
||||
|
||||
def step_06_add_api_key(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "provider_filtered", "api_key")
|
||||
page = flow_page
|
||||
available_section = page.locator("[data-testid='available-models-section']")
|
||||
provider = available_section.locator(
|
||||
"[data-testid='available-model-card'][data-provider='ZHIPU-AI']"
|
||||
).first
|
||||
|
||||
with step("add ZHIPU-AI api key"):
|
||||
if provider.count() > 0:
|
||||
provider.click()
|
||||
else:
|
||||
added_section = page.locator("[data-testid='added-models-section']")
|
||||
card = added_section.locator(
|
||||
"[data-testid='added-model-card'][data-provider='ZHIPU-AI']"
|
||||
).first
|
||||
api_key_button = card.locator("button", has_text=re.compile("API-?Key", re.I)).first
|
||||
expect(api_key_button).to_be_visible()
|
||||
api_key_button.click()
|
||||
modal = page.locator("[data-testid='apikey-modal']")
|
||||
expect(modal).to_be_visible()
|
||||
api_input = modal.locator("[data-testid='apikey-input']").first
|
||||
save_button = modal.locator("[data-testid='apikey-save']").first
|
||||
try:
|
||||
def trigger():
|
||||
api_input.fill(flow_state["api_key"])
|
||||
save_button.click()
|
||||
|
||||
capture_response(
|
||||
page,
|
||||
trigger,
|
||||
lambda resp: resp.request.method == "POST" and "/v1/llm/set_api_key" in resp.url,
|
||||
)
|
||||
expect(modal).not_to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
except Exception:
|
||||
safe_close_modal(modal)
|
||||
raise
|
||||
|
||||
with step("confirm added model"):
|
||||
added_section = page.locator("[data-testid='added-models-section']")
|
||||
expect(added_section).to_be_visible()
|
||||
expect(
|
||||
added_section.locator(
|
||||
"[data-testid='added-model-card'][data-provider='ZHIPU-AI']"
|
||||
)
|
||||
).to_be_visible()
|
||||
flow_state["provider_added"] = True
|
||||
snap("provider_saved")
|
||||
|
||||
|
||||
def step_07_set_defaults(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "provider_added")
|
||||
page = flow_page
|
||||
with step("set default models"):
|
||||
llm_combo = page.locator("[data-testid='default-llm-combobox']").first
|
||||
emb_combo = page.locator("[data-testid='default-embedding-combobox']").first
|
||||
|
||||
select_default_model(
|
||||
page,
|
||||
expect,
|
||||
llm_combo,
|
||||
"glm-4-flash",
|
||||
"glm-4-flash",
|
||||
list_testid="default-llm-options",
|
||||
fallback_to_first=False,
|
||||
timeout_ms=RESULT_TIMEOUT_MS,
|
||||
)
|
||||
selected_emb_text, _ = select_default_model(
|
||||
page,
|
||||
expect,
|
||||
emb_combo,
|
||||
"embedding-2",
|
||||
"embedding-2",
|
||||
list_testid="default-embedding-options",
|
||||
fallback_to_first=True,
|
||||
timeout_ms=RESULT_TIMEOUT_MS,
|
||||
)
|
||||
flow_state["selected_emb_text"] = selected_emb_text
|
||||
flow_state["defaults_set"] = True
|
||||
snap("defaults_selected")
|
||||
|
||||
|
||||
def step_08_verify_persist(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "defaults_set")
|
||||
page = flow_page
|
||||
with step("reload and verify defaults"):
|
||||
page.reload(wait_until="domcontentloaded")
|
||||
expect(page.locator("text=Set default models")).to_be_visible()
|
||||
llm_combo = page.locator("[data-testid='default-llm-combobox']").first
|
||||
emb_combo = page.locator("[data-testid='default-embedding-combobox']").first
|
||||
expect(llm_combo).to_contain_text("glm-4-flash")
|
||||
expect(emb_combo).to_contain_text(flow_state.get("selected_emb_text") or "embedding-2")
|
||||
added_section = page.locator("[data-testid='added-models-section']")
|
||||
expect(
|
||||
added_section.locator(
|
||||
"[data-testid='added-model-card'][data-provider='ZHIPU-AI']"
|
||||
)
|
||||
).to_be_visible()
|
||||
snap("defaults_persisted")
|
||||
snap("success")
|
||||
|
||||
|
||||
STEPS = [
|
||||
("01_open_login", step_01_open_login),
|
||||
("02_login", step_02_login),
|
||||
("03_open_settings", step_03_open_settings),
|
||||
("04_open_model_providers", step_04_open_model_providers),
|
||||
("05_filter_zhipu", step_05_filter_zhipu),
|
||||
("06_add_api_key", step_06_add_api_key),
|
||||
("07_set_defaults", step_07_set_defaults),
|
||||
("08_verify_persist", step_08_verify_persist),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.p1
|
||||
@pytest.mark.auth
|
||||
@pytest.mark.parametrize("step_fn", flow_params(STEPS))
|
||||
def test_add_zhipu_ai_set_defaults_persist_flow(
|
||||
step_fn,
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
step_fn(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
)
|
||||
397
test/playwright/e2e/test_next_apps_agent.py
Normal file
397
test/playwright/e2e/test_next_apps_agent.py
Normal file
@ -0,0 +1,397 @@
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from playwright.sync_api import expect
|
||||
|
||||
from test.playwright.helpers._auth_helpers import ensure_authed
|
||||
from test.playwright.helpers.flow_steps import flow_params, require
|
||||
from test.playwright.helpers._next_apps_helpers import (
|
||||
RESULT_TIMEOUT_MS,
|
||||
_fill_and_save_create_modal,
|
||||
_goto_home,
|
||||
_nav_click,
|
||||
_open_create_from_list,
|
||||
_unique_name,
|
||||
_wait_for_url_regex,
|
||||
)
|
||||
|
||||
|
||||
def _visible_testids(page, limit: int = 80):
|
||||
try:
|
||||
return page.evaluate(
|
||||
"""
|
||||
(limit) => {
|
||||
const elements = Array.from(document.querySelectorAll('[data-testid]'));
|
||||
const visible = elements.filter((el) => {
|
||||
const style = window.getComputedStyle(el);
|
||||
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
|
||||
return false;
|
||||
}
|
||||
const rect = el.getBoundingClientRect();
|
||||
return rect.width > 0 && rect.height > 0;
|
||||
});
|
||||
const values = Array.from(
|
||||
new Set(
|
||||
visible.map((el) => el.getAttribute('data-testid')).filter(Boolean),
|
||||
),
|
||||
);
|
||||
values.sort();
|
||||
return values.slice(0, limit);
|
||||
}
|
||||
""",
|
||||
limit,
|
||||
)
|
||||
except Exception as exc:
|
||||
return [f"<testid_dump_failed: {exc}>"]
|
||||
|
||||
|
||||
def _raise_with_diagnostics(page, message: str, snap=None, snap_name: str = "") -> None:
|
||||
testids = _visible_testids(page)
|
||||
if snap is not None and snap_name:
|
||||
try:
|
||||
snap(snap_name)
|
||||
except Exception:
|
||||
pass
|
||||
details = f"{message} url={page.url} testids={testids}"
|
||||
print(details, flush=True)
|
||||
raise AssertionError(details)
|
||||
|
||||
|
||||
def _set_import_file(modal, file_path: str) -> None:
|
||||
upload_target = modal.locator("[data-testid='agent-import-file']").first
|
||||
if upload_target.count() == 0:
|
||||
raise AssertionError("agent-import-file not found in import modal.")
|
||||
tag_name = upload_target.evaluate("el => el.tagName.toLowerCase()")
|
||||
if tag_name == "input" and upload_target.get_attribute("type") == "file":
|
||||
upload_target.set_input_files(file_path)
|
||||
return
|
||||
file_input = modal.locator("input[type='file']").first
|
||||
if file_input.count() == 0:
|
||||
raise AssertionError("No file input found in agent import modal.")
|
||||
file_input.set_input_files(file_path)
|
||||
|
||||
|
||||
def step_01_ensure_authed(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
repo_root = Path(__file__).resolve().parents[3]
|
||||
dv_path = repo_root / "test/benchmark/test_docs/dv.json"
|
||||
if not dv_path.is_file():
|
||||
pytest.fail(f"Missing agent import fixture: {dv_path}")
|
||||
flow_state["dv_path"] = str(dv_path)
|
||||
|
||||
with step("ensure logged in"):
|
||||
ensure_authed(
|
||||
flow_page,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
auth_click,
|
||||
seeded_user_credentials=seeded_user_credentials,
|
||||
)
|
||||
flow_state["logged_in"] = True
|
||||
snap("authed")
|
||||
|
||||
|
||||
def step_02_open_agent_list(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "logged_in")
|
||||
page = flow_page
|
||||
with step("open agent list"):
|
||||
_goto_home(page, base_url)
|
||||
_nav_click(page, "nav-agent")
|
||||
expect(page.locator("[data-testid='agents-list']")).to_be_visible(
|
||||
timeout=RESULT_TIMEOUT_MS
|
||||
)
|
||||
snap("agent_list_open")
|
||||
|
||||
|
||||
def step_03_create_first_agent(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "logged_in")
|
||||
page = flow_page
|
||||
first_name = _unique_name("qa-agent")
|
||||
flow_state["first_agent_name"] = first_name
|
||||
with step("create first agent"):
|
||||
_open_create_from_list(
|
||||
page,
|
||||
"agents-empty-create",
|
||||
"create-agent",
|
||||
modal_testid="agent-create-modal",
|
||||
)
|
||||
_fill_and_save_create_modal(
|
||||
page,
|
||||
first_name,
|
||||
modal_testid="agent-create-modal",
|
||||
name_input_testid="agent-name-input",
|
||||
save_testid="agent-save",
|
||||
)
|
||||
expect(page.locator("[data-testid='agents-list']")).to_be_visible(
|
||||
timeout=RESULT_TIMEOUT_MS
|
||||
)
|
||||
expect(page.locator("[data-testid='agent-card']").first).to_be_visible(
|
||||
timeout=RESULT_TIMEOUT_MS
|
||||
)
|
||||
flow_state["first_agent_created"] = True
|
||||
snap("agent_first_created")
|
||||
|
||||
|
||||
def step_04_import_agent(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "first_agent_created", "dv_path")
|
||||
page = flow_page
|
||||
second_name = _unique_name("qa-agent-import")
|
||||
flow_state["second_agent_name"] = second_name
|
||||
with step("import agent json"):
|
||||
create_button = page.locator("[data-testid='create-agent']")
|
||||
expect(create_button).to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
create_button.click()
|
||||
menu = page.locator("[data-testid='agent-create-menu']")
|
||||
expect(menu).to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
menu.locator("[data-testid='agent-import-json']").click()
|
||||
|
||||
modal = page.locator("[data-testid='agent-import-modal']")
|
||||
expect(modal).to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
snap("agent_import_modal")
|
||||
|
||||
_set_import_file(modal, flow_state["dv_path"])
|
||||
name_input = modal.locator("[data-testid='agent-name-input']")
|
||||
expect(name_input).to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
name_input.fill(second_name)
|
||||
save_button = modal.locator("[data-testid='agent-import-save']")
|
||||
expect(save_button).to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
save_button.click()
|
||||
expect(modal).not_to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
flow_state["second_agent_created"] = True
|
||||
snap("agent_second_created")
|
||||
|
||||
|
||||
def step_05_open_imported_agent(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "second_agent_created", "second_agent_name")
|
||||
page = flow_page
|
||||
with step("open imported agent"):
|
||||
card = page.locator(
|
||||
"[data-testid='agent-card']",
|
||||
has=page.locator(
|
||||
"[data-testid='agent-name']", has_text=re.compile(flow_state["second_agent_name"])
|
||||
),
|
||||
).first
|
||||
expect(card).to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
auth_click(card, "open_agent")
|
||||
_wait_for_url_regex(page, r"/agent/")
|
||||
expect(page.locator("[data-testid='agent-detail']")).to_be_visible(
|
||||
timeout=RESULT_TIMEOUT_MS
|
||||
)
|
||||
flow_state["agent_detail_open"] = True
|
||||
snap("agent_detail_open")
|
||||
|
||||
|
||||
def step_06_run_agent(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "agent_detail_open")
|
||||
page = flow_page
|
||||
with step("run agent"):
|
||||
import os
|
||||
|
||||
run_ui_timeout_ms = int(os.getenv("PW_AGENT_RUN_UI_TIMEOUT_MS", "60000"))
|
||||
run_root = page.locator("[data-testid='agent-run']")
|
||||
run_ui_selector = (
|
||||
"[data-testid='agent-run-chat'], "
|
||||
"[data-testid='chat-textarea'], "
|
||||
"[data-testid='agent-run-idle']"
|
||||
)
|
||||
run_ui_locator = page.locator(run_ui_selector)
|
||||
|
||||
try:
|
||||
if run_ui_locator.count() > 0 and run_ui_locator.first.is_visible():
|
||||
flow_state["agent_running"] = True
|
||||
snap("agent_run_already_open")
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if run_root.count() == 0:
|
||||
run_button = page.get_by_role("button", name=re.compile(r"^run$", re.I))
|
||||
else:
|
||||
run_button = run_root
|
||||
expect(run_button).to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
try:
|
||||
auth_click(run_button, "agent_run")
|
||||
except Exception:
|
||||
page.wait_for_timeout(500)
|
||||
auth_click(run_button, "agent_run_retry")
|
||||
|
||||
try:
|
||||
run_ui_locator.first.wait_for(state="visible", timeout=run_ui_timeout_ms)
|
||||
except Exception:
|
||||
_raise_with_diagnostics(
|
||||
page,
|
||||
"Agent run UI did not open after clicking Run.",
|
||||
snap=snap,
|
||||
snap_name="agent_run_missing",
|
||||
)
|
||||
|
||||
flow_state["agent_running"] = True
|
||||
snap("agent_run_started")
|
||||
return
|
||||
|
||||
|
||||
def step_07_send_chat(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "agent_running")
|
||||
page = flow_page
|
||||
with step("send agent chat"):
|
||||
dataset_combobox = page.locator("[data-testid='chat-datasets-combobox']")
|
||||
if dataset_combobox.count() > 0:
|
||||
try:
|
||||
if dataset_combobox.is_visible():
|
||||
dataset_combobox.click()
|
||||
options = page.locator("[data-testid='datasets-options']")
|
||||
expect(options).to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
option = page.locator("[data-testid='datasets-option-0']")
|
||||
if option.count() == 0:
|
||||
option = page.locator("[data-testid^='datasets-option-']").first
|
||||
if option.count() > 0 and option.is_visible():
|
||||
try:
|
||||
flow_state["dataset_label"] = option.inner_text()
|
||||
except Exception:
|
||||
flow_state["dataset_label"] = ""
|
||||
option.click()
|
||||
flow_state["dataset_selected"] = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
textarea = page.locator("[data-testid='chat-textarea']")
|
||||
idle_marker = page.locator("[data-testid='agent-run-idle']")
|
||||
try:
|
||||
expect(textarea).to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
except AssertionError:
|
||||
_raise_with_diagnostics(
|
||||
page,
|
||||
"Chat textarea not visible in agent run UI.",
|
||||
snap=snap,
|
||||
snap_name="agent_run_chat_missing",
|
||||
)
|
||||
|
||||
textarea.fill("say hello")
|
||||
textarea.press("Enter")
|
||||
try:
|
||||
expect(idle_marker).to_be_visible(timeout=60000)
|
||||
except AssertionError:
|
||||
# Older UI builds do not expose agent-run-idle; fallback to assistant reply.
|
||||
agent_chat = page.locator("[data-testid='agent-run-chat']")
|
||||
assistant_reply = agent_chat.locator(
|
||||
"text=/how can i assist|hello/i"
|
||||
).first
|
||||
try:
|
||||
expect(assistant_reply).to_be_visible(timeout=60000)
|
||||
except AssertionError:
|
||||
_raise_with_diagnostics(
|
||||
page,
|
||||
"Agent run chat did not return to idle state after sending message.",
|
||||
snap=snap,
|
||||
snap_name="agent_run_idle_missing",
|
||||
)
|
||||
snap("agent_run_idle_restored")
|
||||
|
||||
|
||||
STEPS = [
|
||||
("01_ensure_authed", step_01_ensure_authed),
|
||||
("02_open_agent_list", step_02_open_agent_list),
|
||||
("03_create_first_agent", step_03_create_first_agent),
|
||||
("04_import_agent", step_04_import_agent),
|
||||
("05_open_imported_agent", step_05_open_imported_agent),
|
||||
("06_run_agent", step_06_run_agent),
|
||||
("07_send_chat", step_07_send_chat),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.p1
|
||||
@pytest.mark.auth
|
||||
@pytest.mark.parametrize("step_fn", flow_params(STEPS))
|
||||
def test_agent_create_then_import_json_then_run_and_wait_idle_flow(
|
||||
step_fn,
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
step_fn(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
)
|
||||
126
test/playwright/e2e/test_next_apps_chat.py
Normal file
126
test/playwright/e2e/test_next_apps_chat.py
Normal file
@ -0,0 +1,126 @@
|
||||
import pytest
|
||||
from playwright.sync_api import expect
|
||||
|
||||
from test.playwright.helpers.flow_context import FlowContext
|
||||
from test.playwright.helpers._auth_helpers import ensure_authed
|
||||
from test.playwright.helpers.flow_steps import flow_params, require
|
||||
from test.playwright.helpers._next_apps_helpers import (
|
||||
RESULT_TIMEOUT_MS,
|
||||
_fill_and_save_create_modal,
|
||||
_goto_home,
|
||||
_nav_click,
|
||||
_open_create_from_list,
|
||||
_select_first_dataset_and_save,
|
||||
_send_chat_and_wait_done,
|
||||
_unique_name,
|
||||
_wait_for_url_or_testid,
|
||||
)
|
||||
|
||||
|
||||
def step_01_ensure_authed(ctx: FlowContext, step, snap):
|
||||
with step("ensure logged in"):
|
||||
ensure_authed(
|
||||
ctx.page,
|
||||
ctx.login_url,
|
||||
ctx.active_auth_context,
|
||||
ctx.auth_click,
|
||||
seeded_user_credentials=ctx.seeded_user_credentials,
|
||||
)
|
||||
ctx.state["logged_in"] = True
|
||||
snap("authed")
|
||||
|
||||
|
||||
def step_02_open_chat_list(ctx: FlowContext, step, snap):
|
||||
require(ctx.state, "logged_in")
|
||||
page = ctx.page
|
||||
with step("open chat list"):
|
||||
_goto_home(page, ctx.base_url)
|
||||
_nav_click(page, "nav-chat")
|
||||
expect(page.locator("[data-testid='chats-list']")).to_be_visible(
|
||||
timeout=RESULT_TIMEOUT_MS
|
||||
)
|
||||
snap("chat_list_open")
|
||||
|
||||
|
||||
def step_03_open_create_modal(ctx: FlowContext, step, snap):
|
||||
require(ctx.state, "logged_in")
|
||||
page = ctx.page
|
||||
with step("open create chat modal"):
|
||||
_open_create_from_list(page, "chats-empty-create", "create-chat")
|
||||
ctx.state["chat_modal_open"] = True
|
||||
snap("chat_create_modal")
|
||||
|
||||
|
||||
def step_04_create_chat(ctx: FlowContext, step, snap):
|
||||
require(ctx.state, "chat_modal_open")
|
||||
page = ctx.page
|
||||
chat_name = _unique_name("qa-chat")
|
||||
ctx.state["chat_name"] = chat_name
|
||||
with step("create chat app"):
|
||||
_fill_and_save_create_modal(page, chat_name)
|
||||
chat_detail = page.locator("[data-testid='chat-detail']")
|
||||
try:
|
||||
_wait_for_url_or_testid(page, r"/next-chat/", "chat-detail", timeout_ms=5000)
|
||||
except AssertionError:
|
||||
list_root = page.locator("[data-testid='chats-list']")
|
||||
expect(list_root).to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
card = list_root.locator(f"text={chat_name}").first
|
||||
expect(card).to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
card.click()
|
||||
expect(chat_detail).to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
ctx.state["chat_created"] = True
|
||||
snap("chat_created")
|
||||
|
||||
|
||||
def step_05_select_dataset(ctx: FlowContext, step, snap):
|
||||
require(ctx.state, "chat_created")
|
||||
page = ctx.page
|
||||
with step("select dataset"):
|
||||
_select_first_dataset_and_save(page, timeout_ms=RESULT_TIMEOUT_MS)
|
||||
ctx.state["chat_dataset_selected"] = True
|
||||
snap("chat_dataset_saved")
|
||||
|
||||
|
||||
def step_06_ask_question(ctx: FlowContext, step, snap):
|
||||
require(ctx.state, "chat_dataset_selected")
|
||||
page = ctx.page
|
||||
with step("ask question"):
|
||||
_send_chat_and_wait_done(page, "what is ragflow", timeout_ms=60000)
|
||||
snap("chat_stream_done")
|
||||
|
||||
|
||||
STEPS = [
|
||||
("01_ensure_authed", step_01_ensure_authed),
|
||||
("02_open_chat_list", step_02_open_chat_list),
|
||||
("03_open_create_modal", step_03_open_create_modal),
|
||||
("04_create_chat", step_04_create_chat),
|
||||
("05_select_dataset", step_05_select_dataset),
|
||||
("06_ask_question", step_06_ask_question),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.p1
|
||||
@pytest.mark.auth
|
||||
@pytest.mark.parametrize("step_fn", flow_params(STEPS))
|
||||
def test_chat_create_select_dataset_and_receive_answer_flow(
|
||||
step_fn,
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
ctx = FlowContext(
|
||||
page=flow_page,
|
||||
state=flow_state,
|
||||
base_url=base_url,
|
||||
login_url=login_url,
|
||||
active_auth_context=active_auth_context,
|
||||
auth_click=auth_click,
|
||||
seeded_user_credentials=seeded_user_credentials,
|
||||
)
|
||||
step_fn(ctx, step, snap)
|
||||
216
test/playwright/e2e/test_next_apps_search.py
Normal file
216
test/playwright/e2e/test_next_apps_search.py
Normal file
@ -0,0 +1,216 @@
|
||||
import pytest
|
||||
from playwright.sync_api import expect
|
||||
|
||||
from test.playwright.helpers._auth_helpers import ensure_authed
|
||||
from test.playwright.helpers.flow_steps import flow_params, require
|
||||
from test.playwright.helpers._next_apps_helpers import (
|
||||
RESULT_TIMEOUT_MS,
|
||||
_fill_and_save_create_modal,
|
||||
_goto_home,
|
||||
_nav_click,
|
||||
_open_create_from_list,
|
||||
_select_first_dataset_and_save,
|
||||
_unique_name,
|
||||
_wait_for_url_or_testid,
|
||||
)
|
||||
|
||||
|
||||
def _wait_for_results_navigation(page, timeout_ms: int = RESULT_TIMEOUT_MS) -> None:
|
||||
wait_js = """
|
||||
() => {
|
||||
const top = document.querySelector("[data-testid='top-nav']");
|
||||
const navs = Array.from(document.querySelectorAll('[role="navigation"]'));
|
||||
return navs.some((nav) => !top || !top.contains(nav));
|
||||
}
|
||||
"""
|
||||
page.wait_for_function(wait_js, timeout=timeout_ms)
|
||||
index = page.evaluate(
|
||||
"""
|
||||
() => {
|
||||
const top = document.querySelector("[data-testid='top-nav']");
|
||||
const navs = Array.from(document.querySelectorAll('[role="navigation"]'));
|
||||
for (let i = 0; i < navs.length; i += 1) {
|
||||
if (!top || !top.contains(navs[i])) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
"""
|
||||
)
|
||||
navs = page.locator("[role='navigation']")
|
||||
target = navs.first if index < 0 else navs.nth(index)
|
||||
expect(target).to_be_visible(timeout=timeout_ms)
|
||||
|
||||
|
||||
def step_01_ensure_authed(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
with step("ensure logged in"):
|
||||
ensure_authed(
|
||||
flow_page,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
auth_click,
|
||||
seeded_user_credentials=seeded_user_credentials,
|
||||
)
|
||||
flow_state["logged_in"] = True
|
||||
snap("authed")
|
||||
|
||||
|
||||
def step_02_open_search_list(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "logged_in")
|
||||
page = flow_page
|
||||
with step("open search list"):
|
||||
_goto_home(page, base_url)
|
||||
_nav_click(page, "nav-search")
|
||||
expect(page.locator("[data-testid='search-list']")).to_be_visible(
|
||||
timeout=RESULT_TIMEOUT_MS
|
||||
)
|
||||
snap("search_list_open")
|
||||
|
||||
|
||||
def step_03_open_create_modal(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "logged_in")
|
||||
page = flow_page
|
||||
with step("open create search modal"):
|
||||
_open_create_from_list(page, "search-empty-create", "create-search")
|
||||
flow_state["search_modal_open"] = True
|
||||
snap("search_create_modal")
|
||||
|
||||
|
||||
def step_04_create_search(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "search_modal_open")
|
||||
page = flow_page
|
||||
search_name = _unique_name("qa-search")
|
||||
flow_state["search_name"] = search_name
|
||||
with step("create search app"):
|
||||
_fill_and_save_create_modal(page, search_name)
|
||||
_wait_for_url_or_testid(page, r"/next-search/", "search-detail")
|
||||
expect(page.locator("[data-testid='search-detail']")).to_be_visible(
|
||||
timeout=RESULT_TIMEOUT_MS
|
||||
)
|
||||
flow_state["search_created"] = True
|
||||
snap("search_created")
|
||||
|
||||
|
||||
def step_05_select_dataset(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "search_created")
|
||||
page = flow_page
|
||||
with step("select dataset"):
|
||||
search_input = page.locator(
|
||||
"input[placeholder*='How can I help you today']"
|
||||
).first
|
||||
_select_first_dataset_and_save(
|
||||
page,
|
||||
timeout_ms=RESULT_TIMEOUT_MS,
|
||||
post_save_ready_locator=search_input,
|
||||
)
|
||||
flow_state["search_input_ready"] = True
|
||||
snap("search_dataset_saved")
|
||||
|
||||
|
||||
def step_06_run_query(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
require(flow_state, "search_input_ready")
|
||||
page = flow_page
|
||||
search_input = page.locator("input[placeholder*='How can I help you today']").first
|
||||
with step("run search query"):
|
||||
expect(search_input).to_be_visible(timeout=RESULT_TIMEOUT_MS)
|
||||
search_input.fill("ragflow")
|
||||
search_input.press("Enter")
|
||||
_wait_for_results_navigation(page, timeout_ms=RESULT_TIMEOUT_MS)
|
||||
snap("search_results_nav")
|
||||
|
||||
|
||||
STEPS = [
|
||||
("01_ensure_authed", step_01_ensure_authed),
|
||||
("02_open_search_list", step_02_open_search_list),
|
||||
("03_open_create_modal", step_03_open_create_modal),
|
||||
("04_create_search", step_04_create_search),
|
||||
("05_select_dataset", step_05_select_dataset),
|
||||
("06_run_query", step_06_run_query),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.p1
|
||||
@pytest.mark.auth
|
||||
@pytest.mark.parametrize("step_fn", flow_params(STEPS))
|
||||
def test_search_create_select_dataset_and_results_nav_appears_flow(
|
||||
step_fn,
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
):
|
||||
step_fn(
|
||||
flow_page,
|
||||
flow_state,
|
||||
base_url,
|
||||
login_url,
|
||||
active_auth_context,
|
||||
step,
|
||||
snap,
|
||||
auth_click,
|
||||
seeded_user_credentials,
|
||||
)
|
||||
Reference in New Issue
Block a user