新增UI测试步骤支持,优化execute_ui_test_case函数,更新相关数据结构和数据库表
This commit is contained in:
parent
b7263f1814
commit
de3972577c
2
.gitignore
vendored
2
.gitignore
vendored
@ -183,5 +183,5 @@ cython_debug/
|
|||||||
*pb2.py*
|
*pb2.py*
|
||||||
*pb.go
|
*pb.go
|
||||||
|
|
||||||
*.sql
|
#*.sql
|
||||||
*.yaml
|
*.yaml
|
@ -44,12 +44,6 @@ CREATE TABLE `composite_case_steps`
|
|||||||
`parameters_json` JSON NULL COMMENT '步骤特定参数,例如:{"endpoint": "/users", "method": "GET"}',
|
`parameters_json` JSON NULL COMMENT '步骤特定参数,例如:{"endpoint": "/users", "method": "GET"}',
|
||||||
`is_required` BOOLEAN DEFAULT TRUE COMMENT '是否必需步骤',
|
`is_required` BOOLEAN DEFAULT TRUE COMMENT '是否必需步骤',
|
||||||
`step_description` TEXT NULL COMMENT '步骤描述',
|
`step_description` TEXT NULL COMMENT '步骤描述',
|
||||||
`selector` VARCHAR(255) NULL COMMENT 'UI元素选择器',
|
|
||||||
`input_value` VARCHAR(255) NULL COMMENT '输入值',
|
|
||||||
`event_type` VARCHAR(50) NULL COMMENT '事件类型',
|
|
||||||
`offset_x` INT NULL COMMENT 'X轴偏移量',
|
|
||||||
`offset_y` INT NULL COMMENT 'Y轴偏移量',
|
|
||||||
`wait_time_seconds` INT NULL COMMENT '等待时间(秒)',
|
|
||||||
`success_next_step_order` INT NULL COMMENT '成功时跳转到的步骤顺序',
|
`success_next_step_order` INT NULL COMMENT '成功时跳转到的步骤顺序',
|
||||||
`failure_next_step_order` INT NULL COMMENT '失败时跳转到的步骤顺序',
|
`failure_next_step_order` INT NULL COMMENT '失败时跳转到的步骤顺序',
|
||||||
`run_condition` JSON NULL COMMENT '执行条件,例如:{"previous_step_id": "step_xyz", "status": "success"}',
|
`run_condition` JSON NULL COMMENT '执行条件,例如:{"previous_step_id": "step_xyz", "status": "success"}',
|
||||||
@ -66,23 +60,22 @@ ALTER TABLE composite_cases
|
|||||||
ADD COLUMN deleted_at DATETIME NULL AFTER updated_at;
|
ADD COLUMN deleted_at DATETIME NULL AFTER updated_at;
|
||||||
|
|
||||||
-- 为composite_case_steps表添加缺失的字段
|
-- 为composite_case_steps表添加缺失的字段
|
||||||
ALTER TABLE composite_case_steps
|
|
||||||
ADD COLUMN step_description TEXT NULL AFTER step_name COMMENT '步骤描述';
|
|
||||||
|
|
||||||
ALTER TABLE composite_case_steps
|
ALTER TABLE composite_case_steps
|
||||||
ADD COLUMN selector VARCHAR(255) NULL AFTER is_required COMMENT 'UI元素选择器';
|
ADD COLUMN selector VARCHAR(255) NULL COMMENT 'UI元素选择器' AFTER is_required;
|
||||||
|
|
||||||
ALTER TABLE composite_case_steps
|
ALTER TABLE composite_case_steps
|
||||||
ADD COLUMN input_value VARCHAR(255) NULL AFTER selector COMMENT '输入值';
|
ADD COLUMN input_value VARCHAR(255) NULL COMMENT '输入值' AFTER selector;
|
||||||
|
|
||||||
ALTER TABLE composite_case_steps
|
ALTER TABLE composite_case_steps
|
||||||
ADD COLUMN event_type VARCHAR(50) NULL AFTER input_value COMMENT '事件类型';
|
ADD COLUMN event_type VARCHAR(50) NULL COMMENT '事件类型' AFTER input_value;
|
||||||
|
|
||||||
ALTER TABLE composite_case_steps
|
ALTER TABLE composite_case_steps
|
||||||
ADD COLUMN offset_x INT NULL AFTER event_type COMMENT 'X轴偏移量';
|
ADD COLUMN offset_x INT NULL COMMENT 'X轴偏移量' AFTER event_type;
|
||||||
|
|
||||||
ALTER TABLE composite_case_steps
|
ALTER TABLE composite_case_steps
|
||||||
ADD COLUMN offset_y INT NULL AFTER offset_x COMMENT 'Y轴偏移量';
|
ADD COLUMN offset_y INT NULL COMMENT 'Y轴偏移量' AFTER offset_x;
|
||||||
|
|
||||||
ALTER TABLE composite_case_steps
|
ALTER TABLE composite_case_steps
|
||||||
ADD COLUMN wait_time_seconds INT NULL AFTER offset_y COMMENT '等待时间(秒)';
|
ADD COLUMN wait_time_seconds INT NULL COMMENT '等待时间(秒)' AFTER offset_y;
|
4
go.sum
4
go.sum
@ -99,6 +99,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
|||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@ -287,6 +289,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||||
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||||
|
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||||
|
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
@ -30,6 +30,12 @@ type CompositeCaseStep struct {
|
|||||||
ActivityName string `json:"activity_name" gorm:"not null;size:255"`
|
ActivityName string `json:"activity_name" gorm:"not null;size:255"`
|
||||||
ParametersJson string `json:"parameters_json" gorm:"type:json"`
|
ParametersJson string `json:"parameters_json" gorm:"type:json"`
|
||||||
IsRequired bool `json:"is_required" gorm:"default:true"`
|
IsRequired bool `json:"is_required" gorm:"default:true"`
|
||||||
|
Selector string `json:"selector" gorm:"size:255"`
|
||||||
|
InputValue string `json:"input_value" gorm:"size:255"`
|
||||||
|
EventType string `json:"event_type" gorm:"size:50"`
|
||||||
|
OffsetX int `json:"offset_x"`
|
||||||
|
OffsetY int `json:"offset_y"`
|
||||||
|
WaitTimeSeconds int `json:"wait_time_seconds"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
@ -51,6 +57,12 @@ type CreateCompositeCaseStepRequest struct {
|
|||||||
ActivityName string `json:"activity_name"`
|
ActivityName string `json:"activity_name"`
|
||||||
ParametersJson string `json:"parameters_json"`
|
ParametersJson string `json:"parameters_json"`
|
||||||
IsRequired bool `json:"is_required"`
|
IsRequired bool `json:"is_required"`
|
||||||
|
Selector string `json:"selector"`
|
||||||
|
InputValue string `json:"input_value"`
|
||||||
|
EventType string `json:"event_type"`
|
||||||
|
OffsetX int `json:"offset_x"`
|
||||||
|
OffsetY int `json:"offset_y"`
|
||||||
|
WaitTimeSeconds int `json:"wait_time_seconds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateCompositeCaseRequest 更新复合案例请求
|
// UpdateCompositeCaseRequest 更新复合案例请求
|
||||||
@ -71,4 +83,10 @@ type UpdateCompositeCaseStepRequest struct {
|
|||||||
ActivityName string `json:"activity_name"`
|
ActivityName string `json:"activity_name"`
|
||||||
ParametersJson string `json:"parameters_json"`
|
ParametersJson string `json:"parameters_json"`
|
||||||
IsRequired bool `json:"is_required"`
|
IsRequired bool `json:"is_required"`
|
||||||
|
Selector string `json:"selector"`
|
||||||
|
InputValue string `json:"input_value"`
|
||||||
|
EventType string `json:"event_type"`
|
||||||
|
OffsetX int `json:"offset_x"`
|
||||||
|
OffsetY int `json:"offset_y"`
|
||||||
|
WaitTimeSeconds int `json:"wait_time_seconds"`
|
||||||
}
|
}
|
||||||
|
@ -26,14 +26,26 @@ message ApiTestRequest {
|
|||||||
int32 expected_status_code = 6;
|
int32 expected_status_code = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 单个UI测试步骤
|
||||||
|
message UiStep {
|
||||||
|
string name = 1; // 步骤名称
|
||||||
|
string selector = 2; // CSS or XPath selector for the element
|
||||||
|
string input_value = 3; // Value to input (for input events)
|
||||||
|
string event_type = 4; // "click", "input", "swipe", "wait"
|
||||||
|
int32 offset_x = 5; // Offset X for click/swipe
|
||||||
|
int32 offset_y = 6; // Offset Y for click/swipe
|
||||||
|
int32 wait_time_seconds = 7; // Time to wait after the event
|
||||||
|
}
|
||||||
|
|
||||||
// 单个UI测试请求的参数
|
// 单个UI测试请求的参数
|
||||||
message UiTestRequest {
|
message UiTestRequest {
|
||||||
string test_case_id = 1; // UI测试用例ID
|
string test_case_id = 1; // UI测试用例ID
|
||||||
string url_path = 2; // 相对于 base_url 的路径
|
string url_path = 2; // 相对于 base_url 的路径
|
||||||
string browser_type = 3; // "chromium", "firefox", "webkit"
|
string browser_type = 3; // "chromium", "firefox", "webkit"
|
||||||
bool headless = 4; // 是否无头模式
|
bool headless = 4; // 是否无头模式
|
||||||
map<string, string> user_data = 5; // 用户名、密码等敏感信息,不建议直接传输,建议 Worker 端获取
|
// map<string, string> user_data = 5; // 用户名、密码等敏感信息,不建议直接传输,建议 Worker 端获取
|
||||||
optional string browser_session_id = 6; // 可选,用于复用现有浏览器会话
|
optional string browser_session_id = 6; // 可选,用于复用现有浏览器会话
|
||||||
|
repeated UiStep steps = 7; // 新增:UI测试步骤列表
|
||||||
}
|
}
|
||||||
|
|
||||||
// 请求关闭浏览器会话
|
// 请求关闭浏览器会话
|
||||||
|
@ -66,6 +66,12 @@ func (s *CompositeCaseService) CreateCompositeCase(req *models.CreateCompositeCa
|
|||||||
ActivityName: activityName,
|
ActivityName: activityName,
|
||||||
ParametersJson: fixParametersJson(stepReq.ParametersJson),
|
ParametersJson: fixParametersJson(stepReq.ParametersJson),
|
||||||
IsRequired: stepReq.IsRequired,
|
IsRequired: stepReq.IsRequired,
|
||||||
|
Selector: stepReq.Selector,
|
||||||
|
InputValue: stepReq.InputValue,
|
||||||
|
EventType: stepReq.EventType,
|
||||||
|
OffsetX: stepReq.OffsetX,
|
||||||
|
OffsetY: stepReq.OffsetY,
|
||||||
|
WaitTimeSeconds: stepReq.WaitTimeSeconds,
|
||||||
}
|
}
|
||||||
steps = append(steps, step)
|
steps = append(steps, step)
|
||||||
}
|
}
|
||||||
@ -190,6 +196,12 @@ func (s *CompositeCaseService) UpdateCompositeCase(id uint, req *models.UpdateCo
|
|||||||
ActivityName: activityName,
|
ActivityName: activityName,
|
||||||
ParametersJson: fixParametersJson(stepReq.ParametersJson),
|
ParametersJson: fixParametersJson(stepReq.ParametersJson),
|
||||||
IsRequired: stepReq.IsRequired,
|
IsRequired: stepReq.IsRequired,
|
||||||
|
Selector: stepReq.Selector,
|
||||||
|
InputValue: stepReq.InputValue,
|
||||||
|
EventType: stepReq.EventType,
|
||||||
|
OffsetX: stepReq.OffsetX,
|
||||||
|
OffsetY: stepReq.OffsetY,
|
||||||
|
WaitTimeSeconds: stepReq.WaitTimeSeconds,
|
||||||
}
|
}
|
||||||
steps = append(steps, step)
|
steps = append(steps, step)
|
||||||
}
|
}
|
||||||
|
0
workers/__init__.py
Normal file
0
workers/__init__.py
Normal file
0
workers/python/__init__.py
Normal file
0
workers/python/__init__.py
Normal file
@ -12,7 +12,7 @@ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'gen'
|
|||||||
from pb import common_test_pb2 as pb
|
from pb import common_test_pb2 as pb
|
||||||
from api_tests import execute_api_test_case
|
from api_tests import execute_api_test_case
|
||||||
from ui_tests import execute_ui_test_case, close_browser_session
|
from ui_tests import execute_ui_test_case, close_browser_session
|
||||||
from utils import upload_file_to_s3, scalar_map_to_dict
|
from workers.python.utils import upload_file_to_s3, scalar_map_to_dict
|
||||||
|
|
||||||
|
|
||||||
class TestActivities:
|
class TestActivities:
|
||||||
@ -134,10 +134,12 @@ class TestActivities:
|
|||||||
activity.heartbeat()
|
activity.heartbeat()
|
||||||
|
|
||||||
# 调用实际的UI测试逻辑,执行浏览器自动化测试并返回本地文件路径
|
# 调用实际的UI测试逻辑,执行浏览器自动化测试并返回本地文件路径
|
||||||
ui_test_success, log_output, screenshot_path, html_report_path = await execute_ui_test_case(
|
ui_test_success, log_output, screenshot_path, html_report_path, browser_session_id = await execute_ui_test_case(
|
||||||
req.test_case_id, req.url_path, req.browser_type, req.headless, scalar_map_to_dict(req.user_data)
|
req.test_case_id, req.url_path, req.browser_type, req.headless, list(req.steps), req.browser_session_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
result.browser_session_id = browser_session_id
|
||||||
|
|
||||||
# 填充基本测试结果
|
# 填充基本测试结果
|
||||||
result.base_result.success = ui_test_success
|
result.base_result.success = ui_test_success
|
||||||
result.base_result.log_output = log_output
|
result.base_result.log_output = log_output
|
||||||
|
@ -9,7 +9,7 @@ from temporalio.worker import Worker
|
|||||||
# 确保能导入 gen 模块
|
# 确保能导入 gen 模块
|
||||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'gen')))
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'gen')))
|
||||||
|
|
||||||
from activities import TestActivities # 导入定义的 Activity
|
from workers.python.activities import TestActivities # 导入定义的 Activity
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
# 连接 Temporal Server
|
# 连接 Temporal Server
|
||||||
|
0
workers/python/pb/__init__.py
Normal file
0
workers/python/pb/__init__.py
Normal file
@ -4,4 +4,5 @@ protobuf
|
|||||||
grpcio-tools # 用于生成 protobuf 代码
|
grpcio-tools # 用于生成 protobuf 代码
|
||||||
requests # 用于API测试
|
requests # 用于API测试
|
||||||
pytest # 测试框架
|
pytest # 测试框架
|
||||||
playwright # UI自动化测试库,或使用 selenium
|
playwright # UI自动化测试库,或使用 selenium
|
||||||
|
pytest-asyncio # Pytest support for asyncio
|
0
workers/python/tests/__init__.py
Normal file
0
workers/python/tests/__init__.py
Normal file
52
workers/python/tests/test_ui_tests.py
Normal file
52
workers/python/tests/test_ui_tests.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
import pytest
|
||||||
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
from workers.python.ui_tests import execute_ui_test_case
|
||||||
|
from workers.python.pb import common_test_pb2 as pb
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_execute_ui_test_case_with_steps():
|
||||||
|
# 1. Setup Mocks
|
||||||
|
mock_page = AsyncMock()
|
||||||
|
mock_browser = AsyncMock()
|
||||||
|
mock_browser.new_page.return_value = mock_page
|
||||||
|
|
||||||
|
# `page.locator` is a sync method returning an object with async methods.
|
||||||
|
# So, we use MagicMock to return our AsyncMock element.
|
||||||
|
mock_element = AsyncMock()
|
||||||
|
mock_page.locator = MagicMock(return_value=mock_element)
|
||||||
|
|
||||||
|
# Patch the playwright instance and browser sessions
|
||||||
|
with patch('workers.python.ui_tests._playwright_instance', new_callable=AsyncMock) as mock_playwright:
|
||||||
|
mock_playwright.chromium.launch.return_value = mock_browser
|
||||||
|
|
||||||
|
with patch('workers.python.ui_tests._browser_sessions', new_callable=dict):
|
||||||
|
# 2. Prepare test data
|
||||||
|
steps = [
|
||||||
|
pb.UiStep(name="Click button", event_type="click", selector="#button1"),
|
||||||
|
pb.UiStep(name="Input text", event_type="input", selector="#input1", input_value="test"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# 3. Call the function under test
|
||||||
|
success, log_output, _, _, _ = await execute_ui_test_case(
|
||||||
|
test_case_id="test1",
|
||||||
|
url_path="/",
|
||||||
|
browser_type="chromium",
|
||||||
|
headless=True,
|
||||||
|
steps=steps,
|
||||||
|
browser_session_id=None
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. Assertions
|
||||||
|
assert success is True, f"Test failed with logs: {log_output}"
|
||||||
|
|
||||||
|
# Assert page navigation
|
||||||
|
mock_page.goto.assert_awaited_once_with('https://playwright.dev/')
|
||||||
|
|
||||||
|
# Assert that the locator was called correctly
|
||||||
|
mock_page.locator.assert_any_call("#button1")
|
||||||
|
mock_page.locator.assert_any_call("#input1")
|
||||||
|
|
||||||
|
# Assert that the async methods on the element were awaited
|
||||||
|
mock_element.click.assert_awaited_once()
|
||||||
|
mock_element.fill.assert_awaited_once_with("test")
|
@ -1,5 +1,5 @@
|
|||||||
# UI 测试具体实现 (使用 Playwright)
|
# UI 测试具体实现 (使用 Playwright)
|
||||||
from playwright.async_api import async_playwright, expect
|
from playwright.async_api import async_playwright
|
||||||
import os
|
import os
|
||||||
import datetime
|
import datetime
|
||||||
import uuid
|
import uuid
|
||||||
@ -9,13 +9,13 @@ from typing import Optional
|
|||||||
_browser_sessions = {}
|
_browser_sessions = {}
|
||||||
_playwright_instance = None
|
_playwright_instance = None
|
||||||
|
|
||||||
async def execute_ui_test_case(test_case_id: str, url_path: str, browser_type: str, headless: bool, user_data: dict, browser_session_id: Optional[str] = None):
|
async def execute_ui_test_case(test_case_id: str, url_path: str, browser_type: str, headless: bool, steps: list, browser_session_id: Optional[str] = None):
|
||||||
"""
|
"""
|
||||||
实际执行UI测试的函数。
|
实际执行UI测试的函数。
|
||||||
支持浏览器会话复用。
|
支持浏览器会话复用。
|
||||||
"""
|
"""
|
||||||
global _browser_sessions, _playwright_instance
|
global _browser_sessions, _playwright_instance
|
||||||
base_url = "https://playwright.dev" # 假设 UI 测试的基地址
|
base_url = "" # 假设 UI 测试的基地址
|
||||||
full_url = f"{base_url}{url_path}"
|
full_url = f"{base_url}{url_path}"
|
||||||
|
|
||||||
log_output = []
|
log_output = []
|
||||||
@ -54,26 +54,36 @@ async def execute_ui_test_case(test_case_id: str, url_path: str, browser_type: s
|
|||||||
|
|
||||||
page = await browser.new_page()
|
page = await browser.new_page()
|
||||||
|
|
||||||
# 模拟登录(如果需要)
|
|
||||||
if user_data:
|
|
||||||
log_output.append(f"Attempting to log in with user: {user_data.get('user')}")
|
|
||||||
await page.goto(f"{base_url}/login")
|
|
||||||
await page.fill('input[name="username"]', user_data.get('user', ''))
|
|
||||||
await page.fill('input[name="password"]', user_data.get('pass', ''))
|
|
||||||
await page.click('button[type="submit"]')
|
|
||||||
await page.wait_for_url(full_url)
|
|
||||||
|
|
||||||
await page.goto(full_url)
|
await page.goto(full_url)
|
||||||
log_output.append(f"Navigated to: {full_url}")
|
log_output.append(f"Navigated to: {full_url}")
|
||||||
|
|
||||||
# 示例UI操作和断言
|
# 遍历并执行所有UI步骤
|
||||||
element = page.locator("text=Playwright enables reliable end-to-end testing for modern web apps.")
|
for step in steps:
|
||||||
await expect(element).to_be_visible()
|
log_output.append(f"Executing step: {step.name} ({step.event_type})")
|
||||||
log_output.append("Found expected text on page.")
|
element = page.locator(step.selector)
|
||||||
|
|
||||||
await page.click("text=Docs")
|
if step.event_type == "click":
|
||||||
await page.wait_for_url("**/docs/intro")
|
await element.click()
|
||||||
log_output.append("Clicked 'Docs' link and navigated.")
|
log_output.append(f"Clicked element with selector: {step.selector}")
|
||||||
|
elif step.event_type == "input":
|
||||||
|
await element.fill(step.input_value)
|
||||||
|
log_output.append(f"Input '{step.input_value}' into element with selector: {step.selector}")
|
||||||
|
elif step.event_type == "swipe":
|
||||||
|
# Playwright doesn't have a built-in swipe, so we simulate with mouse actions
|
||||||
|
box = await element.bounding_box()
|
||||||
|
if box:
|
||||||
|
start_x = box['x'] + box['width'] / 2
|
||||||
|
start_y = box['y'] + box['height'] / 2
|
||||||
|
await page.mouse.move(start_x, start_y)
|
||||||
|
await page.mouse.down()
|
||||||
|
await page.mouse.move(start_x + step.offset_x, start_y + step.offset_y)
|
||||||
|
await page.mouse.up()
|
||||||
|
log_output.append(f"Swiped on element with selector: {step.selector}")
|
||||||
|
elif step.event_type == "wait":
|
||||||
|
await page.wait_for_timeout(step.wait_time_seconds * 1000)
|
||||||
|
log_output.append(f"Waited for {step.wait_time_seconds} seconds.")
|
||||||
|
else:
|
||||||
|
log_output.append(f"Unsupported event type: {step.event_type}")
|
||||||
|
|
||||||
success = True
|
success = True
|
||||||
log_output.append("UI Test PASSED.")
|
log_output.append("UI Test PASSED.")
|
||||||
|
@ -82,11 +82,11 @@ func TestRunWorkflow(ctx workflow.Context, input *pb.TestRunInput) (*pb.TestRunO
|
|||||||
// 构造 UI 测试的请求参数
|
// 构造 UI 测试的请求参数
|
||||||
// 包含浏览器配置和测试页面信息
|
// 包含浏览器配置和测试页面信息
|
||||||
uiTestInput := &pb.UiTestRequest{
|
uiTestInput := &pb.UiTestRequest{
|
||||||
TestCaseId: "ui-example-1", // UI 测试用例标识
|
TestCaseId: "ui-example-1", // UI 测试用例标识
|
||||||
UrlPath: "/dashboard", // 要测试的页面路径
|
UrlPath: "/dashboard", // 要测试的页面路径
|
||||||
BrowserType: "chromium", // 使用的浏览器类型
|
BrowserType: "chromium", // 使用的浏览器类型
|
||||||
Headless: true, // 是否使用无头模式运行浏览器
|
Headless: true, // 是否使用无头模式运行浏览器
|
||||||
UserData: map[string]string{"user": "test", "pass": "password"}, // 测试用的用户数据
|
//UserData: map[string]string{"user": "test", "pass": "password"}, // 测试用的用户数据
|
||||||
}
|
}
|
||||||
|
|
||||||
// 声明变量用于接收 UI 测试的结果
|
// 声明变量用于接收 UI 测试的结果
|
||||||
|
Loading…
Reference in New Issue
Block a user