mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-09 01:39:39 +08:00
Merge branch 'master' into bugfix/huangbin/web_v5
This commit is contained in:
@@ -33,8 +33,9 @@ type AssertOptions struct {
|
||||
type AssertionResult struct {
|
||||
Pass bool `json:"pass"`
|
||||
Thought string `json:"thought"`
|
||||
ModelName string `json:"model_name"` // model name used for assertion
|
||||
Usage *schema.TokenUsage `json:"usage,omitempty"` // token usage statistics
|
||||
Content string `json:"content,omitempty"` // raw response content
|
||||
ModelName string `json:"model_name"` // model name used for assertion
|
||||
Usage *schema.TokenUsage `json:"usage,omitempty"` // token usage statistics
|
||||
}
|
||||
|
||||
// Asserter handles assertion using different AI models
|
||||
@@ -180,5 +181,6 @@ func parseAssertionResult(content string, modelType option.LLMServiceType) (*Ass
|
||||
}
|
||||
|
||||
result.ModelName = string(modelType)
|
||||
result.Content = content // Store the original response content
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
@@ -492,9 +492,10 @@ func (dExt *XTDriver) AIAssert(assertion string, opts ...option.ActionOption) (*
|
||||
return assertResult, errors.Wrap(err, "AI assertion failed")
|
||||
}
|
||||
|
||||
// For assertion failure, we should still return success but mark the assertion as failed
|
||||
// This ensures that the AIResult (including screenshot and thought) is properly saved and displayed
|
||||
if !result.Pass {
|
||||
assertResult.Error = result.Thought
|
||||
return assertResult, errors.New(result.Thought)
|
||||
assertResult.Error = result.Thought // Store the failure reason for reporting
|
||||
}
|
||||
|
||||
return assertResult, nil
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/v5/internal/json"
|
||||
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||
)
|
||||
|
||||
type Attachments map[string]interface{}
|
||||
@@ -151,32 +152,38 @@ func (s *DriverSession) buildURL(urlStr string) (string, error) {
|
||||
return baseURL.ResolveReference(relativeURL).String(), nil
|
||||
}
|
||||
|
||||
func (s *DriverSession) GET(urlStr string) (rawResp DriverRawResponse, err error) {
|
||||
return s.RequestWithRetry(http.MethodGet, urlStr, nil)
|
||||
func (s *DriverSession) GET(urlStr string, opts ...option.ActionOption) (rawResp DriverRawResponse, err error) {
|
||||
return s.RequestWithRetry(http.MethodGet, urlStr, nil, opts...)
|
||||
}
|
||||
|
||||
func (s *DriverSession) POST(data interface{}, urlStr string) (rawResp DriverRawResponse, err error) {
|
||||
func (s *DriverSession) POST(data interface{}, urlStr string, opts ...option.ActionOption) (rawResp DriverRawResponse, err error) {
|
||||
var bsJSON []byte = nil
|
||||
if data != nil {
|
||||
if bsJSON, err = json.Marshal(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return s.RequestWithRetry(http.MethodPost, urlStr, bsJSON)
|
||||
return s.RequestWithRetry(http.MethodPost, urlStr, bsJSON, opts...)
|
||||
}
|
||||
|
||||
func (s *DriverSession) DELETE(urlStr string) (rawResp DriverRawResponse, err error) {
|
||||
return s.RequestWithRetry(http.MethodDelete, urlStr, nil)
|
||||
func (s *DriverSession) DELETE(urlStr string, opts ...option.ActionOption) (rawResp DriverRawResponse, err error) {
|
||||
return s.RequestWithRetry(http.MethodDelete, urlStr, nil, opts...)
|
||||
}
|
||||
|
||||
func (s *DriverSession) RequestWithRetry(method string, urlStr string, rawBody []byte) (
|
||||
func (s *DriverSession) RequestWithRetry(method string, urlStr string, rawBody []byte, opts ...option.ActionOption) (
|
||||
rawResp DriverRawResponse, err error,
|
||||
) {
|
||||
var lastError error
|
||||
|
||||
for attempt := 1; attempt <= s.maxRetry; attempt++ {
|
||||
maxRetry := s.maxRetry
|
||||
options := option.NewActionOptions(opts...)
|
||||
if options.MaxRetryTimes > 0 {
|
||||
maxRetry = options.MaxRetryTimes
|
||||
}
|
||||
|
||||
for attempt := 1; attempt <= maxRetry; attempt++ {
|
||||
// Execute the request
|
||||
rawResp, err = s.Request(method, urlStr, rawBody)
|
||||
rawResp, err = s.Request(method, urlStr, rawBody, opts...)
|
||||
if err == nil {
|
||||
if attempt > 1 {
|
||||
log.Info().Msgf("request succeeded after %d attempts", attempt)
|
||||
@@ -210,9 +217,15 @@ func (s *DriverSession) RequestWithRetry(method string, urlStr string, rawBody [
|
||||
return nil, lastError
|
||||
}
|
||||
|
||||
func (s *DriverSession) Request(method string, urlStr string, rawBody []byte) (
|
||||
func (s *DriverSession) Request(method string, urlStr string, rawBody []byte, opts ...option.ActionOption) (
|
||||
rawResp DriverRawResponse, err error,
|
||||
) {
|
||||
timeout := s.timeout
|
||||
options := option.NewActionOptions(opts...)
|
||||
if options.Timeout > 0 {
|
||||
timeout = time.Duration(options.Timeout) * time.Second
|
||||
}
|
||||
|
||||
// build final URL
|
||||
rawURL, err := s.buildURL(urlStr)
|
||||
if err != nil {
|
||||
@@ -252,7 +265,7 @@ func (s *DriverSession) Request(method string, urlStr string, rawBody []byte) (
|
||||
logger.Msg("request uixt driver")
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithTimeout(s.ctx, s.timeout)
|
||||
ctx, cancel := context.WithTimeout(s.ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, rawURL, bytes.NewBuffer(rawBody))
|
||||
|
||||
@@ -65,8 +65,8 @@ func convertToAbsolutePoint(driver IDriver, x, y float64) (absX, absY float64, e
|
||||
}
|
||||
|
||||
func convertToAbsoluteCoordinates(driver IDriver, fromX, fromY, toX, toY float64) (
|
||||
absFromX, absFromY, absToX, absToY float64, err error) {
|
||||
|
||||
absFromX, absFromY, absToX, absToY float64, err error,
|
||||
) {
|
||||
// absolute coordinates
|
||||
if fromX > 1 || toX > 1 || fromY > 1 || toY > 1 {
|
||||
return fromX, fromY, toX, toY, nil
|
||||
@@ -190,32 +190,43 @@ func (dExt *XTDriver) assertSelector(selector, assert string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dExt *XTDriver) DoValidation(check, assert, expected string, message ...string) (err error) {
|
||||
func (dExt *XTDriver) DoValidation(check, assert, expected string, message ...string) (aiResult *AIExecutionResult, err error) {
|
||||
switch check {
|
||||
case option.SelectorOCR:
|
||||
err = dExt.assertOCR(expected, assert)
|
||||
case option.SelectorAI:
|
||||
_, err = dExt.AIAssert(expected)
|
||||
aiResult, err = dExt.AIAssert(expected)
|
||||
case option.SelectorForegroundApp:
|
||||
err = dExt.assertForegroundApp(expected, assert)
|
||||
case option.SelectorSelector:
|
||||
err = dExt.assertSelector(expected, assert)
|
||||
default:
|
||||
return fmt.Errorf("validator %s not implemented", check)
|
||||
return nil, fmt.Errorf("validator %s not implemented", check)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Technical error (not assertion failure)
|
||||
if message == nil {
|
||||
message = []string{""}
|
||||
}
|
||||
log.Error().Err(err).Str("assert", assert).Str("expect", expected).
|
||||
Str("msg", message[0]).Msg("validate failed")
|
||||
return err
|
||||
return nil, err
|
||||
} else if aiResult != nil {
|
||||
// Check assertion result instead of relying on error
|
||||
if !aiResult.AssertionResult.Pass {
|
||||
return aiResult, errors.New(aiResult.AssertionResult.Thought)
|
||||
}
|
||||
log.Info().Str("check", check).Str("assert", assert).
|
||||
Str("expect", expected).
|
||||
Interface("ai_assertion_result", aiResult.AssertionResult).
|
||||
Msg("ai assertion passed")
|
||||
return aiResult, nil
|
||||
} else {
|
||||
log.Info().Str("check", check).Str("assert", assert).
|
||||
Str("expect", expected).Msg("validate success")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
log.Info().Str("check", check).Str("assert", assert).
|
||||
Str("expect", expected).Msg("validate success")
|
||||
return nil
|
||||
}
|
||||
|
||||
type SleepConfig struct {
|
||||
|
||||
@@ -160,22 +160,22 @@ func (dev *IOSDevice) Setup() error {
|
||||
if version.GreaterThan(semver.MustParse("17.4.0")) {
|
||||
info, err := tunnel.TunnelInfoForDevice(dev.DeviceEntry.Properties.SerialNumber, ios.HttpApiHost(), ios.HttpApiPort())
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(code.DeviceConnectionError, err.Error())
|
||||
}
|
||||
dev.DeviceEntry.UserspaceTUNPort = info.UserspaceTUNPort
|
||||
dev.DeviceEntry.UserspaceTUN = info.UserspaceTUN
|
||||
rsdService, err := ios.NewWithAddrPortDevice(info.Address, info.RsdPort, dev.DeviceEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(code.DeviceConnectionError, err.Error())
|
||||
}
|
||||
defer rsdService.Close()
|
||||
rsdProvider, err := rsdService.Handshake()
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(code.DeviceConnectionError, err.Error())
|
||||
}
|
||||
device, err := ios.GetDeviceWithAddress(dev.DeviceEntry.Properties.SerialNumber, info.Address, rsdProvider)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(code.DeviceConnectionError, err.Error())
|
||||
}
|
||||
device.UserspaceTUN = dev.DeviceEntry.UserspaceTUN
|
||||
device.UserspaceTUNPort = dev.DeviceEntry.UserspaceTUNPort
|
||||
@@ -470,7 +470,7 @@ func (dev *IOSDevice) getVersion() (version *semver.Version, err error) {
|
||||
version, err = ios.GetProductVersion(dev.DeviceEntry)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to get version")
|
||||
return nil, err
|
||||
return nil, errors.Wrap(code.DeviceGetInfoError, err.Error())
|
||||
}
|
||||
log.Info().Str("version", version.String()).Msg("get ios device version")
|
||||
return version, nil
|
||||
|
||||
Reference in New Issue
Block a user