Files
httprunner/examples/game/llk/main_test.go
lilong.129 fb0418fa95 feat: add LianLianKan (连连看) game bot implementation
- Add complete LianLianKan game bot with AI-powered interface analysis
- Implement static analysis solver with 0-2 turn connection algorithms
- Support cross-platform game automation (Android, iOS, HarmonyOS, Browser)
- Include comprehensive test suite with real game data
- Add command line tool and documentation
- Integrate with HttpRunner @/uixt module and Doubao AI models
2025-06-12 17:51:23 +08:00

140 lines
4.8 KiB
Go

package llk
import (
"context"
"os"
"testing"
"github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/uixt/ai"
"github.com/httprunner/httprunner/v5/uixt/option"
"github.com/httprunner/httprunner/v5/uixt/types"
"github.com/stretchr/testify/assert"
"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
}
// 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")
require.NoError(t, err)
return screenshot, size
}
// createAIQueryer creates a AI queryer with AI analysis capability
func createAIQueryer(t *testing.T) *ai.Querier {
ctx := context.Background()
modelConfig, err := ai.GetModelConfig(option.DOUBAO_SEED_1_6_250615)
require.NoError(t, err)
querier, err := ai.NewQuerier(ctx, modelConfig)
require.NoError(t, err)
return 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
querier := createAIQueryer(t)
screenshot, size := loadTestImage(t)
t.Logf("Loaded test image with size: %dx%d", size.Width, size.Height)
// Prepare query options for AI analysis
opts := &ai.QueryOptions{
Query: `Analyze this LianLianKan (连连看) game interface and provide CONCISE structured information:
1. Game type: "LianLianKan"
2. Grid dimensions (rows x columns) - CRITICAL: rows are horizontal lines, columns are vertical lines
3. Game elements with positions and types - LIMIT to essential info only
4. Bounding boxes - use approximate coordinates
REQUIREMENTS:
- Count ROWS as horizontal lines (top to bottom)
- Count COLUMNS as vertical lines (left to right)
- Position: row=0 is top, col=0 is left
- Keep response SHORT to avoid truncation
- Use simple element type names (max 10 chars)
- Omit detailed descriptions
Return JSON with: content, dimensions{rows,cols}, elements[{type,position{row,col},boundBox{x,y,width,height}}], statistics{totalElements,uniqueTypes}.`,
Screenshot: screenshot,
Size: size,
OutputSchema: GameElement{},
}
// Query AI model and convert result
result, err := querier.Query(context.Background(), opts)
require.NoError(t, err, "Failed to query AI model")
// Convert result using enhanced compatibility logic
gameElement, err := convertToGameElement(result)
require.NoError(t, err, "Failed to convert query result to GameElement")
require.NotNil(t, gameElement, "GameElement should not be nil")
// Log analysis results
t.Logf("\n=== Game Interface Analysis Results ===")
t.Logf("Dimensions: %dx%d", gameElement.Dimensions.Rows, gameElement.Dimensions.Cols)
// Basic validations
assert.NotEmpty(t, gameElement.Content, "Content should not be empty")
assert.Greater(t, gameElement.Dimensions.Rows, 0, "Rows should be greater than 0")
assert.Greater(t, gameElement.Dimensions.Cols, 0, "Cols should be greater than 0")
assert.Greater(t, len(gameElement.Elements), 0, "Should have detected elements")
// Test solver integration
t.Logf("\n=== Solver Integration Test ===")
solver := NewLLKSolver(gameElement)
require.NotNil(t, solver, "Solver should be created successfully")
pairs := solver.FindAllPairs()
t.Logf("Solver found %d valid matching pairs", len(pairs))
// Log sample element details
t.Logf("\n=== Sample Elements ===")
for i, element := range gameElement.Elements {
if i < 5 { // Show first 5 elements
t.Logf("Element %d: %s at grid(%d,%d)",
i+1, element.Type,
element.Position.Row, element.Position.Col)
}
}
if len(gameElement.Elements) > 5 {
t.Logf("... and %d more elements", len(gameElement.Elements)-5)
}
t.Logf("\n=== Analysis Test Completed Successfully ===")
})
}
// TestLLKGameBot_RealDevice test with real Android device
func TestLLKGameBot_RealDevice(t *testing.T) {
t.Run("CreateAndAnalyze", func(t *testing.T) {
// Create game bot with real device
bot, err := NewLLKGameBot("android", "")
require.NoError(t, err, "Failed to create LLKGameBot")
defer bot.Close()
// err = bot.EnterGame(context.Background())
// require.NoError(t, err, "Failed to enter game")
err = bot.Play()
require.NoError(t, err, "Failed to play game")
})
}