From a5278343594707849247061038271e760a1b88aa Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Wed, 9 Jul 2025 23:03:31 +0800 Subject: [PATCH 01/14] fix: ignore step popup --- step.go | 1 + step_ui.go | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/step.go b/step.go index 7e5ec98b..636d497b 100644 --- a/step.go +++ b/step.go @@ -37,6 +37,7 @@ type StepConfig struct { Validators []interface{} `json:"validate,omitempty" yaml:"validate,omitempty"` StepExport []string `json:"export,omitempty" yaml:"export,omitempty"` Loops int `json:"loops,omitempty" yaml:"loops,omitempty"` + IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"` // ignore popup for this step, keep for compatibility AutoPopupHandler bool `json:"auto_popup_handler,omitempty" yaml:"auto_popup_handler,omitempty"` // enable auto popup handler for this step } diff --git a/step_ui.go b/step_ui.go index 0dba2e80..7c8c9c0d 100644 --- a/step_ui.go +++ b/step_ui.go @@ -715,6 +715,7 @@ func runStepMobileUI(s *SessionRunner, step IStep) (stepResult *StepResult, err var stepVariables map[string]interface{} var stepValidators []interface{} var stepAutoPopupHandler bool + var stepIgnorePopup bool var mobileStep *MobileUI switch stepMobile := step.(type) { @@ -722,11 +723,13 @@ func runStepMobileUI(s *SessionRunner, step IStep) (stepResult *StepResult, err mobileStep = stepMobile.obj() stepVariables = stepMobile.Variables stepAutoPopupHandler = stepMobile.AutoPopupHandler + stepIgnorePopup = stepMobile.IgnorePopup case *StepMobileUIValidation: mobileStep = stepMobile.obj() stepVariables = stepMobile.Variables stepValidators = stepMobile.Validators stepAutoPopupHandler = stepMobile.StepMobile.AutoPopupHandler + stepIgnorePopup = stepMobile.StepMobile.IgnorePopup default: return stepResult, errors.New("invalid mobile UI step type") } @@ -797,7 +800,10 @@ func runStepMobileUI(s *SessionRunner, step IStep) (stepResult *StepResult, err // automatic handling of pop-up windows on each step finished // priority: testcase config > step config, default to disabled shouldHandlePopup := false - if config != nil && config.AutoPopupHandler { + if stepIgnorePopup { + // step level config, keep for compatibility + shouldHandlePopup = false + } else if config != nil && config.AutoPopupHandler { // testcase level config has higher priority shouldHandlePopup = true } else if stepAutoPopupHandler { From 0e80d7726bfe6c1b4d8dcbc07518d46979b8c223 Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Wed, 9 Jul 2025 23:15:52 +0800 Subject: [PATCH 02/14] fix: ignore testcase popup --- config.go | 1 + step_ui.go | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/config.go b/config.go index 9e20bb9a..f96a7fd9 100644 --- a/config.go +++ b/config.go @@ -46,6 +46,7 @@ type TConfig struct { PluginSetting *PluginConfig `json:"plugin,omitempty" yaml:"plugin,omitempty"` // plugin config MCPConfigPath string `json:"mcp_config_path,omitempty" yaml:"mcp_config_path,omitempty"` AntiRisk bool `json:"anti_risk,omitempty" yaml:"anti_risk,omitempty"` // global anti-risk switch + IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"` // ignore popup for all steps, keep for compatibility AutoPopupHandler bool `json:"auto_popup_handler,omitempty" yaml:"auto_popup_handler,omitempty"` // enable auto popup handler AIOptions *option.AIServiceOptions `json:"ai_options,omitempty" yaml:"ai_options,omitempty"` } diff --git a/step_ui.go b/step_ui.go index 7c8c9c0d..903bc53f 100644 --- a/step_ui.go +++ b/step_ui.go @@ -797,10 +797,13 @@ func runStepMobileUI(s *SessionRunner, step IStep) (stepResult *StepResult, err if s.caseRunner != nil && s.caseRunner.Config != nil { config = s.caseRunner.Config.Get() } - // automatic handling of pop-up windows on each step finished - // priority: testcase config > step config, default to disabled + // automatic handling of pop-up windows on each step finished, default to disabled + // priority: testcase config ignore_popup > step ignore_popup > config auto_popup_handler > step auto_popup_handler shouldHandlePopup := false - if stepIgnorePopup { + + if config != nil && config.IgnorePopup { + shouldHandlePopup = false + } else if stepIgnorePopup { // step level config, keep for compatibility shouldHandlePopup = false } else if config != nil && config.AutoPopupHandler { From a353d697e8109e84fed0f387e7ec6ce1a9ccf7a0 Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Thu, 10 Jul 2025 13:18:26 +0800 Subject: [PATCH 03/14] change: remove config ignore_popup --- config.go | 1 - internal/version/VERSION | 2 +- step_ui.go | 6 ++---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/config.go b/config.go index f96a7fd9..9e20bb9a 100644 --- a/config.go +++ b/config.go @@ -46,7 +46,6 @@ type TConfig struct { PluginSetting *PluginConfig `json:"plugin,omitempty" yaml:"plugin,omitempty"` // plugin config MCPConfigPath string `json:"mcp_config_path,omitempty" yaml:"mcp_config_path,omitempty"` AntiRisk bool `json:"anti_risk,omitempty" yaml:"anti_risk,omitempty"` // global anti-risk switch - IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"` // ignore popup for all steps, keep for compatibility AutoPopupHandler bool `json:"auto_popup_handler,omitempty" yaml:"auto_popup_handler,omitempty"` // enable auto popup handler AIOptions *option.AIServiceOptions `json:"ai_options,omitempty" yaml:"ai_options,omitempty"` } diff --git a/internal/version/VERSION b/internal/version/VERSION index a778e596..552c255e 100644 --- a/internal/version/VERSION +++ b/internal/version/VERSION @@ -1 +1 @@ -v5.0.0-250709 +v5.0.0-250710 diff --git a/step_ui.go b/step_ui.go index 903bc53f..e103c255 100644 --- a/step_ui.go +++ b/step_ui.go @@ -798,12 +798,10 @@ func runStepMobileUI(s *SessionRunner, step IStep) (stepResult *StepResult, err config = s.caseRunner.Config.Get() } // automatic handling of pop-up windows on each step finished, default to disabled - // priority: testcase config ignore_popup > step ignore_popup > config auto_popup_handler > step auto_popup_handler + // priority: step ignore_popup > config auto_popup_handler > step auto_popup_handler shouldHandlePopup := false - if config != nil && config.IgnorePopup { - shouldHandlePopup = false - } else if stepIgnorePopup { + if stepIgnorePopup { // step level config, keep for compatibility shouldHandlePopup = false } else if config != nil && config.AutoPopupHandler { From b8a412c3d94772bfd65e24406383efa3487aaf47 Mon Sep 17 00:00:00 2001 From: "huangbin.beal" Date: Thu, 10 Jul 2025 14:02:52 +0800 Subject: [PATCH 04/14] fix: web bug --- uixt/browser_driver.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/uixt/browser_driver.go b/uixt/browser_driver.go index 118fc83f..ca5da376 100644 --- a/uixt/browser_driver.go +++ b/uixt/browser_driver.go @@ -706,3 +706,27 @@ func (wd *BrowserDriver) TapXY(x, y float64, opts ...option.ActionOption) error func (wd *BrowserDriver) TapAbsXY(x, y float64, opts ...option.ActionOption) error { return wd.TapFloat(x, y, opts...) } + +func (wd *BrowserDriver) SetHeader(headers string) (err error) { + data := map[string]interface{}{ + "headers": headers, + } + _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "set_headers")) + return err +} + +func (wd *BrowserDriver) Keyboard(key string) (err error) { + data := map[string]interface{}{ + "press": key, + } + _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/keyboard")) + return err +} + +func (wd *BrowserDriver) PageAction(action string) (err error) { + data := map[string]interface{}{ + "action": action, + } + _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/page/action")) + return err +} From 7ebd42b8339d06235535366767c5e57be968584e Mon Sep 17 00:00:00 2001 From: "huangbin.beal" Date: Thu, 10 Jul 2025 16:38:10 +0800 Subject: [PATCH 05/14] fix: web merge --- uixt/browser_driver.go | 127 ++++++++++++++++++++++++----------------- 1 file changed, 76 insertions(+), 51 deletions(-) diff --git a/uixt/browser_driver.go b/uixt/browser_driver.go index ca5da376..c13bbf2b 100644 --- a/uixt/browser_driver.go +++ b/uixt/browser_driver.go @@ -138,7 +138,7 @@ func (wd *BrowserDriver) Drag(fromX, fromY, toX, toY float64, options ...option. data["duration"] = 0.5 } - _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/drag")) + _, err = wd.CustomePost(data, wd.concatURL(wd.Session.ID, "ui/drag")) return } @@ -146,7 +146,7 @@ func (wd *BrowserDriver) AppLaunch(packageName string) (err error) { data := map[string]interface{}{ "url": packageName, } - _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/page_launch")) + _, err = wd.CustomePost(data, wd.concatURL(wd.Session.ID, "ui/page_launch")) return err } @@ -185,7 +185,7 @@ func (wd *BrowserDriver) Scroll(delta int) (err error) { data := map[string]interface{}{ "delta": delta, } - _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/scroll")) + _, err = wd.CustomePost(data, wd.concatURL(wd.Session.ID, "ui/scroll")) return err } @@ -210,7 +210,7 @@ func (wd *BrowserDriver) CloseTab(pageIndex int) (err error) { "page_index": pageIndex, } - _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/page_close")) + _, err = wd.CustomePost(data, wd.concatURL(wd.Session.ID, "ui/page_close")) return err } @@ -223,7 +223,7 @@ func (wd *BrowserDriver) HoverBySelector(selector string, options ...option.Acti if actionOptions.Index > 0 { data["element_index"] = actionOptions.Index } - _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/hover")) + _, err = wd.CustomePost(data, wd.concatURL(wd.Session.ID, "ui/hover")) return err } @@ -236,7 +236,7 @@ func (wd *BrowserDriver) TapBySelector(selector string, options ...option.Action if actionOptions.Index > 0 { data["element_index"] = actionOptions.Index } - _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/tap")) + _, err = wd.CustomePost(data, wd.concatURL(wd.Session.ID, "ui/tap")) return err } @@ -246,7 +246,7 @@ func (wd *BrowserDriver) SecondaryClick(x, y float64) (err error) { "x": x, "y": y, } - _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/right_click")) + _, err = wd.CustomePost(data, wd.concatURL(wd.Session.ID, "ui/right_click")) return err } @@ -259,7 +259,7 @@ func (wd *BrowserDriver) SecondaryClickBySelector(selector string, options ...op if actionOptions.Index > 0 { data["element_index"] = actionOptions.Index } - _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/right_click")) + _, err = wd.CustomePost(data, wd.concatURL(wd.Session.ID, "ui/right_click")) return err } @@ -315,28 +315,21 @@ func (wd *BrowserDriver) GetPageUrl(options ...option.ActionOption) (text string if actionOptions.Index > 0 { uri = uri + "?page_index=" + fmt.Sprintf("%v", actionOptions.Index) } - resp, err := wd.Session.GET(wd.concatURL(wd.Session.ID, uri)) + resp, err := wd.CustomeGet(wd.concatURL(wd.Session.ID, uri)) if err != nil { return "", err } - data, err := resp.ValueConvertToJsonObject() - if err != nil { - return "", err - } - data = data["data"].(map[string]interface{}) + data := resp.Data.(map[string]interface{}) return data["url"].(string), nil } func (wd *BrowserDriver) IsElementExistBySelector(selector string) (bool, error) { - resp, err := wd.Session.GET(wd.concatURL("ui/element_exist", "?selector=", selector)) + resp, err := wd.CustomeGet(wd.concatURL("ui/element_exist", "?selector=", selector)) if err != nil { return false, err } - data, err := resp.ValueConvertToJsonObject() - if err != nil { - return false, err - } - data = data["data"].(map[string]interface{}) + + data := resp.Data.(map[string]interface{}) return data["exist"].(bool), nil } @@ -345,7 +338,7 @@ func (wd *BrowserDriver) LoginNoneUI(packageName, phoneNumber string, captcha, p "url": packageName, "web_cookie": password, } - _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "stub/login")) + _, err = wd.CustomePost(data, wd.concatURL(wd.Session.ID, "stub/login")) if err != nil { return false, err } @@ -357,7 +350,7 @@ func (wd *BrowserDriver) Hover(x, y float64) (err error) { "x": x, "y": y, } - _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/hover")) + _, err = wd.CustomePost(data, wd.concatURL(wd.Session.ID, "ui/hover")) return err } @@ -365,37 +358,32 @@ func (wd *BrowserDriver) Input(text string, option ...option.ActionOption) (err data := map[string]interface{}{ "text": text, } - _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/input")) + _, err = wd.CustomePost(data, wd.concatURL(wd.Session.ID, "ui/input")) return err } // Source Return application elements tree func (wd *BrowserDriver) Source(srcOpt ...option.SourceOption) (string, error) { - resp, err := wd.Session.GET(wd.concatURL(wd.Session.ID, "stub/source")) + result, err := wd.CustomeGet(wd.concatURL(wd.Session.ID, "stub/source")) + if err != nil { return "", err } - return resp.ValueConvertToString() + jsonData, err := json.Marshal(result.Data) + if err != nil { + return "", err + } + + return string(jsonData), err } func (wd *BrowserDriver) ScreenShot(options ...option.ActionOption) (*bytes.Buffer, error) { - resp, err := wd.Session.GET(wd.concatURL(wd.Session.ID, "screenshot")) + result, err := wd.CustomeGet(wd.concatURL(wd.Session.ID, "screenshot")) if err != nil { return nil, err } - // 将结果解析为 JSON - var result WebAgentResponse - if err = json.Unmarshal(resp, &result); err != nil { - return nil, err - } - - if result.Code != 0 { - log.Info().Msgf("%v", result.Message) - return nil, errors.New(result.Message) - } - data := result.Data.(map[string]interface{}) screenshotBase64 := data["screenshot"].(string) screenRaw, err := base64.StdEncoding.DecodeString(screenshotBase64) @@ -425,16 +413,12 @@ func (wd *BrowserDriver) BatteryInfo() (batteryInfo types.BatteryInfo, err error } func (wd *BrowserDriver) WindowSize() (types.Size, error) { - resp, err := wd.Session.GET(wd.concatURL(wd.Session.ID, "window_size")) + resp, err := wd.CustomeGet(wd.concatURL(wd.Session.ID, "window_size")) if err != nil { return types.Size{}, err } - data, err := resp.ValueConvertToJsonObject() - if err != nil { - return types.Size{}, err - } - data = data["data"].(map[string]interface{}) + data := resp.Data.(map[string]interface{}) width := data["width"] height := data["height"] return types.Size{ @@ -532,7 +516,7 @@ func (wd *BrowserDriver) TapFloat(x, y float64, opts ...option.ActionOption) err "duration": duration, } - _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/tap")) + _, err = wd.CustomePost(data, wd.concatURL(wd.Session.ID, "ui/tap")) return err } @@ -550,7 +534,7 @@ func (wd *BrowserDriver) DoubleTap(x, y float64, options ...option.ActionOption) "y": y, } - _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/double_tap")) + _, err = wd.CustomePost(data, wd.concatURL(wd.Session.ID, "ui/double_tap")) return err } @@ -561,7 +545,7 @@ func (wd *BrowserDriver) UploadFile(x, y float64, FileUrl, FileFormat string) (e "file_url": FileUrl, "file_format": FileFormat, } - _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/upload")) + _, err = wd.CustomePost(data, wd.concatURL(wd.Session.ID, "ui/upload")) return err } @@ -607,7 +591,7 @@ func (wd *BrowserDriver) ForegroundInfo() (app types.AppInfo, err error) { // PressBack Presses the back button func (wd *BrowserDriver) PressBack(options ...option.ActionOption) error { - _, err := wd.Session.POST(nil, wd.concatURL(wd.Session.ID, "ui/back")) + _, err := wd.CustomePost(nil, wd.concatURL(wd.Session.ID, "ui/back")) return err } @@ -699,7 +683,7 @@ func (wd *BrowserDriver) TapXY(x, y float64, opts ...option.ActionOption) error "x": x, "y": y, } - _, err := wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/double_tap")) + _, err := wd.CustomePost(data, wd.concatURL(wd.Session.ID, "ui/tap")) return err } @@ -711,7 +695,7 @@ func (wd *BrowserDriver) SetHeader(headers string) (err error) { data := map[string]interface{}{ "headers": headers, } - _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "set_headers")) + _, err = wd.CustomePost(data, wd.concatURL(wd.Session.ID, "set_headers")) return err } @@ -719,7 +703,7 @@ func (wd *BrowserDriver) Keyboard(key string) (err error) { data := map[string]interface{}{ "press": key, } - _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/keyboard")) + _, err = wd.CustomePost(data, wd.concatURL(wd.Session.ID, "ui/keyboard")) return err } @@ -727,6 +711,47 @@ func (wd *BrowserDriver) PageAction(action string) (err error) { data := map[string]interface{}{ "action": action, } - _, err = wd.Session.POST(data, wd.concatURL(wd.Session.ID, "ui/page/action")) + _, err = wd.CustomePost(data, wd.concatURL(wd.Session.ID, "ui/page/action")) return err } + +func (wd *BrowserDriver) CustomePost(data interface{}, urlStr string) (response *WebAgentResponse, err error) { + //通过wd.Session.POST的opts参数设置重试次数为1 + + rawResp, err := wd.Session.POST(data, urlStr, option.WithMaxRetryTimes(1), option.WithTimeout(3*60)) + if err != nil { + return nil, err + } + + var result WebAgentResponse + if err = json.Unmarshal(rawResp, &result); err != nil { + return nil, err + } + + if result.Code != 0 { + log.Info().Msgf("%v", result.Message) + return nil, errors.New(result.Message) + } + return &result, err +} + +func (wd *BrowserDriver) CustomeGet(urlStr string) (response *WebAgentResponse, err error) { + //通过wd.Session.POST的opts参数设置重试次数为1 + + rawResp, err := wd.Session.GET(urlStr, option.WithMaxRetryTimes(1), option.WithTimeout(3*60)) + if err != nil { + return nil, err + } + + var webResp WebAgentResponse + if err = json.Unmarshal(rawResp, &webResp); err != nil { + return nil, err + } + + if webResp.Code != 0 { + log.Info().Msgf("%v", webResp.Message) + return nil, errors.New(webResp.Message) + } + + return &webResp, err +} From a11eea45f67b43a7970d9161fc1adbdffb391a53 Mon Sep 17 00:00:00 2001 From: "huangbin.beal" Date: Thu, 10 Jul 2025 16:41:46 +0800 Subject: [PATCH 06/14] fix: useless code --- uixt/browser_driver.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/uixt/browser_driver.go b/uixt/browser_driver.go index c13bbf2b..28820a81 100644 --- a/uixt/browser_driver.go +++ b/uixt/browser_driver.go @@ -716,8 +716,6 @@ func (wd *BrowserDriver) PageAction(action string) (err error) { } func (wd *BrowserDriver) CustomePost(data interface{}, urlStr string) (response *WebAgentResponse, err error) { - //通过wd.Session.POST的opts参数设置重试次数为1 - rawResp, err := wd.Session.POST(data, urlStr, option.WithMaxRetryTimes(1), option.WithTimeout(3*60)) if err != nil { return nil, err @@ -736,8 +734,6 @@ func (wd *BrowserDriver) CustomePost(data interface{}, urlStr string) (response } func (wd *BrowserDriver) CustomeGet(urlStr string) (response *WebAgentResponse, err error) { - //通过wd.Session.POST的opts参数设置重试次数为1 - rawResp, err := wd.Session.GET(urlStr, option.WithMaxRetryTimes(1), option.WithTimeout(3*60)) if err != nil { return nil, err From b7e4b3844290920ebf664889359868d10efe128b Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Fri, 11 Jul 2025 13:32:13 +0800 Subject: [PATCH 07/14] fix: ai_assert screenshot --- internal/version/VERSION | 2 +- report.go | 207 ++++++++++++--------------------------- 2 files changed, 64 insertions(+), 145 deletions(-) diff --git a/internal/version/VERSION b/internal/version/VERSION index 552c255e..76fd650b 100644 --- a/internal/version/VERSION +++ b/internal/version/VERSION @@ -1 +1 @@ -v5.0.0-250710 +v5.0.0-250711 diff --git a/report.go b/report.go index 18b44d5f..bb72904b 100644 --- a/report.go +++ b/report.go @@ -1194,6 +1194,15 @@ const htmlTemplate = ` padding: 12px; } + .screenshot-display .screenshot-image { + min-height: 300px; + padding: 15px 0; + } + + .screenshot-display .screenshot-image img { + max-height: 350px; + } + .screenshot-item-compact { text-align: center; } @@ -1212,7 +1221,7 @@ const htmlTemplate = ` .screenshot-item-compact .screenshot-image img { width: 100%; height: auto; - max-height: 500px; + max-height: 350px; border-radius: 4px; cursor: pointer; transition: transform 0.2s; @@ -1223,7 +1232,7 @@ const htmlTemplate = ` /* Handle very tall screenshots */ .screenshot-item-compact .screenshot-image img[style*="height"] { - max-height: 400px; + max-height: 350px; width: auto; max-width: 100%; } @@ -1838,101 +1847,6 @@ const htmlTemplate = ` word-wrap: break-word; } - /* AI Assertion Styles */ - .ai-assertion-section { - margin-top: 15px; - padding: 15px; - background: linear-gradient(135deg, #f0f8ff 0%, #f5f5ff 100%); - border: 2px solid #4169e1; - border-radius: 12px; - box-shadow: 0 4px 8px rgba(65, 105, 225, 0.15); - } - - .ai-assertion-section h5 { - margin: 0 0 15px 0; - color: #4169e1; - font-size: 1.1em; - font-weight: 600; - } - - .ai-screenshot-container { - background: white; - border: 1px solid #dee2e6; - border-radius: 8px; - padding: 12px; - margin-bottom: 15px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - } - - .ai-screenshot { - text-align: center; - margin-top: 10px; - } - - .ai-screenshot img { - max-width: 100%; - height: auto; - border-radius: 8px; - border: 1px solid #dee2e6; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - cursor: pointer; - transition: transform 0.2s; - } - - .ai-screenshot img:hover { - transform: scale(1.02); - } - - .ai-analysis-container { - background: white; - border: 1px solid #dee2e6; - border-radius: 8px; - padding: 12px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - } - - .ai-analysis-content { - margin-top: 10px; - } - - .ai-thought { - background: linear-gradient(135deg, #e8f4fd 0%, #f0f8ff 100%); - border: 1px solid #4169e1; - border-radius: 8px; - padding: 12px; - margin: 10px 0; - color: #2c3e50; - } - - .ai-thought .thought-content { - margin-top: 8px; - font-style: italic; - color: #34495e; - white-space: pre-wrap; - word-wrap: break-word; - } - - .ai-raw-response { - background: #f8f9fa; - border: 1px solid #dee2e6; - border-radius: 8px; - padding: 12px; - margin: 10px 0; - color: #2c3e50; - } - - .ai-raw-response .response-content { - margin-top: 8px; - font-family: monospace; - font-size: 0.9em; - background: white; - padding: 8px; - border-radius: 4px; - border: 1px solid #e9ecef; - white-space: pre-wrap; - word-wrap: break-word; - } - @media screen and (max-width: 768px) { .validator-ai-layout { flex-direction: column; @@ -2891,10 +2805,8 @@ const htmlTemplate = `
{{$base64Image := encodeImageBase64 $action.AIResult.ImagePath}} {{if $base64Image}} -
-
- AI {{title $action.AIResult.Type}} Screenshot -
+
+ AI {{title $action.AIResult.Type}} Screenshot
{{end}}
@@ -3048,53 +2960,60 @@ const htmlTemplate = ` {{if $validator.ai_result}} -
-
🤖 AI Assertion Details
- - - {{if $validator.ai_result.image_path}} -
- 📸 AI Assertion Screenshot - {{if $validator.ai_result.screenshot_elapsed}} - {{formatDuration $validator.ai_result.screenshot_elapsed}} - {{end}} -
- {{$base64Image := encodeImageBase64 $validator.ai_result.image_path}} - {{if $base64Image}} - AI Assertion Screenshot - {{end}} -
-
+
+ + {{if $validator.ai_result.assertion_result.thought}} +
{{$validator.ai_result.assertion_result.thought}}
{{end}} - -
- 🧠 AI Model Analysis - {{if $validator.ai_result.model_call_elapsed}} - {{formatDuration $validator.ai_result.model_call_elapsed}} + +
+ + {{if $validator.ai_result.image_path}} +
+
+
+ 📸 AI Assertion Screenshot + {{if $validator.ai_result.screenshot_elapsed}} + {{formatDuration $validator.ai_result.screenshot_elapsed}} + {{end}} +
+
+ {{$base64Image := encodeImageBase64 $validator.ai_result.image_path}} + {{if $base64Image}} +
+ AI Assertion Screenshot +
+ {{end}} +
+
+
{{end}} -
- {{if $validator.ai_result.assertion_result.model_name}} -
🤖 Model: {{$validator.ai_result.assertion_result.model_name}}
- {{end}} - {{if $validator.ai_result.assertion_result.usage}} -
📊 Tokens: {{$validator.ai_result.assertion_result.usage.PromptTokens}} in / {{$validator.ai_result.assertion_result.usage.CompletionTokens}} out / {{$validator.ai_result.assertion_result.usage.TotalTokens}} total
- {{end}} - {{if $validator.ai_result.resolution}} -
📐 Resolution: {{$validator.ai_result.resolution.Width}}x{{$validator.ai_result.resolution.Height}}
- {{end}} - {{if $validator.ai_result.assertion_result.thought}} -
- 💭 AI Reasoning: -
{{$validator.ai_result.assertion_result.thought}}
+ + +
+
+
+ 🤖 AI Assertion Analysis + {{if $validator.ai_result.model_call_elapsed}} + {{formatDuration $validator.ai_result.model_call_elapsed}} + {{end}} +
+
+ {{if $validator.ai_result.assertion_result.model_name}} +
🤖 Model: {{$validator.ai_result.assertion_result.model_name}}
+ {{end}} + {{if $validator.ai_result.assertion_result.usage}} +
📊 Tokens: {{$validator.ai_result.assertion_result.usage.PromptTokens}} in / {{$validator.ai_result.assertion_result.usage.CompletionTokens}} out / {{$validator.ai_result.assertion_result.usage.TotalTokens}} total
+ {{end}} + {{if $validator.ai_result.resolution}} +
📐 Resolution: {{$validator.ai_result.resolution.Width}}x{{$validator.ai_result.resolution.Height}}
+ {{end}} + {{if $validator.ai_result.assertion_result.content}} +
💬 Assertion Result: {{$validator.ai_result.assertion_result.content}}
+ {{end}} +
- {{end}} - {{if $validator.ai_result.assertion_result.content}} -
- 📝 Raw Model Response: -
{{$validator.ai_result.assertion_result.content}}
-
- {{end}}
From bd268845660b8247dd51adef3f4999510cde9a72 Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Fri, 11 Jul 2025 14:02:30 +0800 Subject: [PATCH 08/14] fix: avoid printing base64 data for screenshot tools --- uixt/sdk.go | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/uixt/sdk.go b/uixt/sdk.go index 8ea1c609..32f153b0 100644 --- a/uixt/sdk.go +++ b/uixt/sdk.go @@ -156,9 +156,13 @@ func (dExt *XTDriver) ExecuteAction(ctx context.Context, action option.MobileAct // For regular actions, collect session data and return it directly sessionData := dExt.GetSession().GetData(true) // reset after getting data - log.Debug().Str("tool", string(tool.Name())). - Interface("result", result.Content). - Msg("executed action via MCP tool") + // Log execution result, but avoid printing base64 data for screenshot tools + logger := log.Debug().Str("tool", string(tool.Name())) + if tool.Name() != option.ACTION_ScreenShot { + logger.Interface("result", result.Content) + } + logger.Msg("executed action via MCP tool") + return sessionData, nil } @@ -241,22 +245,27 @@ func (dExt *XTDriver) CallMCPTool(ctx context.Context, log.Debug().Err(err). Str("server", serverName). Str("tool", toolName). - Msg("MCP hook call failed") + Msg("call MCP tool failed") return nil, err } if result.IsError { - log.Debug(). + logger := log.Debug(). Str("server", serverName). - Str("tool", toolName). - Interface("content", result.Content). - Msg("MCP hook returned error") - return nil, fmt.Errorf("MCP hook returned error") + Str("tool", toolName) + + // Avoid printing base64 data for screenshot tools + if toolName != string(option.ACTION_ScreenShot) { + logger.Interface("content", result.Content) + } + logger.Msg("call MCP tool failed") + + return nil, fmt.Errorf("call MCP tool %s failed", toolName) } log.Debug(). Str("server", serverName). Str("tool", toolName). - Msg("MCP hook called successfully") + Msg("call MCP tool successfully") return result, nil } From 1c5cc1547b97bd4bddd48cc69c42007e51dd29db Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Fri, 11 Jul 2025 15:50:30 +0800 Subject: [PATCH 09/14] fix: print step end logs --- runner.go | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/runner.go b/runner.go index a1f13c4a..a93c96f1 100644 --- a/runner.go +++ b/runner.go @@ -878,9 +878,6 @@ func (r *SessionRunner) RunStep(step IStep) (stepResult *StepResult, err error) } } - stepName := step.Name() - stepType := string(step.Type()) - // execute step with parameters iterator tasks, err := r.generateExecutionTasks(step) if err != nil { @@ -899,30 +896,10 @@ func (r *SessionRunner) RunStep(step IStep) (stepResult *StepResult, err error) r.sessionVariables[k] = v } } - - // log final result - if err == nil && stepResult.Success { - log.Info().Str("step", stepName). - Str("type", stepType). - Bool("success", true). - Int64("elapsed(ms)", stepResult.Elapsed). - Interface("exportVars", stepResult.ExportVars). - Msg(RUN_STEP_END) - } else if stepResult != nil { - log.Error().Str("step", stepName). - Str("type", stepType). - Bool("success", false). - Int64("elapsed(ms)", stepResult.Elapsed). - Int("completed_tasks", len(stepResults)). - Int("total_tasks", len(tasks)). - Msg(RUN_STEP_END) - } }() // execute with loops as outer iteration for _, task := range tasks { - log.Info().Str("step", task.stepName).Str("type", stepType).Msg(RUN_STEP_START) - // Check for interrupt signal before each parameter iteration select { case <-r.caseRunner.hrpRunner.interruptSignal: @@ -967,6 +944,24 @@ func (r *SessionRunner) RunStep(step IStep) (stepResult *StepResult, err error) // executeStepWithVariables executes a single step with given parameters // parameters will override step variables with the same name func (r *SessionRunner) executeStepWithVariables(step IStep, stepName string, parameters map[string]interface{}) (stepResult *StepResult, err error) { + stepType := string(step.Type()) + log.Info().Str("step", stepName).Str("type", stepType).Msg(RUN_STEP_START) + defer func() { + if err == nil && stepResult.Success { + log.Info().Str("step", stepName). + Str("type", stepType). + Bool("success", true). + Int64("elapsed(ms)", stepResult.Elapsed). + Msg(RUN_STEP_END) + } else { + log.Error().Str("step", stepName). + Str("type", stepType). + Bool("success", false). + Int64("elapsed(ms)", stepResult.Elapsed). + Msg(RUN_STEP_END) + } + }() + stepConfig := step.Config() // backup original variables From 4c418f918309544313f7dfac59eb50ab0a1b6197 Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Fri, 11 Jul 2025 16:10:46 +0800 Subject: [PATCH 10/14] change: get wda screen scale --- uixt/ios_driver_wda.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/uixt/ios_driver_wda.go b/uixt/ios_driver_wda.go index c2735e55..61414503 100644 --- a/uixt/ios_driver_wda.go +++ b/uixt/ios_driver_wda.go @@ -275,14 +275,14 @@ func (wd *WDADriver) Scale() (float64, error) { } type Screen struct { - StatusBarSize types.Size `json:"statusBarSize"` - Scale float64 `json:"scale"` + types.Size + Scale float64 `json:"scale"` } func (wd *WDADriver) Screen() (screen Screen, err error) { - // [[FBRoute GET:@"/wda/screen"] respondWithTarget:self action:@selector(handleGetScreen:)] + // [[FBRoute GET:@"/wings/window/size"] respondWithTarget:self action:@selector(handleGetScreen:)] var rawResp DriverRawResponse - if rawResp, err = wd.Session.GET("/wda/screen"); err != nil { + if rawResp, err = wd.Session.GET("/wings/window/size"); err != nil { return Screen{}, err } reply := new(struct{ Value struct{ Screen } }) From db36eef9aa1e48ae68a917f854fc04011bb32f23 Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Fri, 11 Jul 2025 19:06:26 +0800 Subject: [PATCH 11/14] change: rename test report to Wings --- report.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/report.go b/report.go index bb72904b..6c790a68 100644 --- a/report.go +++ b/report.go @@ -635,7 +635,7 @@ const htmlTemplate = ` - HttpRunner Test Report + Wings Test Report