找回密码
 立即注册
首页 业界区 业界 跨平台自动化框架的OCR点击操作实现详解与思考 ...

跨平台自动化框架的OCR点击操作实现详解与思考

湛恶 2025-7-28 08:39:52
在移动端自动化测试领域,基于文字内容的操作一直是一个技术难点。HttpRunner通过集成OCR(光学字符识别)技术,实现了高精度的文字定位与点击功能,为开发者提供了更加直观和可靠的自动化测试方案。
核心架构设计
  1. 用户指定文字 → 截图 → OCR识别 → 文字定位 → 坐标计算 → 执行点击
复制代码
HttpRunner的OCR点击机制采用了分层架构设计,将复杂的图像识别流程抽象为清晰的模块边界。整个系统由用户接口层、文字查找层、OCR服务层和坐标计算层组成,各层职责明确,耦合度低。
用户接口层提供了简洁的API,开发者只需调用TapByOCR()方法并传入目标文字即可完成操作。该方法内部处理了截图文件命名、错误处理策略以及点击位置计算等复杂逻辑。
  1. func (dExt *XTDriver) TapByOCR(text string, opts ...option.ActionOption) error {
  2.         actionOptions := option.NewActionOptions(opts...)
  3.         log.Info().Str("text", text).Interface("options", actionOptions).Msg("TapByOCR")
  4.        
  5.         // 自动生成截图文件名,便于调试追踪
  6.         if actionOptions.ScreenShotFileName == "" {
  7.                 opts = append(opts, option.WithScreenShotFileName(fmt.Sprintf("tap_by_ocr_%s", text)))
  8.         }
  9.         // 执行文字定位操作
  10.         textRect, err := dExt.FindScreenText(text, opts...)
  11.         if err != nil {
  12.                 if actionOptions.IgnoreNotFoundError {
  13.                         return nil  // 容错处理,适用于可选操作场景
  14.                 }
  15.                 return err
  16.         }
  17.         // 智能点击位置计算
  18.         var point ai.PointF
  19.         if actionOptions.TapRandomRect {
  20.                 point = textRect.RandomPoint()  // 防检测随机点击
  21.         } else {
  22.                 point = textRect.Center()       // 精确中心点击
  23.         }
  24.         return dExt.TapAbsXY(point.X, point.Y, opts...)
  25. }
复制代码
文字识别与定位算法

文字查找模块是整个系统的核心,负责在屏幕截图中准确定位目标文字。该模块支持多种匹配模式,包括精确匹配、包含匹配和正则表达式匹配,能够适应不同的业务场景需求。
  1. func (dExt *XTDriver) FindScreenText(text string, opts ...option.ActionOption) (textRect ai.OCRText, err error) {
  2.         options := option.NewActionOptions(opts...)
  3.        
  4.         // 处理相对坐标转换,支持屏幕区域限制
  5.         if len(options.Scope) == 4 {
  6.                 windowSize, _ := dExt.WindowSize()
  7.                 absScope := options.Scope.ToAbs(windowSize)
  8.                 opts = append(opts, absScope.Option())
  9.         }
  10.         // 获取完整的OCR识别结果
  11.         ocrTexts, err := dExt.GetScreenTexts(opts...)
  12.         if err != nil {
  13.                 return
  14.         }
  15.         // 在识别结果中查找目标文字
  16.         textRect, err = ocrTexts.FindText(text, opts...)
  17.         if err != nil {
  18.                 log.Warn().Msgf("FindText failed: %s", err.Error())
  19.                 return
  20.         }
  21.         log.Info().Str("text", text).
  22.                 Interface("textRect", textRect).Msgf("FindScreenText success")
  23.         return textRect, nil
  24. }
复制代码
OCR数据结构与坐标转换

OCR服务返回的原始数据需要经过标准化处理才能被后续模块使用。HttpRunner定义了完整的数据结构来描述文字识别结果,包括文字内容、边界框坐标等关键信息。
OCR服务返回的坐标点数组遵循特定的顺序规则:左上、右上、右下、左下。这种标准化的坐标表示方式确保了不同OCR服务提供商之间的兼容性。
  1. type OCRResult struct {
  2.         Text   string   `json:"text"`    // 识别的文字内容
  3.         Points []PointF `json:"points"`  // 四个顶点坐标,顺序固定
  4. }
  5. func (o OCRResults) ToOCRTexts() (ocrTexts OCRTexts) {
  6.         for _, ocrResult := range o {
  7.                 rect := image.Rectangle{
  8.                         // 利用左上和右下两个关键点构建矩形
  9.                         Min: image.Point{
  10.                                 X: int(ocrResult.Points[0].X),  // 左上角X
  11.                                 Y: int(ocrResult.Points[0].Y),  // 左上角Y
  12.                         },
  13.                         Max: image.Point{
  14.                                 X: int(ocrResult.Points[2].X),  // 右下角X
  15.                                 Y: int(ocrResult.Points[2].Y),  // 右下角Y
  16.                         },
  17.                 }
  18.                
  19.                 rectStr := fmt.Sprintf("%d,%d,%d,%d",
  20.                         rect.Min.X, rect.Min.Y, rect.Max.X, rect.Max.Y)
  21.                
  22.                 ocrText := OCRText{
  23.                         Text:    ocrResult.Text,
  24.                         Rect:    rect,
  25.                         RectStr: rectStr,
  26.                 }
  27.                 ocrTexts = append(ocrTexts, ocrText)
  28.         }
  29.         return
  30. }
复制代码
高精度文字匹配机制

文字匹配算法支持多种策略,包括简单的字符串包含匹配和复杂的正则表达式匹配。为了提高匹配的准确性,系统还提供了区域过滤功能,允许开发者将搜索范围限制在屏幕的特定区域内。
  1. func (t OCRTexts) FindText(text string, opts ...option.ActionOption) (result OCRText, err error) {
  2.         options := option.NewActionOptions(opts...)
  3.        
  4.         var results []OCRText
  5.         for _, ocrText := range t.FilterScope(options.AbsScope) {
  6.                 if options.Regex {
  7.                         // 正则表达式匹配,支持复杂模式
  8.                         matched, _ := regexp.MatchString(text, ocrText.Text)
  9.                         if matched {
  10.                                 results = append(results, ocrText)
  11.                         }
  12.                 } else {
  13.                         // 包含匹配,处理大部分常见场景
  14.                         if strings.Contains(ocrText.Text, text) {
  15.                                 results = append(results, ocrText)
  16.                         }
  17.                 }
  18.         }
  19.         if len(results) == 0 {
  20.                 return result, errors.Wrap(code.CVResultNotFoundError,
  21.                         fmt.Sprintf("text %s not found in %v", text, t.texts()))
  22.         }
  23.         // 支持多匹配结果的索引选择
  24.         idx := options.Index
  25.         if idx >= len(results) {
  26.                 idx = len(results) - 1
  27.         }
  28.         return results[idx], nil
  29. }
复制代码
智能坐标计算算法

在确定目标文字的边界框后,系统需要计算精确的点击坐标。HttpRunner提供了两种点击策略:中心点击和随机点击。中心点击适用于对精度要求较高的场景,而随机点击则可以有效规避反作弊系统的检测。
  1. func (t OCRText) Center() PointF {
  2.         rect := t.Rect
  3.         x, y := float64(rect.Min.X), float64(rect.Min.Y)
  4.         width, height := float64(rect.Dx()), float64(rect.Dy())
  5.         point := PointF{
  6.                 X: x + width*0.5,   // 几何中心X坐标
  7.                 Y: y + height*0.5,  // 几何中心Y坐标
  8.         }
  9.         return point
  10. }
  11. func (t OCRText) RandomPoint() PointF {
  12.         rect := t.Rect
  13.         x, y := float64(rect.Min.X), float64(rect.Min.Y)
  14.         width, height := float64(rect.Dx()), float64(rect.Dy())
  15.         point := PointF{
  16.                 X: x + width*rand.Float64(),   // 随机X坐标
  17.                 Y: y + height*rand.Float64(),  // 随机Y坐标
  18.         }
  19.         return point
  20. }
复制代码
坐标系统与范围限制

为了支持不同分辨率的设备,HttpRunner采用了相对坐标和绝对坐标的双重体系。相对坐标使用0到1的浮点数表示屏幕位置的百分比,而绝对坐标则使用实际的像素值。这种设计使得测试脚本能够在不同设备间无缝迁移。
  1. func (s Scope) ToAbs(windowSize types.Size) AbsScope {
  2.         x1, y1, x2, y2 := s[0], s[1], s[2], s[3]
  3.         // 相对坐标到绝对坐标的线性映射
  4.         absX1 := int(x1 * float64(windowSize.Width))
  5.         absY1 := int(y1 * float64(windowSize.Height))
  6.         absX2 := int(x2 * float64(windowSize.Width))
  7.         absY2 := int(y2 * float64(windowSize.Height))
  8.         return AbsScope{absX1, absY1, absX2, absY2}
  9. }
复制代码
VEDEM OCR服务集成

HttpRunner集成了VEDEM等专业的OCR服务提供商,通过HTTP API的方式实现图像识别功能。系统采用multipart/form-data格式上传截图,并通过action参数指定所需的服务类型。
  1. func (s *vedemCVService) ReadFromBuffer(imageBuf *bytes.Buffer, opts ...option.ActionOption) (
  2.         imageResult *CVResult, err error) {
  3.         bodyBuf := &bytes.Buffer{}
  4.         bodyWriter := multipart.NewWriter(bodyBuf)
  5.         // 指定OCR服务类型
  6.         for _, action := range screenshotActions {
  7.                 bodyWriter.WriteField("actions", action)
  8.         }
  9.         // 使用高精度OCR集群
  10.         bodyWriter.WriteField("ocrCluster", "highPrecision")
  11.         // 上传图像数据
  12.         formWriter, err := bodyWriter.CreateFormFile("image", "screenshot.png")
  13.         size, err := formWriter.Write(imageBuf.Bytes())
  14.         // 发送HTTP请求
  15.         req, err = http.NewRequest("POST", os.Getenv("VEDEM_IMAGE_URL"), copiedBodyBuf)
  16.         resp, err = client.Do(req)
  17. }
复制代码
配置选项与错误处理

系统提供了丰富的配置选项,允许开发者根据具体需求调整OCR点击行为。这些选项包括重试机制、错误处理策略、匹配模式等,能够有效提高自动化测试的稳定性和可靠性。
  1. type ActionOptions struct {
  2.         MaxRetryTimes       int     `json:"max_retry_times,omitempty"`        // 重试次数控制
  3.         Interval            float64 `json:"interval,omitempty"`               // 重试间隔设置
  4.         IgnoreNotFoundError bool    `json:"ignore_NotFoundError,omitempty"`   // 容错策略
  5.         Index               int     `json:"index,omitempty"`                  // 多结果索引
  6.         TapRandomRect       bool    `json:"tap_random_rect,omitempty"`        // 随机点击开关
  7.         Regex               bool    `json:"regex,omitempty"`                  // 正则匹配开关
  8. }
复制代码
跨平台点击框架的思考扩展

基于HttpRunner现有的OCR实现,我们可以构建一个更加完善的跨平台点击操作体系。理想的设计应该遵循效率优先的策略,即优先使用最高效的控件定位方案,当控件定位难以实现时选择图像识别,最后再选择OCR文字识别作为兜底方案。
这种分层策略能够在保证操作成功率的同时,最大化执行效率。控件定位直接与系统API交互,速度最快但受限于应用的可访问性支持;图像识别无需网络调用,适合离线场景但受设备分辨率影响;OCR识别通用性最强,可以处理任何可见文字,但需要网络服务支持且耗时较长。
统一点击接口设计
  1. type TapStrategy int
  2. const (
  3.         TapByControl TapStrategy = iota  // 控件定位优先
  4.         TapByImage                       // 图像识别优先
  5.         TapByOCR                         // OCR识别优先
  6.         TapByAuto                        // 自动降级策略
  7. )
  8. type UnifiedTapOptions struct {
  9.         Strategy        TapStrategy               `json:"strategy"`
  10.         ControlOptions  *ControlTapOptions       `json:"control_options,omitempty"`
  11.         ImageOptions    *ImageTapOptions         `json:"image_options,omitempty"`
  12.         OCROptions      *OCRTapOptions          `json:"ocr_options,omitempty"`
  13.         FallbackEnabled bool                     `json:"fallback_enabled"`
  14.         MaxRetryTimes   int                      `json:"max_retry_times"`
  15. }
  16. func (dExt *XTDriver) TapUnified(opts UnifiedTapOptions) error {
  17.         if opts.Strategy == TapByAuto {
  18.                 return dExt.tapWithFallback(opts)
  19.         }
  20.         return dExt.tapBySingleStrategy(opts)
  21. }
复制代码
控件定位点击实现

控件定位是最高效的点击方式,直接利用操作系统提供的可访问性API来定位UI元素。这种方式不需要图像处理,响应速度快,且不受屏幕分辨率和主题变化影响。
  1. type ControlTapOptions struct {
  2.         Locator        string  `json:"locator"`         // 定位器类型
  3.         Value          string  `json:"value"`           // 定位值
  4.         Timeout        float64 `json:"timeout"`         // 等待超时
  5.         WaitVisible    bool    `json:"wait_visible"`    // 等待可见
  6.         WaitEnabled    bool    `json:"wait_enabled"`    // 等待可点击
  7. }
  8. func (dExt *XTDriver) TapByControl(locator, value string, opts ...option.ActionOption) error {
  9.         log.Info().Str("locator", locator).Str("value", value).Msg("TapByControl")
  10.        
  11.         // 根据定位器类型选择定位策略
  12.         var element WebElement
  13.         var err error
  14.        
  15.         switch locator {
  16.         case "id":
  17.                 element, err = dExt.FindElementByID(value)
  18.         case "xpath":
  19.                 element, err = dExt.FindElementByXPath(value)
  20.         case "name":
  21.                 element, err = dExt.FindElementByName(value)
  22.         case "accessibility_id":
  23.                 element, err = dExt.FindElementByAccessibilityID(value)
  24.         case "class_name":
  25.                 element, err = dExt.FindElementByClassName(value)
  26.         default:
  27.                 return fmt.Errorf("unsupported locator type: %s", locator)
  28.         }
  29.        
  30.         if err != nil {
  31.                 return errors.Wrap(err, "control element not found")
  32.         }
  33.        
  34.         // 等待元素状态就绪
  35.         if err := dExt.waitElementReady(element, opts...); err != nil {
  36.                 return err
  37.         }
  38.        
  39.         // 执行点击操作
  40.         return element.Click()
  41. }
复制代码
图像识别点击实现

图像识别方案使用预先截取的按钮图片作为模板,通过图像匹配算法在屏幕上定位目标按钮。这种方法适合处理那些无法通过控件定位器访问的自定义UI组件。
  1. type ImageTapOptions struct {
  2.         Path       string  `json:"path"`        // 模板图片路径
  3.         Confidence float64 `json:"confidence"`  // 匹配置信度阈值
  4.         Grayscale  bool    `json:"grayscale"`   // 灰度匹配模式
  5.         Timeout    float64 `json:"timeout"`     // 匹配超时时间
  6. }
  7. func (dExt *XTDriver) TapByImage(imagePath string, opts ...option.ActionOption) error {
  8.         actionOptions := option.NewActionOptions(opts...)
  9.         log.Info().Str("imagePath", imagePath).Interface("options", actionOptions).Msg("TapByImage")
  10.        
  11.         // 加载模板图片
  12.         templateImg, err := dExt.loadTemplateImage(imagePath)
  13.         if err != nil {
  14.                 return errors.Wrap(err, "failed to load template image")
  15.         }
  16.        
  17.         // 获取当前屏幕截图
  18.         screenshot, err := dExt.TakeScreenshot()
  19.         if err != nil {
  20.                 return errors.Wrap(err, "failed to take screenshot")
  21.         }
  22.        
  23.         // 执行模板匹配
  24.         matchResult, err := dExt.templateMatch(screenshot, templateImg, actionOptions.Confidence)
  25.         if err != nil {
  26.                 return errors.Wrap(err, "template matching failed")
  27.         }
  28.        
  29.         // 计算点击坐标
  30.         clickPoint := matchResult.Center()
  31.         if actionOptions.TapRandomRect {
  32.                 clickPoint = matchResult.RandomPoint()
  33.         }
  34.        
  35.         return dExt.TapAbsXY(clickPoint.X, clickPoint.Y)
  36. }
  37. func (dExt *XTDriver) templateMatch(screenshot, template image.Image, threshold float64) (*ImageMatchResult, error) {
  38.         // 使用OpenCV或其他图像处理库进行模板匹配
  39.         // 返回匹配位置和置信度
  40.         result := &ImageMatchResult{}
  41.        
  42.         // 模板匹配算法实现
  43.         confidence, location := cv.MatchTemplate(screenshot, template)
  44.        
  45.         if confidence < threshold {
  46.                 return nil, fmt.Errorf("match confidence %.2f below threshold %.2f", confidence, threshold)
  47.         }
  48.        
  49.         result.Confidence = confidence
  50.         result.Rectangle = location
  51.         return result, nil
  52. }
复制代码
DSL语法设计与参数规范

为了提供开发者友好的接口,我们需要设计清晰的DSL语法来描述不同的点击操作。每种点击方式都有其特定的参数需求,DSL应该能够灵活地表达这些差异。
  1. # 控件定位点击
  2. - tap_by_control:
  3.     locator: "id"                    # 定位器类型: id, xpath, name, accessibility_id, class_name
  4.     value: "com.app:id/login_btn"    # 定位值
  5.     options:
  6.       timeout: 10.0                  # 等待超时时间
  7.       wait_visible: true             # 等待元素可见
  8.       wait_enabled: true             # 等待元素可点击
  9.       max_retry_times: 3             # 最大重试次数
  10. # 图像识别点击
  11. - tap_by_image:
  12.     path: "./images/login_button.png"  # 模板图片路径
  13.     options:
  14.       confidence: 0.8                  # 匹配置信度阈值 (0-1)
  15.       grayscale: false                 # 是否使用灰度匹配
  16.       timeout: 15.0                    # 匹配超时时间
  17.       tap_random_rect: false           # 是否随机点击位置
  18. # OCR文字点击
  19. - tap_by_ocr:
  20.     text: "登录"                      # 目标文字
  21.     options:
  22.       index: 0                        # 多匹配结果时的索引
  23.       scope: [0.0, 0.5, 1.0, 1.0]    # 搜索区域限制
  24.       regex: false                    # 是否使用正则表达式
  25.       tap_random_rect: true           # 随机点击防检测
  26. # 自动降级策略
  27. - tap_unified:
  28.     strategy: "auto"                  # auto, control, image, ocr
  29.     fallback_enabled: true            # 启用降级机制
  30.     control_options:
  31.       locator: "id"
  32.       value: "login_btn"
  33.     image_options:
  34.       path: "./images/login.png"
  35.       confidence: 0.75
  36.     ocr_options:
  37.       text: "登录"
  38.       regex: false
复制代码
写在最后

这种多元化的点击策略设计特别适合复杂的企业级应用测试场景。在实际使用中,开发者可以根据应用特点选择合适的策略组合:
对于原生应用,优先使用控件定位,因为它直接利用系统API,速度快且准确。对于混合应用或游戏,图像识别往往更加可靠。对于国际化应用或动态内容,OCR识别提供了最大的灵活性。
通过这种分层设计,HttpRunner不仅保持了原有OCR功能的强大能力,还扩展了更多高效的定位方案,为开发者提供了一个真正跨平台、高可靠性的自动化测试解决方案。
关注 【松哥AI自动化】 公众号,每周获取深度技术解析,从源码角度彻底理解各种工具的实现原理。更重要的是,遇到技术难题时,直接联系我!我会根据你的具体情况,提供最适合的解决方案和技术指导。
上期回顾:(从抓包GitHub Copilot认证请求,认识OAuth 2.0技术)

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册