mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-03-20 22:27:42 +08:00
### What problem does this PR solve? Playwright tests previously depended on cross-file execution order (`auth -> provider -> dataset -> chat`). This change makes setup explicit and idempotent via fixtures so tests can run independently. - Added/standardized prerequisite fixtures in `test/playwright/conftest.py`: - `ensure_auth_context`, `ensure_model_provider_configured`, `ensure_dataset_ready`, `ensure_chat_ready` - Made provisioning reusable/idempotent with `RUN_ID`-based resource naming. - Synced auth envs (`E2E_ADMIN_EMAIL`, `E2E_ADMIN_PASSWORD`) into seeded creds. - Fixed provider cache freshness (`auth_header`/`page` refresh on cache hit). Also included minimal stability fixes: - dataset create stale-element click handling, - search wait logic for results/empty-state, - agent create-menu handling, - agent run-step retry when run UI doesn’t open first click. ### Type of change - [x] Test fix - [x] Refactoring --------- Co-authored-by: Liu An <asiro@qq.com>
222 lines
5.7 KiB
Python
222 lines
5.7 KiB
Python
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,
|
|
_search_query_input,
|
|
_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"]'));
|
|
if (navs.some((nav) => !top || !top.contains(nav))) return true;
|
|
const body = (document.body && document.body.innerText || '').toLowerCase();
|
|
return body.includes('no results found');
|
|
}
|
|
"""
|
|
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)
|
|
if index >= 0:
|
|
expect(target).to_be_visible(timeout=timeout_ms)
|
|
return
|
|
expect(page.locator("text=/no results found/i").first).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 = _search_query_input(page)
|
|
_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 = _search_query_input(page)
|
|
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,
|
|
ensure_dataset_ready,
|
|
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,
|
|
)
|