针对 Python 版 Selenium 4.2.0 进行说明
在编码方面与 Java 版有较大差异时,会特别指出说明。
不尝试列出所有配置,仅列出常用的一些重要配置。
如果不作任何设置,直接启动浏览器,显然浏览器将以默认配置启动。通常,默认配置可能不满足我们的需要,这就需要对浏览器进行一些设置。
【关于浏览器配置】
- 大多数配置需要在 WebDriver 启动浏览器前以“配置对象参数”的形式传入,并且一般在运行时不可修改;某些功能性相关的配置会在浏览器启动后进行,这通常通过 WebDriver 提供的设置方法实现。
- 少量配置是所有浏览器共享的,但是不同的浏览器配置项通常有差异,各大主流浏览器都拥有属于自己的配置项。
共享配置
一部分功能配置需要构建“配置(Option)”对象,并设置其“功能(Capability )”,然后在创建驱动对象时将 配置对象作为参数传入。通常,这部分功能将在驱动对象生命周期内固定不可修改。
另一部分功能配置是以方法的形式提供的,这说明能在运行时动态修改。
如果只需配置公共功能,则不需要构建一个具体的浏览器 Option,可以构建一个基类 ArgOptions
。
【Java Options 的构建】
就 Python 的类结构而言,所有
Options
类拥有公共基类ArgOptions
,可以构建基类对象进行配置。但对 Java 而言,这不太可行,Java 类结构表明,公共基类AbstractDriverOptions
是一个抽象类。
代理
1 | proxy_path = 'localhost:9085' |
一共分3步:
- 构造 Proxy 字典对象
- 为 Options 对象设置 Proxy Capability(传入 Proxy 字典对象)
- 构造驱动对象(传入 Options 对象)
【Proxy 网络路径格式】
Proxy 网络路径格式为
HOST:PORT
,最好不要加协议前缀,可能由于协议与对应端口错误导致代理不可用。比如,笔者使用 v2rayN 配置代理,本地 socks 对应 9084 端口,http 对应 9085 端口。因此,无论使用
localhost:9084
或localhost:9085
都是可以的。如果要加协议前缀,socks://localhost:9084
或http://localhost:9085
也都可以。但是,socks://localhost:9085
或http://localhost:9084
就出错了。【Proxy 字典对象】
Proxy 字典对象中,
httpProxy
和sslProxy
分别指定 http 和 https 协议的代理地址。一般都需要指定,比如不指定sslProxy
则 https 协议的页面请求就不会经过代理。
proxyType
使用手动设置,值为MANUAL
。它是必需的,否则将导致错误。(Java 版本中ProxyType
是个枚举值,可以看到所有支持的类型,以及它们的说明。)【Java 版】
1
2
3
4 Proxy proxy = new Proxy();
proxy.setHttpProxy("<HOST:PORT>");
proxy.setSslProxy("<HOST:PORT>");
options.setCapability("proxy", proxy);
页面加载策略
默认配置下,打开新页面时,Selenium 会等待所有资源下载完成。但对于有大量资源的页面不太友好,可以选择仅等到 DOM 就绪,或者不等待——不过要注意对测试稳定性的影响。
原理上说,Selenium 检测的是 document.readyState
属性,与加载策略对应关系如下:
策略 | 状态 | 备注 |
---|---|---|
normal | complete | 默认,等待所有资源下载完成 |
eager | interactive | DOM 就绪,如图片等其他资源可能仍在加载中 |
none | 任意值 | 不阻塞,不等待 |
1 | options = Options() |
【Java 版】
1
2
3 ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.setPageLoadStrategy(PageLoadStrategy.EAGER);
WebDriver driver = new ChromeDriver(chromeOptions);与 Python 差异不大,Java 的主要区别在于“封装”方面:
- 使用 set 方法设置加载策略;
- 策略候选值
PageLoadStrategy
是一个枚举类。
浏览器窗口
以上列举的都是启动前的配置,下面说说启动后的设置。
首先,就是对浏览器窗口的设置。Selenium 允许我们设置窗口位置、大小、最大化、最小化甚至是全屏显示等。
1 | driver.maximize_window() |
【Java 版】
主要还是封装程度更高。
1
2
3
4
5 driver.manage().window().maximize();
driver.manage().window().minimize();
driver.manage().window().fullscreen();
driver.manage().window().setPosition(x, y);
driver.manage().window().setSize(dimension);
注意:
- 某些浏览器可能不支持像素级的大小设置,设置大小后窗口大小可能与设置值有所差异。
- 窗口设置是在浏览器启动后,所以可以看到浏览器从“普通窗口”到设置状态的改变过程。这有别于某些浏览器的启动设置,比如 Chrome 添加启动参数
--start-maximized
则会在浏览器启动窗口显示时就已经最大化了——看不到状态改变过程。
超时时间
Selenium 允许设置一些超时时间,控制诸如脚本执行、页面加载、元素定位等行为。
脚本超时(Script Timeout),JS 脚本运行超时会被中断,默认 30000 毫秒(即 30 秒)。
页面加载超时(Page Load Timeout),页面加载等待超时会抛出 TimeoutException
中止,默认 30000 毫秒(即 30 秒)。
隐式等待超时(Implicit Wait Timeout),等待定位元素超时才报“找不到元素”,默认为 0(即不等待)。
1 | # 设置 |
【Java 版】
注意时间间隔精度的差异,Python 是秒级,Java 是毫秒级(
Duration
理论是纳秒级,但代码内部只取到了毫秒)。
1
2
3
4
5
6
7
8 // 设置
driver.manage().timeouts().scriptTimeout(duration);
driver.manage().timeouts().pageLoadTimeout(duration);
driver.manage().timeouts().implicitlyWait(duration);
// 读取
driver.manage().timeouts().getScriptTimeout();
driver.manage().timeouts().getPageLoadTimeout();
driver.manage().timeouts().getImplicitWaitTimeout();
Chrome配置
Chrome路径识别与自定义
没有配置 Chrome 路径的情况下,Selenium 尝试从 Chrome 的“默认安装位置”启动它。
【默认安装位置】
Windows 下可能是
C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
,这跟安装的浏览器位数有关。
如果自定义安装在了别处,默认查找将失败,除非配置了正确的路径。
1 | options = webdriver.ChromeOptions() |
【Java 版】
1
2
3 ChromeOptions options = new ChromeOptions();
options.setBinary("/path/to/chrome");
ChromeDriver driver = new ChromeDriver(options);
驱动文件路径识别与自定义
未配置 chromedriver 路径的情况下,Selenium 尝试在 PATH
环境变量下的路径查找。
找不到则报错,如果不想设置环境变量,可以在代码中指定 chromedriver 路径。正确的方法是将路径通过 Service 传入:
1 | service = Service(chromedriver_path) |
【过时的写】
当然,有一种更简洁的方法,就是在构造的时候直接传入路径,如:
webdriver.Chrome('/path/to/chromedriver')
。但是构造参数executable_path
已废弃,该方法不推荐使用。【Java 版】
Java 也应在 Service 中传入 chromedriver 路径,但是
ChromeDriverService
构造器参数太多又不像 Python 可以设置默认值,所以需要借助Builder
来构造:
1
2
3
4
5
6 import org.openqa.selenium.chrome.ChromeDriverService.Builder;
final Builder builder = new Builder();
builder.usingDriverExecutable(new File(chromedriver));
ChromeDriverService service = builder.build();
WebDrvier driver = new ChromeDriver(service, options);不过 Java 还有一个极简的方案,将系统属性
webdriver.chrome.driver
设置为 chromedriver 路径即可:
1 System.setProperty("webdriver.chrome.driver", "/path/to/chromedriver");
无头模式
Chrome 支持无头模式,即无界面运行,这可能在速度上有一些提升。
要么直接设置 Options 相关属性,要么设置启动参数,如下:
1 | # 方法一 |
【Java 版】
1
2
3
4 // 方法一
options.setHeadless(true);
// 方法二
options.addArguments("headless")
加载扩展程序
默认情况下,启动的 Chrome 是全新的,不带扩展程序的,如果想使用扩展程序需要编码加载。
要加载扩展程序,首先需要获得扩展程序文件,这可以通过网站 http://crxextractor.com/ 下载。
扩展程序文件后缀名为 .crx,其实它是一个压缩文件,将后缀名改为 .zip 即可解压。直接从扩展程序文件加载,或者从解压出来的文件夹加载都是可以的:
1 | # 加载 .crx |
【Java 版】
1
2
3
4 // 加载 .crx
options.addExtensions(new File("/path/to/extension.crx"));
// 加载解压的扩展程序文件夹
options.addArguments("load-extension=/path/to/extension-dir");
启动参数
Chrome 一般的配置参数使用 options.add_argument(arg)
进行设置。
【Java 版】
1 options.addArguments(arg);
基本设置
参数名 | 说明 | 示例 |
---|---|---|
args | 参数列表,可以将参数以数组形式一起传 | [‘start-maximized’, ‘user-data-dir=/tmp/temp_profile’] |
headless | 无头模式 | |
incognito | 无痕模式 | |
proxy-server | 代理 | proxy-server=localhost:9085 |
user-agent | 用户代理 | user-agent=Mozilla/5.0 HELL |
load-extension | 加载扩展程序(目录) | load-extension=/path/to/extension-dir |
start-maximized | 最大化 | |
window-size | 浏览器窗口大小 | window-size=1920,1080 |
auto-open-devtools-for-tabs | 开启浏览器时候是否打开开发者工具(F12) | |
host-resolver-rules | 域名解析映射 | host-resolver-rules=MAP www.googletagmanager.com 127.0.0.1,MAP connect.facebook.net |
代理
除了可使用通用设置代理(见“共享配置-代理”章节)外,Chrome 还支持特异设置,而且更加简单,通过 proxy-server
参数配置即可:
1 | driver_options.add_argument('proxy-server=http://localhost:9085') |
【与通用代理配置的差异】
试验后可以发现,Chrome 代理配置不能“自动探测协议”。换句话说,如果不指定协议前缀,那么默认是 http。
比如,笔者使用 v2rayN 配置代理,本地 socks 对应 9084 端口,http 对应 9085 端口。
如果带协议前缀,
socks://localhost:9084
或http://localhost:9085
都可以。但是,不带前缀只能用
localhost:9085
,不能用localhost:9084
。
用户数据目录(User Data Directory)
默认情况下,Chrome 每次启动都是全新的,这是因为 Selenium 每次都会自动创建一个新的“用户数据目录”。
我们可以自行创建一个用户数据目录,并指定 Chrome 使用它启动。
直接使用 Chrome 应用程序加上 user-data-dir
参数,就能在指定路径创建一个用户数据目录:
1 | chrome.exe --user-data-dir=/path/to/my-profile |
再在 Selenium 启动 Chrome 前配置好 user-data-dir
即可:
1 | options.add_argument(r'user-data-dir=/path/to/my-profile') |
这样,每次启动时,包括插件在内的绝大多数浏览器设置都将保留。
【默认路径】
用户数据目录默认路径为:
C:\Users\<USERNAME>\AppData\Local\Google\Chrome\User Data
,也可以使用 Chrome 打开chrome://version
页面查看。
域名解析映射
网站可能接入了很多“第三方功能”,诸如:谷歌分析、Facebook 分享等等。在测试过程中这些功能可能会影响响应时间,而测试暂时又不关注这方面的功能,则可以把它们的域名映射到 127.0.0.1
使其快速失败,以提高响应速度。
这需要配置 host-resolver-rules
参数,值是 MAP <FROM-DOMAIN> <TO>
的形式,多个值可以英文逗号分隔:
1 | options.add_argument('host-resolver-rules=MAP www.googletagmanager.com 127.0.0.1,MAP connect.facebook.net') |
实验选项
Chrome 提供了很多实验选项,可以使用 Options.add_experimental_option(key, value)
配置。
【Java 版】
1 options.setExperimentalOption(key, value);
关闭“受控”提示
Selenium 启动的 Chrome 默认情况下会显示类似“chrome 正受到自动测试软件的控制”的提示信息。如果想配置关闭,则不同 Chrome 版本设置选项不同。
1 | # 老版 Chrome |
【说明】
具体来说哪个版本是“老版”与“新版”的分界线呢?呃,这个没有具体研究,自己试验一下吧。
自动下载
想要 Chrome 自动下载文件到指定目录,而不弹出文件对话框,需要设置 prefs
(字典类型)中的以下选项:
选项 | 值 |
---|---|
download.prompt_for_download | False |
profile.default_content_settings.popups | 0 |
download.default_directory | 下载目录路径 |
download.directory_upgrade | True |
【说明】
可能不同作者关于自动下载的配置选项会略有出入,但核心都是那么几个。上面 4 个是笔者试验得出“必须”选项。
【文件下载扩展说明】
如果不设置自动下载,在有界面场景下会涉及文件对话框的操作(这超出了 Selenium 的控制范畴)——一般需要编写 Autoit 脚本等方式进行操作。
显示,从复杂性上说,自动下载是个“更好的选择”,而且是无头模式下的“唯一选择”。
但是,由于下载进度难以监控等问题,自动下载也并非一个推荐的做法。
针对文件下载场景,一个“最佳实践”是,取得下载链接,使用其他 http 工具库下载——如果真的需要下载文件的话。
已废弃部分
应尽量使用 Chrome Options 进行配置,Desired Capability 已经废弃,不要试图构造它并把它传入 Chrome Driver 来配置。
Firefox配置
与 Chrome 类似,除了参数/选项名称及功能的差异外,其他设置基本上就是把 Chrome 相关的对应物的类名改成 Firefox 对等的类名即可。
Firefox及驱动路径识别与自定义
与 Chrome 类似,Firefox 也使用 options.binary_location
自定义可执行文件位置,而在 Service
构造器中传入驱动路径。(详细请参考 Chrome 相关章节)
【Java 版】
Firefox 存在特异的方法设置可执行文件路径:
1 GeckoDriverService.Builder.usingFirefoxBinary(FirefoxBinary)驱动路径使用系统属性
webdriver.gecko.driver
设置。【驱动兼容性】
Chrome 驱动要求比较严格,一般需要对应版本的驱动才能正常启动该版本的 Chrome。但是 Firefox 使用的 GeckoDriver 没有这么严格的版本对应关系,一般而言,最新的驱动都可以向下兼容老版的 Firefox。
无头模式
Firefox 支持 options.headless = True
设置无头模式启动,但是不支持 options.add_argument('headless')
。
高级配置选项
与 Chrome 不同,Firefox 的配置选项都可以访问 about:config
页面查看。
因此,对于 Firefox 测试来说,不需要明确知道某个功能需要配置哪些选项。反过来,我们可以在“设置”页面配置好某个功能后,查看哪些选项修改了(自定义选项会以粗体显示),测试脚本里就设置这些选项即可。
加载扩展程序
1 | driver.install_addon('/path/to/firebug.xpi') |
【扩展程序文件下载】
在非 Firefox 浏览器——比如 Chrome——中打开扩展程序页面,通过“下载链接”可以直接下载 .xpi 文件。
【过时的写法】
通过
FirefoxProfile
也可以加载扩展程序,虽然有效,但是在 Python 版本中FirefoxProfile
已废弃,使用将会有警告。
1
2 # 从一个 URL、.xpi 文件路径、扩展所在目录等安装扩展程序
profile.add_extension(extension)
配置文件夹
Firefox 的“配置文件夹”跟 Chrome 的“用户数据目录”是等价的概念。
在无 Firefox 进程的情况下,执行 firefox.exe -ProfileManager
命令会弹出“用户配置文件管理”对话框,可以选择目录创建一个新的配置文件夹。
1 | options.set_capability("moz:firefoxOptions", { |
【信息查看】
访问
about:support
打开“排障信息”页面,“配置文件夹”属性查看。【过时的写法】
一个过时的写法是,构造
FirefoxProfile
,如下:
1
2 profile = webdriver.FirefoxProfile('/path/to/profile-dir')
driver = webdriver.Firefox(options=options, service=service, firefox_profile=profile)Python 版本中,
FirefoxProfile
已废弃,虽然依然可用,但会输出警告。【Java 版】
Java 版本中,
FirefoxProfile
并未废弃,仍然可用:
1
2 FirefoxProfile profile = new FirefoxProfile(new File("/path/to/profile-dir"));
options.setProfile(profile);
代理
1 | # 使用手动配置代码 |
【探索代理设置】
Firefox 中,通过“设置-常规-网络设置”,可以打开“连接设置”对话框。通过该配置,可以对比各种代理设置对应的选项——大多是
network.proxy
开头的。【“共享代理设置”不“共享”】
注意,“连接设置”对话框有个“也将此代理用于 HTTPS”的复选框,勾选后会将 HTTP 代理“快捷共享”给 HTTPS 代理——它对应的选项名为
network.proxy.share_proxy_settings
,选中值为true
。有的小伙伴可能会想只设置 HTTP 代理,然后“共享”给 HTTPS 即可。现实情况是,这不可能!“共享”选项行为上是一个 GUI 特性——界面勾选时会联动设置 HTTPS 代理,最后,HTTPS 代理相关选项也会被设置——而脚本如果只设置“共享”不设置 HTTPS 代理,最终 HTTPS 代理还是“空”的。
如果不设置 HTTPS 代理,网站又是 https 协议,那就 BBQ 了。
所以,即使设置了 HTTP 代理,也还是要设置 HTTPS 代理,或者只设置 SOCKS 代理。
【Java 版本】
对于 Python 版本而言,Firefox 的配置废弃了
FirefoxProfile
,而直接在Options
上进行。但是,Java 版本并未废弃
FirefoxProfile
,也没有在Options
上添加setPreference()
方法。所以,还是要使用FirefoxProfile
进行设置:
1
2
3
4 FirefoxProfile profile = new FirefoxProfile();
profile.setPreference("network.proxy.socks", "localhost");
profile.setPreference("network.proxy.socks_port", 9084);
options.setProfile(profile);
自动下载
1 | options.set_preference('browser.download.useDownloadDir', True) |
扩展讨论
第三方库管理驱动
如果测试环境的浏览器会自动升级,那么,为了不频繁的替换驱动文件、修改配置或代码,一个推荐的做法是使用第三方库来管理驱动——WebDriver Manager 就是个这样的库。
1 | from webdriver_manager.chrome import ChromeDriverManager |
大致的原理是:
- 调用
install()
时,会获取相应浏览器的版本,并据此下载对应的驱动文件。 - 下载的驱动文件会缓存在
C:\Users\<username>\.wdm
文件夹中,后续将不必下载直接从缓存中获取。 drivers.json
文件记录了驱动版本以及下载日期。
【Java 版】
WebDriver Manager 也有对应的 Java 版本,Maven 依赖如下:
1
2
3
4
5 <dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>${webdrivermanager.version}</version>
</dependency>只需要在创建
WebDriver
对象前调用如下代码,就不用关注驱动问题了:
1
2
3 ChromeDriverManager.getInstance().setup();
// 或者
WebDriverManager.chromedriver().setup();原理上与 Python 版大致相同,但略微有差异:
- 调用
setup()
时,同样下载驱动文件。不同的是,Java 可以通过系统变量注入驱动,Python 没有类似机制还是需要将驱动传入Service
。- 驱动缓存文件夹为:
C:\Users\<username>\.cache\selenium
。- 注意浏览器版本探测结果以及驱动探测结果都是有时效的,浏览器时效为 1 小时,驱动时效为 24 小时——过期将重新探测。对应的版本及时效信息记录在
resolution.properties
中。
参考
List of Chromium Command Line Switches:列出了 Chromium 1400+ 的命令行参数
ChromeDriver - Capabilities & ChromeOptions
MozillaZine - About:config entries:按功能列出了 Firefox 的配置选项
Using Firefox Extensions with Selenium in Python