mirror of
https://github.com/httprunner/httprunner.git
synced 2026-09-05 23:51:25 +08:00
feat: enhance report generation by integrating session data and improving AI query display
This commit is contained in:
@@ -1 +1 @@
|
|||||||
v5.0.0-beta-2506192157
|
v5.0.0-beta-2506201738
|
||||||
|
|||||||
@@ -369,10 +369,6 @@ func (g *HTMLReportGenerator) calculateTotalActions() int {
|
|||||||
func (g *HTMLReportGenerator) calculateTotalSubActions() int {
|
func (g *HTMLReportGenerator) calculateTotalSubActions() int {
|
||||||
return g.iterateTestData(func(action *ActionResult) int {
|
return g.iterateTestData(func(action *ActionResult) int {
|
||||||
total := 0
|
total := 0
|
||||||
// Count sub-actions from regular actions
|
|
||||||
if action.SubActions != nil {
|
|
||||||
total += len(action.SubActions)
|
|
||||||
}
|
|
||||||
// Count sub-actions from planning results
|
// Count sub-actions from planning results
|
||||||
if action.Plannings != nil {
|
if action.Plannings != nil {
|
||||||
for _, planning := range action.Plannings {
|
for _, planning := range action.Plannings {
|
||||||
@@ -380,6 +376,8 @@ func (g *HTMLReportGenerator) calculateTotalSubActions() int {
|
|||||||
total += len(planning.SubActions)
|
total += len(planning.SubActions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
total += 1
|
||||||
}
|
}
|
||||||
return total
|
return total
|
||||||
})
|
})
|
||||||
@@ -1208,6 +1206,8 @@ const htmlTemplate = `<!DOCTYPE html>
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.request-item-compact {
|
.request-item-compact {
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border: 1px solid #e9ecef;
|
border: 1px solid #e9ecef;
|
||||||
@@ -2085,6 +2085,28 @@ const htmlTemplate = `<!DOCTYPE html>
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.action-session-data {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 15px;
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-requests {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-screenshots {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-action-item {
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -2342,97 +2364,153 @@ const htmlTemplate = `<!DOCTYPE html>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{/* SubActions are now displayed in the right panel, so we don't show them here */}}
|
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{/* Handle special case: ai_query needs enhanced display even when not in planning */}}
|
{{/* Handle special case: ai_query needs enhanced display even when not in planning */}}
|
||||||
{{if $action.SubActions}}
|
{{if eq $action.Method "ai_query"}}
|
||||||
{{range $subAction := $action.SubActions}}
|
<div class="sub-action-item">
|
||||||
{{if eq $subAction.ActionName "ai_query"}}
|
<!-- Enhanced AI Query Display -->
|
||||||
<div class="sub-action-item">
|
<div class="validator-ai-content">
|
||||||
<!-- Enhanced AI Query Display -->
|
<!-- Extract AI query details from step logs -->
|
||||||
<div class="validator-ai-content">
|
{{$stepLogs := getStepLogs $step}}
|
||||||
<!-- Extract AI query details from step logs -->
|
{{$queryThought := ""}}
|
||||||
{{$stepLogs := getStepLogs $step}}
|
{{$queryModel := ""}}
|
||||||
{{$queryThought := ""}}
|
{{$queryUsage := ""}}
|
||||||
{{$queryModel := ""}}
|
{{$queryScreenshot := ""}}
|
||||||
{{$queryUsage := ""}}
|
{{$queryResult := ""}}
|
||||||
{{$queryScreenshot := ""}}
|
{{range $logEntry := $stepLogs}}
|
||||||
{{$queryResult := ""}}
|
{{if and (eq $logEntry.Message "log response message") (index $logEntry.Fields "content")}}
|
||||||
{{range $logEntry := $stepLogs}}
|
{{$content := index $logEntry.Fields "content"}}
|
||||||
{{if and (eq $logEntry.Message "log response message") (index $logEntry.Fields "content")}}
|
{{if $content}}
|
||||||
{{$content := index $logEntry.Fields "content"}}
|
{{$queryResult = $content}}
|
||||||
{{if $content}}
|
|
||||||
{{$queryResult = $content}}
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
{{if and (eq $logEntry.Message "call model service for query") (index $logEntry.Fields "model")}}
|
|
||||||
{{$queryModel = index $logEntry.Fields "model"}}
|
|
||||||
{{end}}
|
|
||||||
{{if and (eq $logEntry.Message "usage statistics") (index $logEntry.Fields "input_tokens")}}
|
|
||||||
{{$inputTokens := index $logEntry.Fields "input_tokens"}}
|
|
||||||
{{$outputTokens := index $logEntry.Fields "output_tokens"}}
|
|
||||||
{{$totalTokens := index $logEntry.Fields "total_tokens"}}
|
|
||||||
{{$queryUsage = printf "📊 Tokens: %v in / %v out / %v total" $inputTokens $outputTokens $totalTokens}}
|
|
||||||
{{end}}
|
|
||||||
{{if and (eq $logEntry.Message "log screenshot") (index $logEntry.Fields "imagePath")}}
|
|
||||||
{{$queryScreenshot = index $logEntry.Fields "imagePath"}}
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
{{if and (eq $logEntry.Message "call model service for query") (index $logEntry.Fields "model")}}
|
||||||
|
{{$queryModel = index $logEntry.Fields "model"}}
|
||||||
|
{{end}}
|
||||||
|
{{if and (eq $logEntry.Message "usage statistics") (index $logEntry.Fields "input_tokens")}}
|
||||||
|
{{$inputTokens := index $logEntry.Fields "input_tokens"}}
|
||||||
|
{{$outputTokens := index $logEntry.Fields "output_tokens"}}
|
||||||
|
{{$totalTokens := index $logEntry.Fields "total_tokens"}}
|
||||||
|
{{$queryUsage = printf "📊 Tokens: %v in / %v out / %v total" $inputTokens $outputTokens $totalTokens}}
|
||||||
|
{{end}}
|
||||||
|
{{if and (eq $logEntry.Message "log screenshot") (index $logEntry.Fields "imagePath")}}
|
||||||
|
{{$queryScreenshot = index $logEntry.Fields "imagePath"}}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<!-- Display AI Query Result at the top -->
|
<!-- Display AI Query Result at the top -->
|
||||||
{{if $queryResult}}
|
{{if $queryResult}}
|
||||||
<div class="thought">{{$queryResult}}</div>
|
<div class="thought">{{$queryResult}}</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<!-- AI Query Layout - similar to validator layout -->
|
<!-- AI Query Layout - similar to validator layout -->
|
||||||
<div class="validator-ai-layout">
|
<div class="validator-ai-layout">
|
||||||
<!-- Left column: Screenshot -->
|
<!-- Left column: Screenshot -->
|
||||||
{{if $queryScreenshot}}
|
{{if $queryScreenshot}}
|
||||||
<div class="validator-column-screenshot">
|
<div class="validator-column-screenshot">
|
||||||
<div class="validator-step-compact">
|
<div class="validator-step-compact">
|
||||||
<div class="step-header-compact">
|
<div class="step-header-compact">
|
||||||
<span class="step-name">📸 Query Screenshot</span>
|
<span class="step-name">📸 Query Screenshot</span>
|
||||||
</div>
|
|
||||||
<div class="screenshot-display">
|
|
||||||
{{$base64Image := encodeImageBase64 $queryScreenshot}}
|
|
||||||
{{if $base64Image}}
|
|
||||||
<div class="screenshot-item-compact">
|
|
||||||
<div class="screenshot-image">
|
|
||||||
<img src="data:image/jpeg;base64,{{$base64Image}}" alt="Query Screenshot" onclick="openImageModal(this.src)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
<div class="screenshot-display">
|
||||||
|
{{$base64Image := encodeImageBase64 $queryScreenshot}}
|
||||||
<!-- Right column: AI Query -->
|
{{if $base64Image}}
|
||||||
<div class="validator-column-analysis">
|
<div class="screenshot-item-compact">
|
||||||
<div class="validator-step-compact">
|
<div class="screenshot-image">
|
||||||
<div class="step-header-compact">
|
<img src="data:image/jpeg;base64,{{$base64Image}}" alt="Query Screenshot" onclick="openImageModal(this.src)" />
|
||||||
<span class="step-name">🤖 AI Query</span>
|
|
||||||
</div>
|
|
||||||
<div class="validator-ai-details">
|
|
||||||
{{if $queryModel}}
|
|
||||||
<div class="model-info">🤖 Model: {{$queryModel}}</div>
|
|
||||||
{{end}}
|
|
||||||
{{if $queryUsage}}
|
|
||||||
<div class="usage-info">{{$queryUsage}}</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<!-- Right column: AI Query -->
|
||||||
|
<div class="validator-column-analysis">
|
||||||
|
<div class="validator-step-compact">
|
||||||
|
<div class="step-header-compact">
|
||||||
|
<span class="step-name">🤖 AI Query</span>
|
||||||
|
</div>
|
||||||
|
<div class="validator-ai-details">
|
||||||
|
{{if $queryModel}}
|
||||||
|
<div class="model-info">🤖 Model: {{$queryModel}}</div>
|
||||||
|
{{end}}
|
||||||
|
{{if $queryUsage}}
|
||||||
|
<div class="usage-info">{{$queryUsage}}</div>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{/* Handle SessionData: display requests and screen results for non-planning actions */}}
|
||||||
|
{{if not $action.Plannings}}
|
||||||
|
{{if or $action.Requests $action.ScreenResults}}
|
||||||
|
<div class="action-session-data">
|
||||||
|
<!-- Display requests if present -->
|
||||||
|
{{if $action.Requests}}
|
||||||
|
<div class="session-requests">
|
||||||
|
<button class="requests-toggle-compact" onclick="toggleRequestsCompact(this)">
|
||||||
|
📡 {{len $action.Requests}} request(s)
|
||||||
|
</button>
|
||||||
|
<div class="requests-content-compact">
|
||||||
|
{{range $request := $action.Requests}}
|
||||||
|
<div class="request-item-compact">
|
||||||
|
<div class="request-header-compact">
|
||||||
|
<span class="method">{{$request.RequestMethod}}</span>
|
||||||
|
<span class="url-compact">{{$request.RequestUrl}}</span>
|
||||||
|
<span class="status {{if $request.Success}}success{{else}}failure{{end}}">{{$request.ResponseStatus}}</span>
|
||||||
|
<span class="duration">{{formatDuration $request.ResponseDuration}}</span>
|
||||||
|
</div>
|
||||||
|
{{if $request.RequestBody}}
|
||||||
|
<div class="request-body-compact">Request: {{$request.RequestBody}}</div>
|
||||||
|
{{end}}
|
||||||
|
{{if $request.ResponseBody}}
|
||||||
|
<div class="response-body-compact">Response: {{$request.ResponseBody}}</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
<!-- Display screen results if present -->
|
||||||
|
{{if $action.ScreenResults}}
|
||||||
|
<div class="session-screenshots">
|
||||||
|
<h5 style="margin: 10px 0; color: #495057;">📸 Screen Results ({{len $action.ScreenResults}})</h5>
|
||||||
|
<div class="screenshots-horizontal">
|
||||||
|
{{range $screenshot := $action.ScreenResults}}
|
||||||
|
{{if $screenshot.ImagePath}}
|
||||||
|
{{$base64Image := encodeImageBase64 $screenshot.ImagePath}}
|
||||||
|
{{if $base64Image}}
|
||||||
|
<div class="screenshot-item small">
|
||||||
|
<div class="screenshot-info">
|
||||||
|
<span class="filename">{{base $screenshot.ImagePath}}</span>
|
||||||
|
{{if $screenshot.Resolution}}
|
||||||
|
<span class="resolution">{{$screenshot.Resolution.Width}}x{{$screenshot.Resolution.Height}}</span>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="screenshot-image">
|
||||||
|
<img src="data:image/jpeg;base64,{{$base64Image}}" alt="Screenshot" onclick="openImageModal(this.src)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{/* Other SubActions (non-ai_query) are displayed in the Planning section's right panel to avoid duplication */}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -58,11 +58,11 @@ type TStep struct {
|
|||||||
// one step contains one or multiple actions
|
// one step contains one or multiple actions
|
||||||
type ActionResult struct {
|
type ActionResult struct {
|
||||||
option.MobileAction `json:",inline"`
|
option.MobileAction `json:",inline"`
|
||||||
StartTime int64 `json:"start_time"` // action start time in millisecond(ms)
|
StartTime int64 `json:"start_time"` // action start time in millisecond(ms)
|
||||||
Elapsed int64 `json:"elapsed_ms"` // action elapsed time(ms)
|
Elapsed int64 `json:"elapsed_ms"` // action elapsed time(ms)
|
||||||
Error error `json:"error"` // action execution result
|
Error error `json:"error,omitempty"` // action execution result
|
||||||
Plannings []*uixt.PlanningExecutionResult `json:"plannings,omitempty"` // store planning results for start_to_goal actions
|
Plannings []*uixt.PlanningExecutionResult `json:"plannings,omitempty"` // store planning results for start_to_goal actions, which contains multiple sub-actions
|
||||||
SubActions []*uixt.SubActionResult `json:"sub_actions,omitempty"` // store sub-actions for other actions
|
uixt.SessionData // store session data for other actions besides start_to_goal
|
||||||
}
|
}
|
||||||
|
|
||||||
// one testcase contains one or multiple steps
|
// one testcase contains one or multiple steps
|
||||||
|
|||||||
+9
-6
@@ -783,13 +783,14 @@ func runStepMobileUI(s *SessionRunner, step IStep) (stepResult *StepResult, err
|
|||||||
},
|
},
|
||||||
StartTime: startTime.UnixMilli(),
|
StartTime: startTime.UnixMilli(),
|
||||||
}
|
}
|
||||||
subActionResults, err1 := uiDriver.ExecuteAction(
|
sessionData, err1 := uiDriver.ExecuteAction(
|
||||||
context.Background(), actionResult.MobileAction)
|
context.Background(), actionResult.MobileAction)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
|
actionResult.Error = err1
|
||||||
log.Warn().Err(err1).Msg("get foreground app failed, ignore")
|
log.Warn().Err(err1).Msg("get foreground app failed, ignore")
|
||||||
}
|
}
|
||||||
actionResult.Elapsed = time.Since(startTime).Milliseconds()
|
actionResult.Elapsed = time.Since(startTime).Milliseconds()
|
||||||
actionResult.SubActions = subActionResults
|
actionResult.SessionData = sessionData
|
||||||
stepResult.Actions = append(stepResult.Actions, actionResult)
|
stepResult.Actions = append(stepResult.Actions, actionResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -827,13 +828,14 @@ func runStepMobileUI(s *SessionRunner, step IStep) (stepResult *StepResult, err
|
|||||||
},
|
},
|
||||||
StartTime: startTime.UnixMilli(),
|
StartTime: startTime.UnixMilli(),
|
||||||
}
|
}
|
||||||
subActionResults, err2 := uiDriver.ExecuteAction(
|
sessionData, err2 := uiDriver.ExecuteAction(
|
||||||
context.Background(), actionResult.MobileAction)
|
context.Background(), actionResult.MobileAction)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
|
actionResult.Error = err2
|
||||||
log.Warn().Err(err2).Str("step", step.Name()).Msg("auto handle popup failed")
|
log.Warn().Err(err2).Str("step", step.Name()).Msg("auto handle popup failed")
|
||||||
}
|
}
|
||||||
actionResult.Elapsed = time.Since(startTime).Milliseconds()
|
actionResult.Elapsed = time.Since(startTime).Milliseconds()
|
||||||
actionResult.SubActions = subActionResults
|
actionResult.SessionData = sessionData
|
||||||
stepResult.Actions = append(stepResult.Actions, actionResult)
|
stepResult.Actions = append(stepResult.Actions, actionResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -950,9 +952,10 @@ func runStepMobileUI(s *SessionRunner, step IStep) (stepResult *StepResult, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handle other actions
|
// handle other actions
|
||||||
subActionResults, err := uiDriver.ExecuteAction(ctx, action)
|
sessionData, err := uiDriver.ExecuteAction(ctx, action)
|
||||||
|
actionResult.Error = err
|
||||||
actionResult.Elapsed = time.Since(actionStartTime).Milliseconds()
|
actionResult.Elapsed = time.Since(actionStartTime).Milliseconds()
|
||||||
actionResult.SubActions = subActionResults
|
actionResult.SessionData = sessionData
|
||||||
stepResult.Actions = append(stepResult.Actions, actionResult)
|
stepResult.Actions = append(stepResult.Actions, actionResult)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !code.IsErrorPredefined(err) {
|
if !code.IsErrorPredefined(err) {
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ func (dExt *XTDriver) StartToGoal(ctx context.Context, prompt string, opts ...op
|
|||||||
planningResult.Elapsed = time.Since(planningStartTime).Milliseconds()
|
planningResult.Elapsed = time.Since(planningStartTime).Milliseconds()
|
||||||
allPlannings = append(allPlannings, planningResult)
|
allPlannings = append(allPlannings, planningResult)
|
||||||
|
|
||||||
if options.MaxRetryTimes > 0 && attempt >= options.MaxRetryTimes {
|
if options.MaxRetryTimes > 0 && attempt > options.MaxRetryTimes {
|
||||||
return allPlannings, errors.New("reached max retry times")
|
return allPlannings, errors.New("reached max retry times")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-21
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/uixt/ai"
|
"github.com/httprunner/httprunner/v5/uixt/ai"
|
||||||
"github.com/httprunner/httprunner/v5/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
@@ -113,34 +112,23 @@ func (c *MCPClient4XTDriver) GetToolByAction(actionName option.ActionName) Actio
|
|||||||
return c.Server.GetToolByAction(actionName)
|
return c.Server.GetToolByAction(actionName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dExt *XTDriver) ExecuteAction(ctx context.Context, action option.MobileAction) ([]*SubActionResult, error) {
|
func (dExt *XTDriver) ExecuteAction(ctx context.Context, action option.MobileAction) (SessionData, error) {
|
||||||
subActionStartTime := time.Now()
|
|
||||||
|
|
||||||
// Find the corresponding tool for this action method
|
// Find the corresponding tool for this action method
|
||||||
tool := dExt.client.Server.GetToolByAction(action.Method)
|
tool := dExt.client.Server.GetToolByAction(action.Method)
|
||||||
if tool == nil {
|
if tool == nil {
|
||||||
return nil, fmt.Errorf("no tool found for action method: %s", action.Method)
|
return SessionData{}, fmt.Errorf("no tool found for action method: %s", action.Method)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the tool's own conversion method
|
// Use the tool's own conversion method
|
||||||
req, err := tool.ConvertActionToCallToolRequest(action)
|
req, err := tool.ConvertActionToCallToolRequest(action)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to convert action to MCP tool call: %w", err)
|
return SessionData{}, fmt.Errorf("failed to convert action to MCP tool call: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
// Create sub-action result
|
|
||||||
subActionResult := &SubActionResult{
|
|
||||||
ActionName: string(action.Method),
|
|
||||||
Arguments: action.Params,
|
|
||||||
StartTime: subActionStartTime.UnixMilli(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute via MCP tool
|
// Execute via MCP tool
|
||||||
result, err := dExt.client.CallTool(ctx, req)
|
result, err := dExt.client.CallTool(ctx, req)
|
||||||
subActionResult.Elapsed = time.Since(subActionStartTime).Milliseconds()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
subActionResult.Error = err
|
return SessionData{}, fmt.Errorf("MCP tool call failed: %w", err)
|
||||||
return []*SubActionResult{subActionResult}, fmt.Errorf("MCP tool call failed: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the tool execution had business logic errors
|
// Check if the tool execution had business logic errors
|
||||||
@@ -152,16 +140,15 @@ func (dExt *XTDriver) ExecuteAction(ctx context.Context, action option.MobileAct
|
|||||||
errMsg = fmt.Sprintf("invoke tool %s failed", tool.Name())
|
errMsg = fmt.Sprintf("invoke tool %s failed", tool.Name())
|
||||||
}
|
}
|
||||||
err := errors.New(errMsg)
|
err := errors.New(errMsg)
|
||||||
subActionResult.Error = err
|
return SessionData{}, err
|
||||||
return []*SubActionResult{subActionResult}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For regular actions, collect session data and return single sub-action result
|
// For regular actions, collect session data and return it directly
|
||||||
subActionResult.SessionData = dExt.GetSession().GetData(true) // reset after getting data
|
sessionData := dExt.GetSession().GetData(true) // reset after getting data
|
||||||
|
|
||||||
log.Debug().Str("tool", string(tool.Name())).
|
log.Debug().Str("tool", string(tool.Name())).
|
||||||
Msg("executed action via MCP tool")
|
Msg("executed action via MCP tool")
|
||||||
return []*SubActionResult{subActionResult}, nil
|
return sessionData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDeviceWithDefault is a helper function to create a device with default options
|
// NewDeviceWithDefault is a helper function to create a device with default options
|
||||||
|
|||||||
Reference in New Issue
Block a user