【Go|第6期】浅谈Lorca页面中Chrome正受到自动测试软件的控制

日期:2023年7月8日
作者:Commas
签名:(ง •_•)ง 积跬步以致千里,积小流以成江海……
注释:如果您觉得有所帮助,帮忙点个赞,也可以关注我,我们一起成长;如果有不对的地方,还望各位大佬不吝赐教,谢谢^ - ^
1.01365 = 37.7834;0.99365 = 0.0255
1.02365 = 1377.4083;0.98365 = 0.0006


在这里插入图片描述



一、前言

上期教程《【Go|第5期】Lorca无法正常运行的解决方案》解决了 Chrome 安全机制导致无法正常运行 Lorca 程序的问题。小伙伴们可能又发现了另外一个问题,Lorca 窗口的副标题上 多了一行非常醒目的字:Chrome正受到自动测试软件的控制。虽然功能方面不受影响,但是这一行字却足以让你的用户感到困惑。

  • 示例一:
    在这里插入图片描述

  • 示例二:
    在这里插入图片描述

  • 示例三:
    在这里插入图片描述

二、分析问题

关于“Chrome正受到自动测试软件的控制”这个副标题的问题,一般是由于浏览器的自动化测试工具插件所引起的。这些工具可能会在浏览器窗口的标题栏中显示此消息,以指示当前的浏览器会话处于自动化测试模式下。

  • 第一点,Lorca 整个运行过程中,没有使用 Selenium 或其他类似的自动化测试框架进行 Web
    应用程序测试;
  • 第二点,Lorca 在启动的时候,添加了 禁用浏览器扩展(Extensions)功能 的命令行启动参数;
  • 第三点,旧版 Chrome 不会显示此类副标题,可能没有实现这一功能,而新版则增加了这个浏览器行为,旨在提醒用户当前浏览器正处于自动化测试模式下。

不确定未来最新版本会不会解决此问题,与其寄托未来,不如把握现在。既然是 自动化测试(automation 这个因素引起的,那么我们是否可以查找一下 Lorca 是否运行的时候有添加此类参数呢?

(1)打开 Lorca 库

  • main.go 的代码片段:
package main

import (
	"log"
	"net/url"

	"github.com/zserge/lorca"
)

func main() {
	// Create UI with basic HTML passed via data URI
	ui, err := lorca.New("data:text/html,"+url.PathEscape(`
	<html>
		<head><title>Hello</title></head>
		<body><h1>Hello, world!</h1></body>
	</html>
	`), "", 480, 320, "--remote-allow-origins=*")
	if err != nil {
		log.Fatal(err)
	}
	defer ui.Close()
	// Wait until UI window is closed
	<-ui.Done()
}

  • 截图说明:
    Ctrl + 鼠标左键,点选 lorca.New 中的 New, 打开 Lorca 库。

在这里插入图片描述

  • Lorca 库的 ui.go 代码片段
func New(url, dir string, width, height int, customArgs ...string) (UI, error) {
	if url == "" {
		url = "data:text/html,<html></html>"
	}
	tmpDir := ""
	if dir == "" {
		name, err := ioutil.TempDir("", "lorca")
		if err != nil {
			return nil, err
		}
		dir, tmpDir = name, name
	}
	args := append(defaultChromeArgs, fmt.Sprintf("--app=%s", url))
	args = append(args, fmt.Sprintf("--user-data-dir=%s", dir))
	args = append(args, fmt.Sprintf("--window-size=%d,%d", width, height))
	args = append(args, customArgs...)
	args = append(args, "--remote-debugging-port=0")

	chrome, err := newChromeWithArgs(ChromeExecutable(), args...)
	done := make(chan struct{})
	if err != nil {
		return nil, err
	}

	go func() {
		chrome.cmd.Wait()
		close(done)
	}()
	return &ui{chrome: chrome, done: done, tmpDir: tmpDir}, nil
}

(2)分析 Lorca 库中的 New 函数

1、chrome, err := newChromeWithArgs(ChromeExecutable(), args...) 这一行代码是创建一个 chrome 实例,其中 args 就是启动程序的命令行参数的切片(slice)。

在这里插入图片描述

命令行参数参数说明
--app=<url>用于指定要打开的应用程序或网页,其中 <url> 提供要打开的应用程序或网页的 URL。通过使用 -app= 参数,您可以在 Chromium 浏览器中直接打开指定的应用程序或网页,而无需显示浏览器的地址栏、标签栏和其他界面元素。
--user-data-dir=<path>用于指定用户数据目录的路径,其中 <path> 代表需要指定的路径。每个用户在 Chromium 浏览器中都有一个对应的用户数据目录,用于存储其个人配置、书签、扩展、历史记录等数据。通过使用 --user-data-dir= 参数,您可以自定义用户数据目录的路径。
--window-size=<w>,<h>用于设置浏览器窗口的尺寸,其中 <w> 代表宽度像素值,<h> 代表高度像素值。通过使用 --window-size= 参数,您可以指定浏览器窗口的宽度和高度。尺寸可以以像素(px)为单位进行指定。
-remote-debugging-port=0用于禁用远程调试端口Chromium 浏览器默认情况下会打开一个用于远程调试的端口,允许开发者通过远程连接来调试和控制浏览器的行为。该参数允许您指定要使用的特定端口号,但使用0会禁用远程调试端口。

这些参数也并非我们所要查找的参数,这里可以过了。

2、接着,我们顺藤摸瓜看一下 customArgs,如下:

var defaultChromeArgs = []string{
	"--disable-background-networking",
	"--disable-background-timer-throttling",
	"--disable-backgrounding-occluded-windows",
	"--disable-breakpad",
	"--disable-client-side-phishing-detection",
	"--disable-default-apps",
	"--disable-dev-shm-usage",
	"--disable-infobars",
	"--disable-extensions",
	"--disable-features=site-per-process",
	"--disable-hang-monitor",
	"--disable-ipc-flooding-protection",
	"--disable-popup-blocking",
	"--disable-prompt-on-repost",
	"--disable-renderer-backgrounding",
	"--disable-sync",
	"--disable-translate",
	"--disable-windows10-custom-titlebar",
	"--metrics-recording-only",
	"--no-first-run",
	"--no-default-browser-check",
	"--safebrowsing-disable-auto-update",
	"--enable-automation",
	"--password-store=basic",
	"--use-mock-keychain",
}

其中,--enable-automation 貌似与自动化测试automation)有关,这不就是我们想要找到参数吗?

命令行参数参数说明
–enable-automation用于启用自动化功能。通过启用自动化功能,您可以使用自动化工具(如 Selenium脚本控制 Chromium 浏览器的行为。这对于自动化测试、网络爬虫或其他自动化任务非常有用。

另外,在上述命令行中,我们也发现了--disable-infobars命令,用于启动 Chromium 网页浏览器并禁用副标题(也叫做信息栏)功能。该信息栏就是浏览器窗口顶部显示通知、警告或其他消息的小型信息栏,然而新版 chrome 目前已不再支持该命令了,这才是导致问题出现的真正原因。

三、解决问题

不希望看到"Chrome正受到自动测试软件的控制" 这个副标题,可以在启动 Chrome 时省略 --enable-automation 标志,或者将其设置为 --disable-automation。这里我们以省略 --enable-automation 做演示:
在这里插入图片描述

完整的 ui.go 代码,可以直接拿走,覆盖原来的代码即可使用,如下:

package lorca

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"reflect"
)

// UI interface allows talking to the HTML5 UI from Go.
type UI interface {
	Load(url string) error
	Bounds() (Bounds, error)
	SetBounds(Bounds) error
	Bind(name string, f interface{}) error
	Eval(js string) Value
	Done() <-chan struct{}
	Close() error
}

type ui struct {
	chrome *chrome
	done   chan struct{}
	tmpDir string
}

var defaultChromeArgs = []string{
	"--disable-background-networking",
	"--disable-background-timer-throttling",
	"--disable-backgrounding-occluded-windows",
	"--disable-breakpad",
	"--disable-client-side-phishing-detection",
	"--disable-default-apps",
	"--disable-dev-shm-usage",
	"--disable-infobars",
	"--disable-extensions",
	"--disable-features=site-per-process",
	"--disable-hang-monitor",
	"--disable-ipc-flooding-protection",
	"--disable-popup-blocking",
	"--disable-prompt-on-repost",
	"--disable-renderer-backgrounding",
	"--disable-sync",
	"--disable-translate",
	"--disable-windows10-custom-titlebar",
	"--metrics-recording-only",
	"--no-first-run",
	"--no-default-browser-check",
	"--safebrowsing-disable-auto-update",
	"--password-store=basic",
	"--use-mock-keychain",
}

// New returns a new HTML5 UI for the given URL, user profile directory, window
// size and other options passed to the browser engine. If URL is an empty
// string - a blank page is displayed. If user profile directory is an empty
// string - a temporary directory is created and it will be removed on
// ui.Close(). You might want to use "--headless" custom CLI argument to test
// your UI code.
func New(url, dir string, width, height int, customArgs ...string) (UI, error) {
	if url == "" {
		url = "data:text/html,<html></html>"
	}
	tmpDir := ""
	if dir == "" {
		name, err := ioutil.TempDir("", "lorca")
		if err != nil {
			return nil, err
		}
		dir, tmpDir = name, name
	}
	args := append(defaultChromeArgs, fmt.Sprintf("--app=%s", url))
	args = append(args, fmt.Sprintf("--user-data-dir=%s", dir))
	args = append(args, fmt.Sprintf("--window-size=%d,%d", width, height))
	args = append(args, customArgs...)
	args = append(args, "--remote-debugging-port=0")

	chrome, err := newChromeWithArgs(ChromeExecutable(), args...)
	done := make(chan struct{})
	if err != nil {
		return nil, err
	}

	go func() {
		chrome.cmd.Wait()
		close(done)
	}()
	return &ui{chrome: chrome, done: done, tmpDir: tmpDir}, nil
}

func (u *ui) Done() <-chan struct{} {
	return u.done
}

func (u *ui) Close() error {
	// ignore err, as the chrome process might be already dead, when user close the window.
	u.chrome.kill()
	<-u.done
	if u.tmpDir != "" {
		if err := os.RemoveAll(u.tmpDir); err != nil {
			return err
		}
	}
	return nil
}

func (u *ui) Load(url string) error { return u.chrome.load(url) }

func (u *ui) Bind(name string, f interface{}) error {
	v := reflect.ValueOf(f)
	// f must be a function
	if v.Kind() != reflect.Func {
		return errors.New("only functions can be bound")
	}
	// f must return either value and error or just error
	if n := v.Type().NumOut(); n > 2 {
		return errors.New("function may only return a value or a value+error")
	}

	return u.chrome.bind(name, func(raw []json.RawMessage) (interface{}, error) {
		if len(raw) != v.Type().NumIn() {
			return nil, errors.New("function arguments mismatch")
		}
		args := []reflect.Value{}
		for i := range raw {
			arg := reflect.New(v.Type().In(i))
			if err := json.Unmarshal(raw[i], arg.Interface()); err != nil {
				return nil, err
			}
			args = append(args, arg.Elem())
		}
		errorType := reflect.TypeOf((*error)(nil)).Elem()
		res := v.Call(args)
		switch len(res) {
		case 0:
			// No results from the function, just return nil
			return nil, nil
		case 1:
			// One result may be a value, or an error
			if res[0].Type().Implements(errorType) {
				if res[0].Interface() != nil {
					return nil, res[0].Interface().(error)
				}
				return nil, nil
			}
			return res[0].Interface(), nil
		case 2:
			// Two results: first one is value, second is error
			if !res[1].Type().Implements(errorType) {
				return nil, errors.New("second return value must be an error")
			}
			if res[1].Interface() == nil {
				return res[0].Interface(), nil
			}
			return res[0].Interface(), res[1].Interface().(error)
		default:
			return nil, errors.New("unexpected number of return values")
		}
	})
}

func (u *ui) Eval(js string) Value {
	v, err := u.chrome.eval(js)
	return value{err: err, raw: v}
}

func (u *ui) SetBounds(b Bounds) error {
	return u.chrome.setBounds(b)
}

func (u *ui) Bounds() (Bounds, error) {
	return u.chrome.bounds()
}

四、最终效果展示

  • 示例一:
    在这里插入图片描述

  • 示例二:
    在这里插入图片描述

  • 示例三:
    在这里插入图片描述


参考文章:


我的微信公众号 会飞的小猴子,等你来关注哦 ^ - ^


版权声明:本文为博主原创文章,如需转载,请给出:
原文链接:https://blog.csdn.net/qq_35844043/article/details/131612569

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Commas.KM

码路共同进步,感恩一路有您

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值