From 059332e39f49d4a98d13e74f8953be15474c202d Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Sun, 3 Aug 2025 00:46:50 +0800 Subject: [PATCH 1/5] fix: claude code actions --- .github/workflows/claude-code.yml | 6 +++--- internal/version/VERSION | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/claude-code.yml b/.github/workflows/claude-code.yml index 5c1c9bd9..6b6b740d 100644 --- a/.github/workflows/claude-code.yml +++ b/.github/workflows/claude-code.yml @@ -2,11 +2,11 @@ name: Claude Code on: issue_comment: - types: [created] + types: [created, edited] pull_request_review_comment: - types: [created] + types: [created, edited] issues: - types: [opened, assigned] + types: [opened, assigned, edited] pull_request_review: types: [submitted] diff --git a/internal/version/VERSION b/internal/version/VERSION index a36de551..0abffd80 100644 --- a/internal/version/VERSION +++ b/internal/version/VERSION @@ -1 +1 @@ -v5.0.0-250802 +v5.0.0-250803 From c6f358ffca0e7ba9702aa8644779648d7d72f029 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 3 Aug 2025 11:43:21 +0800 Subject: [PATCH 2/5] fix: convert AI tests from skip statements to build tags (#1783) * fix: convert AI tests from skip statements to build tags - Add //go:build localtest tag to uixt/ai/ai_test.go and examples/game/llk/main_test.go - Remove environment-based skip statements and hasRequiredEnvVars functions - Maintain consistency with existing build tag approach for mobile/device tests - Prevents CI/CD failures when external AI services are not available Co-authored-by: debugtalk * fix: add missing BoundBox type and field to Element struct - Add BoundBox struct with X, Y, Width, Height fields - Update Element struct to include BoundBox field - Fix structural mismatch between test expectations and Go code - Resolves CI compilation failures Co-authored-by: debugtalk --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --- examples/game/llk/main.go | 9 +++++++++ examples/game/llk/main_test.go | 18 ++---------------- uixt/ai/ai_test.go | 24 ++---------------------- 3 files changed, 13 insertions(+), 38 deletions(-) diff --git a/examples/game/llk/main.go b/examples/game/llk/main.go index 2e0bfafa..c8e5f201 100644 --- a/examples/game/llk/main.go +++ b/examples/game/llk/main.go @@ -34,6 +34,15 @@ type Dimensions struct { type Element struct { Type string `json:"type"` // Element type/name Position Position `json:"position"` // Position in grid + BoundBox BoundBox `json:"boundBox"` // Bounding box coordinates +} + +// BoundBox represents bounding box coordinates +type BoundBox struct { + X float64 `json:"x"` // X coordinate + Y float64 `json:"y"` // Y coordinate + Width float64 `json:"width"` // Box width + Height float64 `json:"height"` // Box height } // Position represents grid position diff --git a/examples/game/llk/main_test.go b/examples/game/llk/main_test.go index d55667ed..74421801 100644 --- a/examples/game/llk/main_test.go +++ b/examples/game/llk/main_test.go @@ -1,10 +1,11 @@ +//go:build localtest + package llk import ( "context" "encoding/json" "fmt" - "os" "testing" "github.com/stretchr/testify/assert" @@ -97,18 +98,6 @@ func convertToGameElementFromQueryResult(result *ai.QueryResult) (*GameElement, return &gameElement, nil } -// hasRequiredEnvVars checks if the required environment variables are set for testing -func hasRequiredEnvVars() bool { - // Check for OpenAI environment variables - if os.Getenv("OPENAI_BASE_URL") != "" && os.Getenv("OPENAI_API_KEY") != "" { - return true - } - // Check for GPT-4O specific environment variables - if os.Getenv("OPENAI_GPT_4O_BASE_URL") != "" && os.Getenv("OPENAI_GPT_4O_API_KEY") != "" { - return true - } - return false -} // loadTestImage loads the test image from testdata func loadTestImage(t *testing.T) (string, types.Size) { @@ -129,9 +118,6 @@ func createAIQueryer(t *testing.T) *ai.Querier { // TestLLKGameBot_AnalyzeGameInterface comprehensive test for game interface analysis func TestLLKGameBot_AnalyzeGameInterface(t *testing.T) { - if !hasRequiredEnvVars() { - t.Skip("Skipping test: required environment variables not set") - } t.Run("AnalyzeWithTestImage", func(t *testing.T) { // Create test bot and load test image diff --git a/uixt/ai/ai_test.go b/uixt/ai/ai_test.go index 2035c047..0a387784 100644 --- a/uixt/ai/ai_test.go +++ b/uixt/ai/ai_test.go @@ -1,8 +1,9 @@ +//go:build localtest + package ai import ( "context" - "os" "testing" "github.com/httprunner/httprunner/v5/internal/builtin" @@ -11,24 +12,7 @@ import ( "github.com/stretchr/testify/require" ) -// hasRequiredEnvVars checks if the required environment variables are set for testing -func hasRequiredEnvVars() bool { - // Check for OpenAI environment variables - if os.Getenv("OPENAI_BASE_URL") != "" && os.Getenv("OPENAI_API_KEY") != "" { - return true - } - // Check for GPT-4O specific environment variables - if os.Getenv("OPENAI_GPT_4O_BASE_URL") != "" && os.Getenv("OPENAI_GPT_4O_API_KEY") != "" { - return true - } - return false -} - func TestILLMServiceQuery(t *testing.T) { - // Skip test if required environment variables are not set - if !hasRequiredEnvVars() { - t.Skip("Skipping test: required environment variables not set") - } // Create LLM service service, err := NewLLMService(option.OPENAI_GPT_4O) @@ -96,10 +80,6 @@ func TestILLMServiceQuery(t *testing.T) { } func TestILLMServiceIntegration(t *testing.T) { - // Skip test if required environment variables are not set - if !hasRequiredEnvVars() { - t.Skip("Skipping test: required environment variables not set") - } // Create LLM service service, err := NewLLMService(option.OPENAI_GPT_4O) From b25599aba4cfb99eba5b1c285227222b8cafb56a Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Sun, 3 Aug 2025 11:48:22 +0800 Subject: [PATCH 3/5] feat: add CLAUDE.md --- CLAUDE.md | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..ec539501 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,128 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +HttpRunner v5 is a comprehensive testing framework written in Go that supports API testing, load testing, and UI automation across multiple platforms (Android/iOS/Harmony/Browser). The framework integrates LLM technology for intelligent test automation and uses a pure visual-driven approach (OCR/CV/VLM) for UI testing. + +## Development Commands + +### Building +- `make build` - Build the hrp CLI tool with static linking and embedded version info +- `go build -o output/hrp ./cmd/cli` - Alternative build command +- `make test` - Run unit tests with race detection + +### Testing +- `go test -race -v ./...` - Run all tests with race detection +- `go test -v ./tests/...` - Run test suite only +- `go test -v ./uixt/...` - Run UI automation tests +- `go test -v ./cmd/...` - Run CLI command tests + +### Code Quality +- `go mod tidy` - Clean up dependencies +- `gofmt -w .` - Format code +- Pre-commit hooks are available in `scripts/` directory + +## Core Architecture + +### Main Components + +**Core Testing Engine** +- `runner.go` - Main test runner (HRPRunner, CaseRunner, SessionRunner) +- `testcase.go` - Test case definitions and loading (ITestCase interface) +- `step.go` - Step definitions and configurations +- `step_*.go` - Specific step implementations (request, api, testcase, ui, etc.) + +**Step Types** +- `step_request.go` - HTTP/HTTPS requests +- `step_api.go` - API calls with parameters +- `step_testcase.go` - Nested test cases +- `step_websocket.go` - WebSocket communication +- `step_ui.go` - UI automation steps +- `step_transaction.go` - Transaction grouping +- `step_rendezvous.go` - Synchronization points +- `step_shell.go` - Shell command execution +- `step_function.go` - Custom function calls + +**UI Automation (uixt/)** +- `device.go` - Device abstraction interface (IDevice) +- `driver.go` - Driver interface and session management +- `android_*.go` - Android platform implementation (ADB/UIAutomator2) +- `ios_*.go` - iOS platform implementation (WDA) +- `harmony_*.go` - HarmonyOS implementation (HDC) +- `browser_*.go` - Web browser automation +- `ai/` - AI-powered UI interaction (OCR/VLM) + +**CLI Interface (cmd/)** +- `root.go` - Root command and global configuration +- `run.go` - Test execution +- `server.go` - HTTP server mode +- `convert.go` - Format conversion utilities +- `build.go` - Plugin building +- `adb/` - Android device management +- `ios/` - iOS device management + +### Plugin System + +The framework supports both Go and Python plugins: +- `build.go` - Plugin compilation system +- `plugin.go` - Plugin interface definitions +- Templates in `internal/scaffold/templates/plugin/` + +### Configuration Management + +- `config.go` - Global configuration +- `internal/config/` - Environment and settings management +- Environment variables and .env file support + +## Key Design Patterns + +### Interface-Driven Architecture +- `ITestCase` interface for different test case sources +- `IDevice` interface for multi-platform support +- `IDriver` interface for different automation drivers + +### Step-Based Testing +- Each test consists of configurable steps +- Steps support setup/teardown hooks +- Variables and parameters flow between steps + +### Plugin Architecture +- Hashicorp go-plugin for Go plugins +- Python plugin support via funplugin +- Template-based plugin generation + +## Testing Approach + +### Test Formats Supported +- YAML/JSON test cases +- Go test files +- Python pytest integration +- HAR, Postman, cURL conversion + +### UI Testing Strategy +- Pure visual-driven (no element locators) +- OCR/VLM for text recognition +- Cross-platform unified API +- AI-powered interaction planning + +## Development Guidelines + +### Code Structure +- Core framework logic in root directory +- Platform-specific implementations in `uixt/` +- CLI commands in `cmd/` +- Internal utilities in `internal/` +- Examples in `examples/` + +### Dependencies +- Go 1.23+ required +- Uses Cobra for CLI +- Integrates with multiple automation frameworks +- LLM integration via CloudWeGo Eino + +### Build Configuration +- Static linking for deployment +- Version info embedded via ldflags +- Cross-platform builds supported \ No newline at end of file From 758ece299885654b17d50f59d6c1cad630ab77e6 Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Sun, 3 Aug 2025 14:29:34 +0800 Subject: [PATCH 4/5] change: update docs --- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_adb.md | 2 +- docs/cmd/hrp_adb_devices.md | 2 +- docs/cmd/hrp_adb_install.md | 2 +- docs/cmd/hrp_adb_screencap.md | 2 +- docs/cmd/hrp_build.md | 2 +- docs/cmd/hrp_convert.md | 2 +- docs/cmd/hrp_ios.md | 2 +- docs/cmd/hrp_ios_apps.md | 2 +- docs/cmd/hrp_ios_devices.md | 2 +- docs/cmd/hrp_ios_install.md | 2 +- docs/cmd/hrp_ios_mount.md | 2 +- docs/cmd/hrp_ios_ps.md | 2 +- docs/cmd/hrp_ios_reboot.md | 2 +- docs/cmd/hrp_ios_tunnel.md | 2 +- docs/cmd/hrp_ios_uninstall.md | 2 +- docs/cmd/hrp_ios_xctest.md | 2 +- docs/cmd/hrp_mcp-server.md | 2 +- docs/cmd/hrp_mcphost.md | 2 +- docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_report.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_server.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- docs/cmd/hrp_wiki.md | 2 +- examples/game/llk/main_test.go | 2 - examples/game/llk/solver_test.go | 5 +- examples/uitest/android_e2e_delay_test.go | 61 --- examples/uitest/android_e2e_delay_test.json | 151 ------- examples/uitest/android_expert_test.json | 409 ------------------ .../uitest/android_swipe_tap_loadmore.json | 171 ++++++++ examples/uitest/harmony_e2e_delay_test.go | 66 --- examples/uitest/harmony_e2e_delay_test.json | 146 ------- examples/uitest/ios_expert_test.json | 388 ----------------- examples/uitest/sph_search.json | 206 +++++++++ examples/uitest/sph_search_test.go | 64 +++ 36 files changed, 470 insertions(+), 1249 deletions(-) delete mode 100644 examples/uitest/android_e2e_delay_test.go delete mode 100644 examples/uitest/android_e2e_delay_test.json delete mode 100644 examples/uitest/android_expert_test.json create mode 100644 examples/uitest/android_swipe_tap_loadmore.json delete mode 100644 examples/uitest/harmony_e2e_delay_test.go delete mode 100644 examples/uitest/harmony_e2e_delay_test.json delete mode 100644 examples/uitest/ios_expert_test.json create mode 100644 examples/uitest/sph_search.json create mode 100644 examples/uitest/sph_search_test.go diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 22b7a5a2..817e9676 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -63,4 +63,4 @@ Copyright © 2017-present debugtalk. Apache-2.0 License. * [hrp startproject](hrp_startproject.md) - Create a scaffold project * [hrp wiki](hrp_wiki.md) - visit https://httprunner.com -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_adb.md b/docs/cmd/hrp_adb.md index 1d75c96a..b5567af5 100644 --- a/docs/cmd/hrp_adb.md +++ b/docs/cmd/hrp_adb.md @@ -23,4 +23,4 @@ simple utils for android device management * [hrp adb install](hrp_adb_install.md) - push package to the device and install them automatically * [hrp adb screencap](hrp_adb_screencap.md) - Start android screen capture -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_adb_devices.md b/docs/cmd/hrp_adb_devices.md index a0aa341b..48656aab 100644 --- a/docs/cmd/hrp_adb_devices.md +++ b/docs/cmd/hrp_adb_devices.md @@ -24,4 +24,4 @@ hrp adb devices [flags] * [hrp adb](hrp_adb.md) - simple utils for android device management -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_adb_install.md b/docs/cmd/hrp_adb_install.md index 16c1e90a..420b66df 100644 --- a/docs/cmd/hrp_adb_install.md +++ b/docs/cmd/hrp_adb_install.md @@ -28,4 +28,4 @@ hrp adb install [flags] PACKAGE * [hrp adb](hrp_adb.md) - simple utils for android device management -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_adb_screencap.md b/docs/cmd/hrp_adb_screencap.md index 22179c91..defddfb6 100644 --- a/docs/cmd/hrp_adb_screencap.md +++ b/docs/cmd/hrp_adb_screencap.md @@ -25,4 +25,4 @@ hrp adb screencap [flags] * [hrp adb](hrp_adb.md) - simple utils for android device management -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_build.md b/docs/cmd/hrp_build.md index 109582be..a884bbfd 100644 --- a/docs/cmd/hrp_build.md +++ b/docs/cmd/hrp_build.md @@ -36,4 +36,4 @@ hrp build $path ... [flags] * [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index 1d34c9c9..b4bab2a0 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -34,4 +34,4 @@ hrp convert $path... [flags] * [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_ios.md b/docs/cmd/hrp_ios.md index d91fab1b..f9eeebf1 100644 --- a/docs/cmd/hrp_ios.md +++ b/docs/cmd/hrp_ios.md @@ -29,4 +29,4 @@ simple utils for ios device management * [hrp ios uninstall](hrp_ios_uninstall.md) - uninstall package automatically * [hrp ios xctest](hrp_ios_xctest.md) - run xctest -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_ios_apps.md b/docs/cmd/hrp_ios_apps.md index eb02b1fc..ee92d84b 100644 --- a/docs/cmd/hrp_ios_apps.md +++ b/docs/cmd/hrp_ios_apps.md @@ -26,4 +26,4 @@ hrp ios apps [flags] * [hrp ios](hrp_ios.md) - simple utils for ios device management -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_ios_devices.md b/docs/cmd/hrp_ios_devices.md index fbd2a6ee..d86bfcd3 100644 --- a/docs/cmd/hrp_ios_devices.md +++ b/docs/cmd/hrp_ios_devices.md @@ -24,4 +24,4 @@ hrp ios devices [flags] * [hrp ios](hrp_ios.md) - simple utils for ios device management -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_ios_install.md b/docs/cmd/hrp_ios_install.md index 74bcc464..4d06ea83 100644 --- a/docs/cmd/hrp_ios_install.md +++ b/docs/cmd/hrp_ios_install.md @@ -25,4 +25,4 @@ hrp ios install [flags] PACKAGE * [hrp ios](hrp_ios.md) - simple utils for ios device management -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_ios_mount.md b/docs/cmd/hrp_ios_mount.md index b08ceb11..280c47b5 100644 --- a/docs/cmd/hrp_ios_mount.md +++ b/docs/cmd/hrp_ios_mount.md @@ -28,4 +28,4 @@ hrp ios mount [flags] * [hrp ios](hrp_ios.md) - simple utils for ios device management -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_ios_ps.md b/docs/cmd/hrp_ios_ps.md index 7e44e89a..675a4603 100644 --- a/docs/cmd/hrp_ios_ps.md +++ b/docs/cmd/hrp_ios_ps.md @@ -26,4 +26,4 @@ hrp ios ps [flags] * [hrp ios](hrp_ios.md) - simple utils for ios device management -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_ios_reboot.md b/docs/cmd/hrp_ios_reboot.md index 0ff1628f..641a1022 100644 --- a/docs/cmd/hrp_ios_reboot.md +++ b/docs/cmd/hrp_ios_reboot.md @@ -25,4 +25,4 @@ hrp ios reboot [flags] * [hrp ios](hrp_ios.md) - simple utils for ios device management -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_ios_tunnel.md b/docs/cmd/hrp_ios_tunnel.md index 07698c8d..18dd3bd0 100644 --- a/docs/cmd/hrp_ios_tunnel.md +++ b/docs/cmd/hrp_ios_tunnel.md @@ -24,4 +24,4 @@ hrp ios tunnel [flags] * [hrp ios](hrp_ios.md) - simple utils for ios device management -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_ios_uninstall.md b/docs/cmd/hrp_ios_uninstall.md index 4674cf29..4c4efcb5 100644 --- a/docs/cmd/hrp_ios_uninstall.md +++ b/docs/cmd/hrp_ios_uninstall.md @@ -26,4 +26,4 @@ hrp ios uninstall [flags] PACKAGE * [hrp ios](hrp_ios.md) - simple utils for ios device management -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_ios_xctest.md b/docs/cmd/hrp_ios_xctest.md index 57807f93..65032225 100644 --- a/docs/cmd/hrp_ios_xctest.md +++ b/docs/cmd/hrp_ios_xctest.md @@ -28,4 +28,4 @@ hrp ios xctest [flags] * [hrp ios](hrp_ios.md) - simple utils for ios device management -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_mcp-server.md b/docs/cmd/hrp_mcp-server.md index 28e05be4..e79e353a 100644 --- a/docs/cmd/hrp_mcp-server.md +++ b/docs/cmd/hrp_mcp-server.md @@ -28,4 +28,4 @@ hrp mcp-server [flags] * [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_mcphost.md b/docs/cmd/hrp_mcphost.md index dce4bb76..6a0834b2 100644 --- a/docs/cmd/hrp_mcphost.md +++ b/docs/cmd/hrp_mcphost.md @@ -31,4 +31,4 @@ hrp mcphost [flags] * [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index 20f2acca..b7dff5e7 100644 --- a/docs/cmd/hrp_pytest.md +++ b/docs/cmd/hrp_pytest.md @@ -24,4 +24,4 @@ hrp pytest $path ... [flags] * [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_report.md b/docs/cmd/hrp_report.md index c1a63415..b5ef2375 100644 --- a/docs/cmd/hrp_report.md +++ b/docs/cmd/hrp_report.md @@ -33,4 +33,4 @@ hrp report [result_folder] [flags] * [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 3bfca80f..01dacdf9 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -46,4 +46,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_server.md b/docs/cmd/hrp_server.md index cd8c3e21..1dfe0a6a 100644 --- a/docs/cmd/hrp_server.md +++ b/docs/cmd/hrp_server.md @@ -30,4 +30,4 @@ hrp server start [flags] * [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index 64784a0e..40a3ef8e 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -29,4 +29,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/docs/cmd/hrp_wiki.md b/docs/cmd/hrp_wiki.md index 643ae924..94ea3c70 100644 --- a/docs/cmd/hrp_wiki.md +++ b/docs/cmd/hrp_wiki.md @@ -24,4 +24,4 @@ hrp wiki [flags] * [hrp](hrp.md) - All-in-One Testing Framework for API, UI and Performance -###### Auto generated by spf13/cobra on 28-Jun-2025 +###### Auto generated by spf13/cobra on 3-Aug-2025 diff --git a/examples/game/llk/main_test.go b/examples/game/llk/main_test.go index 74421801..348cfe3e 100644 --- a/examples/game/llk/main_test.go +++ b/examples/game/llk/main_test.go @@ -98,7 +98,6 @@ func convertToGameElementFromQueryResult(result *ai.QueryResult) (*GameElement, return &gameElement, nil } - // loadTestImage loads the test image from testdata func loadTestImage(t *testing.T) (string, types.Size) { screenshot, size, err := builtin.LoadImage("../../../uixt/ai/testdata/llk_1.png") @@ -118,7 +117,6 @@ func createAIQueryer(t *testing.T) *ai.Querier { // TestLLKGameBot_AnalyzeGameInterface comprehensive test for game interface analysis func TestLLKGameBot_AnalyzeGameInterface(t *testing.T) { - t.Run("AnalyzeWithTestImage", func(t *testing.T) { // Create test bot and load test image querier := createAIQueryer(t) diff --git a/examples/game/llk/solver_test.go b/examples/game/llk/solver_test.go index b6b7acc3..ecfe3430 100644 --- a/examples/game/llk/solver_test.go +++ b/examples/game/llk/solver_test.go @@ -1,3 +1,5 @@ +//go:build localtest + package llk import ( @@ -7,10 +9,11 @@ import ( "os" "testing" - "github.com/httprunner/httprunner/v5/uixt/ai" "github.com/rs/zerolog/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/httprunner/httprunner/v5/uixt/ai" ) // TestLLKSolver tests the LianLianKan solver functionality diff --git a/examples/uitest/android_e2e_delay_test.go b/examples/uitest/android_e2e_delay_test.go deleted file mode 100644 index 6b113c4c..00000000 --- a/examples/uitest/android_e2e_delay_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package uitest - -import ( - "testing" - - hrp "github.com/httprunner/httprunner/v5" - "github.com/httprunner/httprunner/v5/uixt/option" -) - -func TestAndroidDouyinE2E(t *testing.T) { - testCase := &hrp.TestCase{ - Config: hrp.NewConfig("直播_抖音_端到端时延_android"). - WithVariables(map[string]interface{}{ - "device": "${ENV(SerialNumber)}", - "ups": "${ENV(LIVEUPLIST)}", - }). - SetAndroid( - option.WithSerialNumber("$device"), - option.WithAdbLogOn(true)), - TestSteps: []hrp.IStep{ - hrp.NewStep("启动抖音"). - Android(). - AppTerminate("com.ss.android.ugc.aweme"). - AppLaunch("com.ss.android.ugc.aweme"). - Home(). - SwipeToTapApp( - "抖音", - option.WithMaxRetryTimes(5), - option.WithTapOffset(0, -50), - ). - Sleep(20). - Validate(). - AssertOCRExists("推荐", "进入抖音失败"), - hrp.NewStep("点击放大镜"). - Android(). - TapXY(0.9, 0.08). - Sleep(5), - hrp.NewStep("输入账号名称"). - Android(). - Input("$ups"). - Sleep(5), - hrp.NewStep("点击搜索"). - Android(). - TapByOCR("搜索"). - Sleep(5), - hrp.NewStep("端到端采集").Loop(5). - Android(). - TapByOCR( - "直播中", - option.WithIgnoreNotFoundError(true), - option.WithIndex(-1), - ). - EndToEndDelay(option.WithInterval(5), option.WithTimeout(120)). - TapByUITypes(option.WithScreenShotUITypes("close")), - }, - } - - if err := testCase.Dump2JSON("android_e2e_delay_test.json"); err != nil { - t.Fatal(err) - } -} diff --git a/examples/uitest/android_e2e_delay_test.json b/examples/uitest/android_e2e_delay_test.json deleted file mode 100644 index d289d61f..00000000 --- a/examples/uitest/android_e2e_delay_test.json +++ /dev/null @@ -1,151 +0,0 @@ -{ - "config": { - "name": "直播_抖音_端到端时延_android", - "variables": { - "device": "${ENV(SerialNumber)}", - "ups": "${ENV(LIVEUPLIST)}" - }, - "android": [ - { - "serial": "$device", - "log_on": true, - "adb_server_host": "localhost", - "adb_server_port": 5037, - "uia2_ip": "localhost", - "uia2_port": 6790, - "uia2_server_package_name": "io.appium.uiautomator2.server", - "uia2_server_test_package_name": "io.appium.uiautomator2.server.test" - } - ] - }, - "teststeps": [ - { - "name": "启动抖音", - "android": { - "os_type": "android", - "actions": [ - { - "method": "app_terminate", - "params": "com.ss.android.ugc.aweme" - }, - { - "method": "app_launch", - "params": "com.ss.android.ugc.aweme" - }, - { - "method": "home" - }, - { - "method": "swipe_to_tap_app", - "params": "抖音", - "options": { - "max_retry_times": 5, - "tap_offset": [ - 0, - -50 - ] - } - }, - { - "method": "sleep", - "params": 20 - } - ] - }, - "validate": [ - { - "check": "ui_ocr", - "assert": "exists", - "expect": "推荐", - "msg": "进入抖音失败" - } - ] - }, - { - "name": "点击放大镜", - "android": { - "os_type": "android", - "actions": [ - { - "method": "tap_xy", - "params": [ - 0.9, - 0.08 - ], - "options": {} - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "输入账号名称", - "android": { - "os_type": "android", - "actions": [ - { - "method": "input", - "params": "$ups", - "options": {} - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "点击搜索", - "android": { - "os_type": "android", - "actions": [ - { - "method": "tap_ocr", - "params": "搜索", - "options": {} - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "端到端采集", - "loops": 5, - "android": { - "os_type": "android", - "actions": [ - { - "method": "tap_ocr", - "params": "直播中", - "options": { - "index": -1, - "ignore_NotFoundError": true - } - }, - { - "method": "live_e2e", - "options": { - "interval": 5, - "timeout": 120 - } - }, - { - "method": "tap_cv", - "options": { - "screenshot_with_ui_types": [ - "close" - ] - } - } - ] - } - } - ] -} diff --git a/examples/uitest/android_expert_test.json b/examples/uitest/android_expert_test.json deleted file mode 100644 index 1b11d98d..00000000 --- a/examples/uitest/android_expert_test.json +++ /dev/null @@ -1,409 +0,0 @@ -{ - "config": { - "name": "安卓专家用例", - "variables": { - "app_name": "抖音", - "bundle_id": "com.ss.android.ugc.aweme", - "device": "${ENV(SerialNumber)}", - "query": "${ENV(query)}" - }, - "android": [ - { - "serial": "$device", - "log_on": true, - "adb_server_host": "localhost", - "adb_server_port": 5037, - "uia2": true, - "uia2_ip": "localhost", - "uia2_port": 6790, - "uia2_server_package_name": "io.appium.uiautomator2.server", - "uia2_server_test_package_name": "io.appium.uiautomator2.server.test" - } - ] - }, - "teststeps": [ - { - "name": "app_launch 以及 ui_foreground_app equal 断言", - "android": { - "os_type": "android", - "actions": [ - { - "method": "app_launch", - "params": "$bundle_id" - }, - { - "method": "sleep", - "params": 2 - } - ] - }, - "validate": [ - { - "check": "ui_foreground_app", - "assert": "equal", - "expect": "$bundle_id", - "msg": "app [$bundle_id] should be in foreground" - } - ] - }, - { - "name": "home 以及 swipe_to_tap_app 默认配置", - "android": { - "os_type": "android", - "actions": [ - { - "method": "home" - }, - { - "method": "swipe_to_tap_app", - "params": "$app_name", - "options": {} - }, - { - "method": "sleep", - "params": 10 - } - ] - } - }, - { - "name": "处理弹窗 close_popups 默认配置 以及 ui_ocr exists 断言", - "android": { - "os_type": "android", - "actions": [ - { - "method": "close_popups", - "options": {} - } - ] - }, - "validate": [ - { - "check": "ui_ocr", - "assert": "exists", - "expect": "推荐", - "msg": "进入抖音失败" - } - ] - }, - { - "name": "【直播】feed头像或卡片进房 swipe_to_tap_texts 自定义配置", - "android": { - "os_type": "android", - "actions": [ - { - "method": "swipe_to_tap_texts", - "params": [ - "直播", - "直播中", - "点击进入直播间" - ], - "options": { - "identifier": "click_live", - "max_retry_times": 50, - "interval": 1.5, - "direction": [ - 0.5, - 0.7, - 0.5, - 0.3 - ], - "scope": [ - 0.2, - 0.2, - 1, - 0.8 - ] - } - } - ] - } - }, - { - "name": "sleep 10s", - "android": { - "os_type": "android", - "actions": [ - { - "method": "sleep", - "params": 10 - } - ] - } - }, - { - "name": "【直播】swipe 自定义配置 以及 back", - "android": { - "os_type": "android", - "actions": [ - { - "method": "swipe_coordinate", - "params": [ - 0.5, - 0.7, - 0.5, - 0.3 - ], - "options": { - "identifier": "slide_in_live" - } - }, - { - "method": "sleep", - "params": 5 - }, - { - "method": "back" - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "【搜索】点击放大镜 tap_xy 自定义配置", - "android": { - "os_type": "android", - "actions": [ - { - "method": "tap_xy", - "params": [ - 0.9, - 0.08 - ], - "options": { - "identifier": "click_search_in_middle_page" - } - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "【搜索】输入query词 input", - "android": { - "os_type": "android", - "actions": [ - { - "method": "input", - "params": "$query", - "options": { - "identifier": "input_query" - } - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "【搜索】点击搜索按钮 tap_ocr 自定义配置", - "android": { - "os_type": "android", - "actions": [ - { - "method": "tap_ocr", - "params": "搜索", - "options": { - "identifier": "click_search_after_input_query" - } - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "选择直播页签 tap_ocr 默认配置", - "android": { - "os_type": "android", - "actions": [ - { - "method": "tap_ocr", - "params": "直播", - "options": {} - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "【生活服务】进入直播间 tap_xy", - "android": { - "os_type": "android", - "actions": [ - { - "method": "tap_xy", - "params": [ - 0.5, - 0.5 - ], - "options": {} - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "【生活服务】点击货架商品 tap_ocr 自定义配置", - "android": { - "os_type": "android", - "actions": [ - { - "method": "tap_cv", - "options": { - "identifier": "click_sales_rack", - "screenshot_with_ui_types": [ - "dyhouse", - "shoppingbag" - ] - } - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "app_terminate 以及 ui_foreground_app not_equal 断言", - "android": { - "os_type": "android", - "actions": [ - { - "method": "app_terminate", - "params": "$bundle_id" - }, - { - "method": "sleep", - "params": 2 - } - ] - }, - "validate": [ - { - "check": "ui_foreground_app", - "assert": "not_equal", - "expect": "$bundle_id", - "msg": "app [$bundle_id] should not be in foreground" - } - ] - }, - { - "name": "home 以及 swipe_to_tap_app 自定义配置", - "android": { - "os_type": "android", - "actions": [ - { - "method": "home" - }, - { - "method": "swipe_to_tap_app", - "params": "$app_name", - "options": { - "max_retry_times": 5, - "interval": 1, - "tap_offset": [ - 0, - -50 - ] - } - }, - { - "method": "sleep", - "params": 10 - } - ] - } - }, - { - "name": "处理弹窗 close_popups 自定义配置 以及 ui_ocr exists 断言", - "android": { - "os_type": "android", - "actions": [ - { - "method": "close_popups", - "options": { - "max_retry_times": 3, - "interval": 2 - } - } - ] - }, - "validate": [ - { - "check": "ui_ocr", - "assert": "exists", - "expect": "推荐", - "msg": "进入抖音失败" - } - ] - }, - { - "name": "返回主界面,并打开本地时间戳", - "android": { - "os_type": "android", - "actions": [ - { - "method": "home" - }, - { - "method": "app_terminate", - "params": "$bundle_id" - }, - { - "method": "sleep", - "params": 3 - }, - { - "method": "swipe_to_tap_app", - "params": "local", - "options": { - "max_retry_times": 5 - } - }, - { - "method": "sleep", - "params": 10 - } - ] - } - }, - { - "name": "screeshot 以及 sleep_random", - "loops": 3, - "android": { - "os_type": "android", - "actions": [ - { - "method": "screenshot", - "options": {} - }, - { - "method": "sleep_random", - "params": [ - 1, - 3 - ] - } - ] - } - } - ] -} diff --git a/examples/uitest/android_swipe_tap_loadmore.json b/examples/uitest/android_swipe_tap_loadmore.json new file mode 100644 index 00000000..7b935158 --- /dev/null +++ b/examples/uitest/android_swipe_tap_loadmore.json @@ -0,0 +1,171 @@ +{ + "config": { + "name": "起点_安卓_无限流加载耗时", + "variables": { + "device": "${ENV(SerialNumber)}" + }, + "android": [ + { + "serial": "$device", + "log_on": true, + "ignore_popup": true + } + ] + }, + "teststeps": [ + { + "name": "杀掉之前清除缓存的进程", + "android": { + "actions": [ + { + "method": "app_terminate", + "params": "com.qidian.QDReader" + }, + { + "method": "sleep", + "params": 30 + } + + ] + } + }, + { + "name": "冷启动起点读书app", + "android": { + "actions": [ + { + "method": "app_launch", + "params": "com.qidian.QDReader" + }, + { + "method": "sleep", + "params": 30 + } + ] + } + }, + { + "name": "进入精选-男生频道", + "android":{ + "actions":[ + { + "method": "tap_ocr", + "params": "精选", + "offset": [ + 0, + -50 + ] + }, + { + "method": "sleep", + "params": 7 + }, + { + "method": "tap_ocr", + "params": "男生" + }, + { + "method": "sleep", + "params": 7 + } + ] + } + }, + { + "name": "向下滑动,触发加载", + "android": { + "actions": [ + { + "method": "swipe", + "params": [ + 0.5, + 0.8, + 0.5, + 0.2 + ], + "steps": 1, + "identifier": "xiaoshuo_swip_tab_loadmore" + }, + { + "method": "sleep", + "params": 3 + }, + { + "method": "swipe", + "params": [ + 0.5, + 0.8, + 0.5, + 0.2 + ], + "steps": 1 + }, + { + "method": "sleep", + "params": 3 + }, + { + "method": "swipe", + "params": [ + 0.5, + 0.8, + 0.5, + 0.2 + ], + "steps": 1 + }, + { + "method": "sleep", + "params": 3 + }, + { + "method": "swipe", + "params": [ + 0.5, + 0.8, + 0.5, + 0.2 + ], + "steps": 1 + }, + { + "method": "sleep", + "params": 3 + }, + { + "method": "swipe", + "params": [ + 0.5, + 0.8, + 0.5, + 0.2 + ], + "steps": 1 + } + ] + } + }, + { + "name": "返回", + "android": { + "actions": [ + { + "method": "home" + }, + { + "method": "swipe_to_tap_app", + "params": "local", + "offset": [ + 0, + -50 + ] + }, + { + "method": "sleep", + "params": 7 + } + ] + } + } + ] +} diff --git a/examples/uitest/harmony_e2e_delay_test.go b/examples/uitest/harmony_e2e_delay_test.go deleted file mode 100644 index 31312a7a..00000000 --- a/examples/uitest/harmony_e2e_delay_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package uitest - -import ( - "testing" - - hrp "github.com/httprunner/httprunner/v5" - "github.com/httprunner/httprunner/v5/uixt/option" -) - -func TestHarmonyDouyinE2E(t *testing.T) { - testCase := &hrp.TestCase{ - Config: hrp.NewConfig("直播_抖音_端到端时延_harmony"). - WithVariables(map[string]interface{}{ - "device": "${ENV(SerialNumber)}", - "ups": "${ENV(LIVEUPLIST)}", - }). - SetHarmony( - option.WithConnectKey("$device"), - option.WithLogOn(true)), - TestSteps: []hrp.IStep{ - hrp.NewStep("启动抖音"). - Harmony(). - AppTerminate("com.ss.hm.ugc.aweme"). - SwipeToTapApp("com.ss.hm.ugc.aweme"). - Home(). - SwipeToTapApp( - "抖音", - option.WithMaxRetryTimes(5), - option.WithTapOffset(0, -50), - ). - Sleep(20). - Validate(). - AssertOCRExists("推荐", "进入抖音失败"), - hrp.NewStep("点击放大镜"). - Harmony(). - TapXY(0.9, 0.08). - Sleep(5), - hrp.NewStep("输入账号名称"). - Harmony(). - Input("$ups"). - Sleep(5), - hrp.NewStep("点击搜索"). - Harmony(). - TapByOCR("搜索"). - Sleep(5), - hrp.NewStep("端到端采集").Loop(5). - Harmony(). - TapByOCR( - "直播中", - option.WithIgnoreNotFoundError(true), - option.WithIndex(-1), - ). - EndToEndDelay(option.WithInterval(5), option.WithTimeout(120)). - TapByUITypes(option.WithScreenShotUITypes("close")), - }, - } - - if err := testCase.Dump2JSON("harmony_e2e_delay_test.json"); err != nil { - t.Fatal(err) - } - - err := hrp.Run(t, testCase) - if err != nil { - t.Fatal(err) - } -} diff --git a/examples/uitest/harmony_e2e_delay_test.json b/examples/uitest/harmony_e2e_delay_test.json deleted file mode 100644 index dc57f7ed..00000000 --- a/examples/uitest/harmony_e2e_delay_test.json +++ /dev/null @@ -1,146 +0,0 @@ -{ - "config": { - "name": "直播_抖音_端到端时延_harmony", - "variables": { - "device": "${ENV(SerialNumber)}", - "ups": "${ENV(LIVEUPLIST)}" - }, - "harmony": [ - { - "connect_key": "$device", - "log_on": true - } - ] - }, - "teststeps": [ - { - "name": "启动抖音", - "harmony": { - "os_type": "harmony", - "actions": [ - { - "method": "app_terminate", - "params": "com.ss.hm.ugc.aweme" - }, - { - "method": "swipe_to_tap_app", - "params": "com.ss.hm.ugc.aweme", - "options": {} - }, - { - "method": "home" - }, - { - "method": "swipe_to_tap_app", - "params": "抖音", - "options": { - "max_retry_times": 5, - "tap_offset": [ - 0, - -50 - ] - } - }, - { - "method": "sleep", - "params": 20 - } - ] - }, - "validate": [ - { - "check": "ui_ocr", - "assert": "exists", - "expect": "推荐", - "msg": "进入抖音失败" - } - ] - }, - { - "name": "点击放大镜", - "harmony": { - "os_type": "harmony", - "actions": [ - { - "method": "tap_xy", - "params": [ - 0.9, - 0.08 - ], - "options": {} - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "输入账号名称", - "harmony": { - "os_type": "harmony", - "actions": [ - { - "method": "input", - "params": "$ups", - "options": {} - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "点击搜索", - "harmony": { - "os_type": "harmony", - "actions": [ - { - "method": "tap_ocr", - "params": "搜索", - "options": {} - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "端到端采集", - "loops": 5, - "harmony": { - "os_type": "harmony", - "actions": [ - { - "method": "tap_ocr", - "params": "直播中", - "options": { - "index": -1, - "ignore_NotFoundError": true - } - }, - { - "method": "live_e2e", - "options": { - "interval": 5, - "timeout": 120 - } - }, - { - "method": "tap_cv", - "options": { - "screenshot_with_ui_types": [ - "close" - ] - } - } - ] - } - } - ] -} diff --git a/examples/uitest/ios_expert_test.json b/examples/uitest/ios_expert_test.json deleted file mode 100644 index 3693d169..00000000 --- a/examples/uitest/ios_expert_test.json +++ /dev/null @@ -1,388 +0,0 @@ -{ - "config": { - "name": "iOS 专家用例", - "variables": { - "app_name": "抖音", - "bundle_id": "com.ss.iphone.ugc.Aweme", - "device": "${ENV(UDID)}", - "query": "${ENV(query)}" - }, - "ios": [ - { - "udid": "$device", - "port": 8700, - "mjpeg_port": 8800, - "log_on": true - } - ] - }, - "teststeps": [ - { - "name": "启动应用程序 app_launch", - "ios": { - "os_type": "ios", - "actions": [ - { - "method": "app_launch", - "params": "$bundle_id" - }, - { - "method": "sleep", - "params": 2 - } - ] - } - }, - { - "name": "home 以及 swipe_to_tap_app 默认配置", - "ios": { - "os_type": "ios", - "actions": [ - { - "method": "home" - }, - { - "method": "swipe_to_tap_app", - "params": "$app_name", - "options": {} - }, - { - "method": "sleep", - "params": 10 - } - ] - } - }, - { - "name": "处理弹窗 close_popups 默认配置 以及 ui_ocr exists 断言", - "ios": { - "os_type": "ios", - "actions": [ - { - "method": "close_popups", - "options": {} - } - ] - }, - "validate": [ - { - "check": "ui_ocr", - "assert": "exists", - "expect": "推荐", - "msg": "进入抖音失败" - } - ] - }, - { - "name": "【直播】feed头像或卡片进房 swipe_to_tap_texts 自定义配置", - "ios": { - "os_type": "ios", - "actions": [ - { - "method": "swipe_to_tap_texts", - "params": [ - "直播", - "直播中", - "点击进入直播间" - ], - "options": { - "identifier": "click_live", - "max_retry_times": 50, - "interval": 1.5, - "direction": [ - 0.5, - 0.7, - 0.5, - 0.3 - ], - "scope": [ - 0.2, - 0.2, - 1, - 0.8 - ] - } - } - ] - } - }, - { - "name": "sleep 10s", - "ios": { - "os_type": "ios", - "actions": [ - { - "method": "sleep", - "params": 10 - } - ] - } - }, - { - "name": "【直播】swipe 自定义配置 以及 back", - "ios": { - "os_type": "ios", - "actions": [ - { - "method": "swipe_coordinate", - "params": [ - 0.5, - 0.7, - 0.5, - 0.3 - ], - "options": { - "identifier": "slide_in_live" - } - }, - { - "method": "sleep", - "params": 5 - }, - { - "method": "back" - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "【搜索】点击放大镜 tap_xy 自定义配置", - "ios": { - "os_type": "ios", - "actions": [ - { - "method": "tap_xy", - "params": [ - 0.9, - 0.075 - ], - "options": { - "identifier": "click_search_in_middle_page" - } - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "【搜索】输入query词 input", - "ios": { - "os_type": "ios", - "actions": [ - { - "method": "input", - "params": "$query", - "options": { - "identifier": "input_query" - } - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "【搜索】点击搜索按钮 tap_ocr 自定义配置", - "ios": { - "os_type": "ios", - "actions": [ - { - "method": "tap_ocr", - "params": "搜索", - "options": { - "identifier": "click_search_after_input_query" - } - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "选择直播页签 tap_ocr 默认配置", - "ios": { - "os_type": "ios", - "actions": [ - { - "method": "tap_ocr", - "params": "直播", - "options": {} - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "【生活服务】进入直播间 tap_xy", - "ios": { - "os_type": "ios", - "actions": [ - { - "method": "tap_xy", - "params": [ - 0.5, - 0.5 - ], - "options": {} - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "【生活服务】点击货架商品 tap_ocr 自定义配置", - "ios": { - "os_type": "ios", - "actions": [ - { - "method": "tap_cv", - "options": { - "identifier": "click_sales_rack", - "screenshot_with_ui_types": [ - "dyhouse", - "shoppingbag" - ] - } - }, - { - "method": "sleep", - "params": 5 - } - ] - } - }, - { - "name": "终止应用程序 app_terminate", - "ios": { - "os_type": "ios", - "actions": [ - { - "method": "app_terminate", - "params": "$bundle_id" - }, - { - "method": "sleep", - "params": 2 - } - ] - } - }, - { - "name": "home 以及 swipe_to_tap_app 自定义配置", - "ios": { - "os_type": "ios", - "actions": [ - { - "method": "home" - }, - { - "method": "swipe_to_tap_app", - "params": "$app_name", - "options": { - "max_retry_times": 5, - "interval": 1, - "tap_offset": [ - 0, - -50 - ] - } - }, - { - "method": "sleep", - "params": 10 - } - ] - } - }, - { - "name": "处理弹窗 close_popups 自定义配置 以及 ui_ocr exists 断言", - "ios": { - "os_type": "ios", - "actions": [ - { - "method": "close_popups", - "options": { - "max_retry_times": 3, - "interval": 2 - } - } - ] - }, - "validate": [ - { - "check": "ui_ocr", - "assert": "exists", - "expect": "推荐", - "msg": "进入抖音失败" - } - ] - }, - { - "name": "返回主界面,并打开本地时间戳", - "ios": { - "os_type": "ios", - "actions": [ - { - "method": "home" - }, - { - "method": "app_terminate", - "params": "$bundle_id" - }, - { - "method": "sleep", - "params": 3 - }, - { - "method": "swipe_to_tap_app", - "params": "local", - "options": { - "max_retry_times": 5 - } - }, - { - "method": "sleep", - "params": 10 - } - ] - } - }, - { - "name": "screeshot 以及 sleep_random", - "loops": 3, - "ios": { - "os_type": "ios", - "actions": [ - { - "method": "screenshot", - "options": {} - }, - { - "method": "sleep_random", - "params": [ - 1, - 3 - ] - } - ] - } - } - ] -} diff --git a/examples/uitest/sph_search.json b/examples/uitest/sph_search.json new file mode 100644 index 00000000..e68a2ee5 --- /dev/null +++ b/examples/uitest/sph_search.json @@ -0,0 +1,206 @@ +{ + "config": { + "name": "视频号搜索", + "ai_options": { + "llm_service": "doubao-1.5-ui-tars-250328" + } + }, + "teststeps": [ + { + "name": "启动视频号 app", + "android": { + "os_type": "android", + "actions": [ + { + "method": "app_launch", + "params": "com.tencent.mm" + }, + { + "method": "sleep", + "params": 5 + } + ] + }, + "validate": [ + { + "check": "ui_foreground_app", + "assert": "equal", + "expect": "com.tencent.mm", + "msg": "app [com.tencent.mm] should be in foreground" + } + ] + }, + { + "name": "进入视频号搜索页面", + "android": { + "os_type": "android", + "actions": [ + { + "method": "start_to_goal", + "params": "进入「发现」页,点击进入「视频号」页面,点击搜索框", + "options": {} + } + ] + }, + "validate": [ + { + "check": "ui_ai", + "assert": "ai_assert", + "expect": "当前页面包含搜索框和搜索按钮", + "msg": "assert ai prompt [当前页面包含搜索框和搜索按钮] failed" + } + ] + }, + { + "name": "搜索短剧「$dramaName」", + "parameters": { + "dramaName": [ + "督军,你家小福包有祖传乌鸦嘴", + "换亲后我顺便换了江山,很合理吧", + "穿过荆棘拥抱你", + "认亲后,误入帮派成团宠", + "欲念疯长", + "花轿临门她拒嫁,只盼故人归", + "太监武帝,功法自动大圆满", + "容先生,你的爱意藏不住了", + "回家给娘亲改命,心声咋还泄露了", + "相亲遇甜妹,偷娶她闺蜜" + ] + }, + "android": { + "os_type": "android", + "actions": [ + { + "method": "start_to_goal", + "params": "输入「$dramaName」,点击搜索", + "options": {} + }, + { + "method": "sleep", + "params": 1 + }, + { + "method": "swipe_direction", + "params": "up", + "options": {} + }, + { + "method": "sleep_random", + "params": [ + 1, + 2 + ] + }, + { + "method": "swipe_direction", + "params": "up", + "options": {} + }, + { + "method": "sleep_random", + "params": [ + 1, + 2 + ] + }, + { + "method": "swipe_direction", + "params": "up", + "options": {} + }, + { + "method": "sleep_random", + "params": [ + 1, + 2 + ] + }, + { + "method": "swipe_direction", + "params": "up", + "options": {} + }, + { + "method": "sleep_random", + "params": [ + 1, + 2 + ] + }, + { + "method": "swipe_direction", + "params": "up", + "options": {} + }, + { + "method": "sleep_random", + "params": [ + 1, + 2 + ] + }, + { + "method": "swipe_direction", + "params": "up", + "options": {} + }, + { + "method": "sleep_random", + "params": [ + 1, + 2 + ] + }, + { + "method": "swipe_direction", + "params": "up", + "options": {} + }, + { + "method": "sleep_random", + "params": [ + 1, + 2 + ] + }, + { + "method": "swipe_direction", + "params": "up", + "options": {} + }, + { + "method": "sleep_random", + "params": [ + 1, + 2 + ] + }, + { + "method": "swipe_direction", + "params": "up", + "options": {} + }, + { + "method": "sleep_random", + "params": [ + 1, + 2 + ] + }, + { + "method": "swipe_direction", + "params": "up", + "options": {} + }, + { + "method": "sleep_random", + "params": [ + 1, + 2 + ] + } + ] + } + } + ] +} diff --git a/examples/uitest/sph_search_test.go b/examples/uitest/sph_search_test.go new file mode 100644 index 00000000..8f209276 --- /dev/null +++ b/examples/uitest/sph_search_test.go @@ -0,0 +1,64 @@ +package uitest + +import ( + "testing" + + "github.com/stretchr/testify/require" + + hrp "github.com/httprunner/httprunner/v5" + "github.com/httprunner/httprunner/v5/uixt/option" +) + +func TestSPHSearchPage(t *testing.T) { + testCase := &hrp.TestCase{ + Config: hrp.NewConfig("视频号搜索"). + SetLLMService(option.DOUBAO_1_5_UI_TARS_250328), // Configure LLM service for AI operations + TestSteps: []hrp.IStep{ + hrp.NewStep("启动视频号 app"). + Android(). + AppLaunch("com.tencent.mm"). + Sleep(5). + Validate(). + AssertAppInForeground("com.tencent.mm"), + hrp.NewStep("进入视频号搜索页面"). + Android(). + StartToGoal("进入「发现」页,点击进入「视频号」页面,点击搜索框"). + Validate(). + AssertAI("当前页面包含搜索框和搜索按钮"), + hrp.NewStep("搜索短剧「$dramaName」"). + WithParameters(map[string]interface{}{ + "dramaName": []string{ + "督军,你家小福包有祖传乌鸦嘴", + "换亲后我顺便换了江山,很合理吧", + "穿过荆棘拥抱你", + "认亲后,误入帮派成团宠", + "欲念疯长", + "花轿临门她拒嫁,只盼故人归", + "太监武帝,功法自动大圆满", + "容先生,你的爱意藏不住了", + "回家给娘亲改命,心声咋还泄露了", + "相亲遇甜妹,偷娶她闺蜜", + }, + }). + Android(). + StartToGoal("输入「$dramaName」,点击搜索"). + Sleep(1). + SwipeUp().SleepRandom(1, 2). + SwipeUp().SleepRandom(1, 2). + SwipeUp().SleepRandom(1, 2). + SwipeUp().SleepRandom(1, 2). + SwipeUp().SleepRandom(1, 2). + SwipeUp().SleepRandom(1, 2). + SwipeUp().SleepRandom(1, 2). + SwipeUp().SleepRandom(1, 2). + SwipeUp().SleepRandom(1, 2). + SwipeUp().SleepRandom(1, 2), + }, + } + + err := testCase.Dump2JSON("sph_search.json") + require.Nil(t, err) + + // err := hrp.NewRunner(t).Run(testCase) + // assert.Nil(t, err) +} From 08849850f92fb643d90c2e46b108d06bd5c45ba4 Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Wed, 6 Aug 2025 11:42:22 +0800 Subject: [PATCH 5/5] feat: add gadb pull folder/file --- .github/workflows/smoketest.yml | 8 ++++++ .github/workflows/unittest.yml | 8 +++++- .gitignore | 4 +++ internal/version/VERSION | 2 +- pkg/gadb/device.go | 49 +++++++++++++++++++++++++++++++++ pkg/gadb/device_test.go | 11 ++++++++ 6 files changed, 80 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoketest.yml b/.github/workflows/smoketest.yml index 83df9a2b..c0a79a20 100644 --- a/.github/workflows/smoketest.yml +++ b/.github/workflows/smoketest.yml @@ -24,8 +24,16 @@ jobs: uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} + - name: Install Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' - name: Checkout code uses: actions/checkout@v2 + - name: Install Python dependencies + run: | + python3 -m pip install --upgrade pip + python3 -m pip install funppy httprunner - name: Build hrp binary run: make build - name: Run smoketest - run with parameters diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index 0a854858..55ce8434 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -23,8 +23,14 @@ jobs: uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} + - name: Install Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' - name: Install Python plugin dependencies - run: python3 -m pip install funppy + run: | + python3 -m pip install --upgrade pip + python3 -m pip install funppy httprunner - name: Checkout code uses: actions/checkout@v2 - name: Run coverage diff --git a/.gitignore b/.gitignore index b2daa1e9..8a25c14f 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,7 @@ dist *.egg-info .python-version .pytest_cache + +# generated go module files in templates +internal/scaffold/templates/plugin/go.mod +internal/scaffold/templates/plugin/go.sum diff --git a/internal/version/VERSION b/internal/version/VERSION index 0abffd80..77429189 100644 --- a/internal/version/VERSION +++ b/internal/version/VERSION @@ -1 +1 @@ -v5.0.0-250803 +v5.0.0-250806 diff --git a/pkg/gadb/device.go b/pkg/gadb/device.go index e6cd5aed..c6857fee 100644 --- a/pkg/gadb/device.go +++ b/pkg/gadb/device.go @@ -610,6 +610,55 @@ func (d *Device) Pull(remotePath string, dest io.Writer) (err error) { return } +func (d *Device) PullFolder(remotePath string, localPath string) (err error) { + // Check if remote path exists and is a directory + fileInfos, err := d.List(remotePath) + if err != nil { + return fmt.Errorf("failed to list remote directory: %w", err) + } + + // Create local directory if it doesn't exist + if err = os.MkdirAll(localPath, 0o755); err != nil { + return fmt.Errorf("failed to create local directory: %w", err) + } + + // Pull each file/directory recursively + for _, fileInfo := range fileInfos { + remoteItemPath := remotePath + "/" + fileInfo.Name + localItemPath := localPath + "/" + fileInfo.Name + + if fileInfo.IsDir() { + // Recursively pull subdirectory + if err = d.PullFolder(remoteItemPath, localItemPath); err != nil { + return fmt.Errorf("failed to pull subdirectory %s: %w", remoteItemPath, err) + } + } else { + // Pull file + if err = d.PullFile(remoteItemPath, localItemPath); err != nil { + return fmt.Errorf("failed to pull file %s: %w", remoteItemPath, err) + } + } + } + + return nil +} + +func (d *Device) PullFile(remotePath string, localPath string) (err error) { + // Create local file + localFile, err := os.Create(localPath) + if err != nil { + return fmt.Errorf("failed to create local file: %w", err) + } + defer localFile.Close() + + // Use existing Pull method to pull file content + if err = d.Pull(remotePath, localFile); err != nil { + return fmt.Errorf("failed to pull file content: %w", err) + } + + return nil +} + func (d *Device) installViaABBExec(apk io.ReadSeeker, args ...string) (raw []byte, err error) { var ( tp transport diff --git a/pkg/gadb/device_test.go b/pkg/gadb/device_test.go index 0a55e56b..8d8077a2 100644 --- a/pkg/gadb/device_test.go +++ b/pkg/gadb/device_test.go @@ -296,6 +296,17 @@ func TestDevice_Pull(t *testing.T) { } } +func TestDevice_PullFolder(t *testing.T) { + setupDevices(t) + + for _, dev := range devices { + err := dev.PullFolder("/storage/emulated/0/Download/", "/tmp/test/") + if err != nil { + t.Fatal(err) + } + } +} + func TestDevice_ScreenRecord(t *testing.T) { setupDevices(t)