【Python】高效的Web自动化测试利器—Python+Playwright快速上手自动化实战指南

文章目录

前言

本教程旨在引导读者从零开始,逐步掌握使用Python和Playwright进行Web自动化测试的技能。无论你是初涉自动化测试的开发者,还是希望提升现有测试框架效能的资深测试工程师,本教程都将为你提供丰富的实战案例、详细的步骤解析以及最佳实践分享。

一.playwright是什么

Playwright是微软在2020 年初开源自动化测试工具,功能和 selenium 类似,都可以驱动浏览器进行各种自动化操作。

  • 支持主流浏览器,如Chrome、Firefox、Safari 等,同时支持以无头模式、有头模式运行,并提供了同步、异步的 API,可以结合 主流测试框架使用,并且支持浏览器端的自动化脚本录制等功能。

特点:

  • 跨浏览器:Playwright 支持所有现代渲染引擎,包括Chromium、WebKit 和 Firefox;
  • 跨平台:在 Windows、Linux 和 MacOS 上进行本地或 CI、无头或有头测试;
  • 跨语言:在 TypeScript、JavaScript、Python、.NET、Java 中使用Playwright API;
  • 测试移动网络:适用于 Android 和 Mobile Safari 的 Google Chrome 原生移动仿真。相同的渲染引擎适用于您的桌面和云端。
  • 官网:https://playwright.dev/
    官方文档

其他鼎鼎大名的selenium、Pyppeteer、DrissionPage等

推荐原因:

  1. 运行 playwright codegen命令 可自动自动生成代码,降低编写爬虫代码的门槛和难度,不用自己逐个去分析页面代码结构
  2. playwright微软是从2020年开始创建的项目,更新稳定且频率不低,可长期使用一个库或框架
  3. 代码结构清晰,功能齐全,门槛低
  4. 支持多个语言版本:python、Node.js、Java、.net,原生支持同步异步两种方式**
  5. 自动等待 pw在做某个操作之前需要有一个前提条件成立才进行,系统会自动等待检查通过,直到超时
    比如我要系统自动点击某个元素,那么playwright会自动:
    1. 等待指定选择器的元素出现在 DOM 中 (不用自己去写轮询等待了)
    2. 等待它显示出来,即不为空,不为display:none,不为visibility:hidden (这个太人性化了,不用去判断元素是否隐藏)
    3. 等待它停止移动,例如,直到 css 转换完成
    4. 将元素滚动到视图中 (这个太人性化了,不用自己去滚动了)
    5. 等待它在动作点接收指针事件,例如,等待直到元素变得不被其他元素遮挡
    如果元素检测到上述任何场景,则重试

二.python引入playwright

1.安装

  1. 安装 playwright-python 依赖库 (需要注意的是,playwright库需要依赖 Python3.7+以上)

    pip install playwright
    
    • 官网推荐pip install pytest-playwright来安装,但没必要,会安装playwright及其他一堆测试所用的库,如果只是使用playwright,那么就没必要这样去安装。
  2. 自动下载使用的浏览器

    playwright install
    
    • 执行命令以后,会自动下载chromium、firefox以及webkit三种浏览器,存放文件夹路径为(windows环境):

      c:\Users\YOURUSERNAME\AppData\Local\ms-playwright\
      
      • 以上三种浏览器分别对应三种不同内核的浏览器,在爬虫过程中可以自定义选择任意一种浏览器

2.playwright命令行参数

想查看Playwright支持的功能, 可在命令行输入:

playwright help
Usage: index [options] [command]

Options:
  -V, --version                          output the version number
  -b, --browser <browserType>            browser to use, one of cr, chromium, ff, firefox, wk,
                                         webkit (default: "chromium")
  --color-scheme <scheme>                emulate preferred color scheme, "light" or "dark"
  --device <deviceName>                  emulate device, for example  "iPhone 11"
  --geolocation <coordinates>            specify geolocation coordinates, for example
                                         "37.819722,-122.478611"
  --lang <language>                      specify language / locale, for example "en-GB"
  #设置代理
  --proxy-server <proxy>                 specify proxy server, for example "http://myproxy:3128" or
                                         "socks5://myproxy:8080"
  --timezone <time zone>                 time zone to emulate, for example "Europe/Rome"
  --timeout <timeout>                    timeout for Playwright actions in milliseconds (default:
                                         "10000")
   #设置user-agent
  --user-agent <ua string>               specify user agent string
   #设置浏览器打开窗口的大小
  --viewport-size <size>                 specify browser viewport size in pixels, for example "1280,  
                                         720"
  -h, --help                             display help for command

Commands:
  open [url]                             open page in browser specified via -b, --browser
  cr [url]                               open page in Chromium
  ff [url]                               open page in Firefox
  wk [url]                               open page in WebKit
  codegen [options] [url]                open page and generate code for user actions
  screenshot [options] <url> <filename>  capture a page screenshot
  pdf [options] <url> <filename>         save page as pdf
  install                                Ensure browsers necessary for this version of Playwright
                                         are installed
  help [command]                         display help for command

3.playwright codegen自动生成代码

在命令行输入下面代码会自动弹出一个浏览器和一个代码编辑器 在该浏览器上每一步操作都会自动生成到代码编辑器上,可复制使用

playwright codegen https://www.baidu.com/ -o script.py

查看录制脚本的命令说明

playwright codegen --help
Usage: index codegen [options] [url]

open page and generate code for user actions

Options:
  #生成自动化脚本路径
  -o, --output <file name>  saves the generated script to a file
  # –target 脚本语言,包含 JS 和 Python,分别对应值为:python 和 javascript
  --target <language>       language to use, one of javascript, python, python-async, csharp (default: "python")
  #帮助文档
  -h, --help                display help for command

Examples:

  $ codegen
  $ codegen --target=python
  #指定浏览器驱动
  $ -b webkit codegen https://example.com
  1. 如要在baidu.com搜索,用chromium驱动,将结果保存为mikezhou.py的python文件。

    playwright codegen --target python -o 'mikezhou.py' -b chromium https://www.baidu.com
    

    命令行输入后会自动打开浏览器,然后可以看见在浏览器上的一举一动都会被自动翻译成代码,如下所示:
    在这里插入图片描述
    最后,自动化脚本会自动生成,保存到文件中mikezhou.py, 且上述所有的人工操作,都会被自动转化成代码:

from playwright import sync_playwright

def run(playwright):
    browser = playwright.chromium.launch(headless=False)
    context = browser.newContext()

    # Open new page
    page = context.newPage()

    # Go to https://www.baidu.com/
    page.goto("https://www.baidu.com/")

    # Click input[name="wd"]
    page.click("input[name=\"wd\"]")

    # Fill input[name="wd"]
    page.fill("input[name=\"wd\"]", "禾目大")

    # Press CapsLock
    page.press("input[name=\"wd\"]", "CapsLock")

    # Fill input[name="wd"]
    page.fill("input[name=\"wd\"]", "自动化测试实战宝典 ")

    # Press Enter
    page.press("input[name=\"wd\"]", "Enter")
    # assert page.url() == "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E5%AE%9E%E6%88%98%E5%AE%9D%E5%85%B8%20&fenlei=256&rsv_pq=af40e9aa00012d5a&rsv_t=c659gpz2%2Fjri1SAoIXdT9gP%2BmrqufXzRtMSSAL0n0fv7GSoLF5vaiNVPA3U&rqlang=cn&rsv_enter=1&rsv_dl=tb&rsv_sug3=38&rsv_sug1=22&rsv_sug7=100&rsv_sug2=0&rsv_btype=i&inputT=8034&rsv_sug4=9153"

    # Close page
    page.close()

    # ---------------------
    context.close()
    browser.close()

with sync_playwright() as playwright:
    run(playwright)

4.Chrome和Chromium有什么关系?

可看做同一个项目下的两个分支,chromium是测试开源版,所有的功能都会先在其身上测试,确定稳定运行后再移植到chrome上,而chrome是Google正式商业版浏览器,两者由Google官方和chromium社区进行维护

三.基本概念

在这里插入图片描述

1. 无头浏览器(Headless Browser)

HeadlessBrowser 俗称的无头浏览器, 实际上就是没有图形界面的浏览器, 因为省去了视觉渲染的工作, 性能和开销有较大优化, 粗略估计, 原本只能启动十个浏览器的内存, 使用 Headless 模式可以至少启动三倍的数量

无头浏览器应用场景

  • 无浏览器 UI,运行速度较快,常用于自动化运行
  • 有浏览器 UI,常用于调试代码

Playwright 支持以无头模式(Headless Browser )执行自动化测试,这样就不会实际打开可见的浏览器窗口。无头模式对于持续集成(CI)、后台执行测试或在没有图形界面的服务器环境中运行测试非常有用

在使用Playwright的无头浏览器模式(‌headless=True)‌时遇到找不到元素的问题

  1. 可能是网站反爬虫机制或User-agent参数问题导致的。‌

    • 常见的反爬虫手段是通过检测当前user-agent是否为真实浏览器来区分当前请求是否来自真实用户。爬虫使用的常见user-agent类型为:
      • user-agent为空。没有设置user-agent。
      • user-agent中包含特殊字符。如:python,java,bot,spider,headless等。

而使用Playwright的Chrome无头浏览器访问网站时,user-agent中会自动添加Headless字段。当网站检测到user-agent包含Headless时判定为非真实请求时,可能会返回空页面,所以导致无头浏览器找不到元素。

  • 用浏览器到https://www.useragentstring.com/index.php 查看当前浏览器使用的User Agent String,在Playwright配置中设置自定义的User-agent
    #通过args设置
    browser = playwright.chromium.launch(headless=True, args=['--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'])
    #通过上下文设置
    context = browser.new_context(no_viewport=True,user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36")
    

2.同步和异步模式操作playwright

2.1.同步(Sync)模式

在同步模式下,代码按照从上到下的顺序执行。每个操作都会阻塞直到完成,然后再执行下一行代码。这意味着在等待某个操作(如页面加载)完成之前,程序不会继续往下执行。

  • 关键字:sync_playwright
  • 对于初学者或习惯于同步编程模型的开发者来说,同步模式可能更加直观和易于理解,因为它遵循了传统的线性编程逻辑。
# 导入Playwright类和sync_palywright 同步类
from playwright.sync_api import sync_playwright

# -------------------------写法1
with sync_playwright() as playwright:
    browser = playwright.chromium.launch(headless=False)  # 启动 chromium 浏览器
    context = browser.new_context()  # 打开一个上下文
    page = context.new_page()  # 打开一个标签页
    page.goto("https://www.baidu.com")  # 打开百度地址
    print(page.title())  # 打印当前页面title
    context.close()
    browser.close()  # 关闭浏览器对象

# -------------------------写法2
# 如果不习惯with语句,也可以用start() 和stop() 的方式:
def main(playwright):
    browser = playwright.chromium.launch(headless=False)
    context = browser.new_context()
    page = context.new_page()
    page.goto("https://www.baidu.com/")
    print(page.title())
    context.close()
    browser.close()

with sync_playwright() as playwright:
    main(playwright)
同步方式代码模板
from playwright.sync_api import Playwright, sync_playwright, expect

def main(playwright: Playwright) -> None:
    """
    这是一个名为main的函数定义,它接受一个名为playwright的参数,该参数被类型注解为Playwright
    (这表明playwright应该是一个Playwright实例,但注意Python本身不强制类型注解,这是为了代码可读性和工具支持)。
    函数没有返回值(-> None表示返回类型为None)。
    """
    browser = playwright.chromium.launch(headless=False)
    context = browser.new_context()
    page = context.new_page()

    ###################这里将是操作页面的代码块##################
	#**完整的playwright流程**
	#1. 创建browser(浏览器实例)
	#2. 创建context(共cookie、session)
	#3. 创建page(具体选项卡页面),然后用页面去模拟操作
	#4. 完成所有的操作后关闭之前创建的三种对象。
    pass
    #############################################################    

    page.close()
    context.close()
    browser.close()

with sync_playwright() as playwright:
    main(playwright)

2.2.异步(Async)模式

异步模式利用Python的asyncio库允许非阻塞的并发操作。可以在等待某个耗时操作(如网络请求)的同时,程序可以继续执行其他任务。你需要使用await关键字来等待异步操作的结果

  • 关键字为:async_playwright,异步操作可结合asyncio同时进行三个浏览器操作。

  • 效率: 异步模式能显著提高脚本的执行效率,特别是在进行大量网络请求或需要同时管理多个页面/浏览器实例的场景下。因为它能够更好地利用系统资源,减少闲置时间。

    • 比我想爬取300章小说,如果用单线程去爬,那么时间是线性的,爬取每一章节所使用的时间积累起来就是最终所用的总时间;如果用多线程,比如说同时用10个线程去爬,那么理论上总时间就是单线程所用时间的1/10
      相反,如果我只是想获取某一个页面的内容,那么直接单线程就完事
  • 复杂度: 相比同步模式,异步编程模型引入了额外的概念,如协程(coroutine)、事件循环(event loop)等,这可能会增加代码的复杂度,尤其是对于不熟悉异步编程的开发者而言。

import asyncio
from playwright.async_api import Playwright, async_playwright

# 写法1
with async_playwright() as playwright:
    browser = await  playwright.chromium.launch(headless=False)  # 启动 chromium 浏览器
    context = await  browser.new_context()  # 打开一个上下文
    page = await context.new_page()  # 打开一个标签页
    await  page.goto("https://www.baidu.com")  # 打开百度地址
    print(await  page.title())  # 打印当前页面title
    await context.close()
    await browser.close()


# 写法2 如果不习惯with语句,也可以用start() 和stop() 的方式:
async def main():
    async with async_playwright() as playwright:
        browser = await playwright.chromium.launch(headless=False)
        context = await browser.new_context()
        page = await context.new_page()
        await page.goto("https://www.baidu.com/")
        print(await page.title())
        await context.close()
        await browser.close()
asyncio.run(main())

例如:

import asyncio
from playwright import async_playwright

# 异步执行
async def main():
    async with async_playwright() as p:
        for browser_type in [p.chromium, p.firefox, p.webkit]:
            # 指定为有头模式,方便查看
            browser = await browser_type.launch(headless=False)
            page = await browser.new_page()

            await page.goto('http://baidu.com')

            # 执行一次搜索操作
            await page.fill("input[name=\"wd\"]", "自动化测试实战宝典")
            await page.press("input[name=\"wd\"]", "Enter")

            # 等待页面加载完全
            await page.wait_for_selector("text=搜索工具")

            # 截图
            await page.screenshot(path=f'test-{browser_type.name}.png')
            await browser.close()


asyncio.get_event_loop().run_until_complete(main())
异步方式代码模板
import asyncio
from playwright.async_api import Playwright, async_playwright, expect


async def run(playwright: Playwright) -> None:
    browser = await playwright.chromium.launch(headless=False)
    context = await browser.new_context()
    page = await context.new_page()

    #########################操作页面的代码块#############
    #**完整的playwright流程**
	#1. 创建browser(浏览器实例)
	#2. 创建context(共cookie、session)
	#3. 创建page(具体选项卡页面),然后用页面去模拟操作
	#4. 完成所有的操作后关闭之前创建的三种对象。
    pass
    ####################################################
    await page.close()
    await context.close()
    await browser.close()


async def main() -> None:
    async with async_playwright() as playwright:
        await run(playwright)


asyncio.run(main())

3.Browser(浏览器驱动)

  • 对应一个浏览器实例(Chromium、Firefox或WebKit) 支持多种浏览器:Chromium(chrome、edge)、Firefox、WebKit(Safari),一般每一种浏览器只需要创建一个 browser 实例。示例:
#launch()方法是Playwright中用于启动浏览器的函数。它接受一个可选参数,该参数可以是一个字典,用于配置浏览器的选项。
#1.创建浏览器:(这里创建的是谷歌浏览器)
browser = playwright.chromium.launch(headless=False) # headless=False 是有头模式,也就是代码运行时候,需要浏览器页面
#(创建的是火狐浏览器)
browser = playwright.firefox.launch()



#关闭浏览器
browser.close()

browser对象常用配置项

1.是否无头模式(即是否隐藏浏览器界面): headless = False # 显示界面,为True时隐藏界面

2.打开时最大化窗口:args = ['--start-maximized'] # 还要配合context中设置 no_viewport = True

3.网络代理:
proxy = {
"server": "http://127.0.0.1:8080", # 代理服务器的地址
"bypass": "*.http://bing.com", # 不使用代理的域名
"username": "Mike", # 代理服务器的用户名
"password": "123456" # 代理服务器的密码
}

4.指定下载保存路径:downloads_path = r"d:\"

5.viewport: 字典,用于指定浏览器窗口的大小和位置。例如:{'width': 800, 'height': 600}6.slow_mo: 浮点数,默认为0。如果设置为大于0的值,则会增加浏览器操作的延迟时间(单位为毫秒)。
7.ignore_https_errors: 默认为False。如果设置为True,则在访问HTTPS网站时不会检查证书错误。
8.args: 列表,用于传递给浏览器进程的命令行参数。例如:['--disable-gpu']

4.Context(浏览器上下文)

一个浏览器实例下可以有多个context,将浏览器分割成不同的上下文,以实现会话的隔离,如需要不同用户登录同一个网页,不需要创建多个浏览器实例,只需要创建多个context即可

  • 可以理解为轻量级的浏览器实例.如需要不同用户登录同一个网页,不需要创建多个浏览器实例,只需要创建多个context即可。

浏览器上下文,相当于一个全新的浏览器配置文件,提供了完全的测试隔离,并且零开销。创建一个新的浏览器上下文只需要几毫秒,每个上下文都有自己的Cookie、浏览器存储和浏览历史记录。浏览器上下文允许同时打开多个页面并与之交互,每个页面都有自己单独的状态,一个 BrowserContext 可以包含多个 Page

#new_context()方法是Playwright库中用于创建一个新的浏览器上下文的函数。它接受一个可选参数,该参数可以是一个字典,用于配置浏览器上下文的选项
#1.创建浏览器上下文
context = browser.new_context()
#2.关闭
context.close()

Context相关常用配置项

1.no_viewport=True:最大化窗口(与browser的args联合使用) 
2.java_script_enabled=False:  禁用javascript:
3.viewport: 字典,用于指定浏览器窗口的大小和位置。例如:{'width': 800, 'height': 600}4.忽略https错误: ignore_https_errors=True
5.user_agent: 字符串,默认为当前浏览器的用户代理字符串。如果设置为其他值,则会使用指定的用户代理字符串。
6.accept_downloads: 布尔值,默认为False。如果设置为True,则会在下载文件时自动接受下载对话框。
7.record_har: 字典,用于录制HTTP请求和响应数据。例如:{'path': '/tmp/har.har'}8.其他配置
device_scale_factor: 浮点数,指定设备缩放比例,例如 1.5。如果不指定,则使用默认的设备缩放比例。
is_mobile: 布尔值,指定是否模拟移动设备,默认为 False。
has_touch: 布尔值,指定是否支持触摸事件,默认为 False。
bypass_csp: 布尔值,指定是否绕过内容安全策略,默认为 False。
locale: 字符串,指定浏览器的语言和地区,例如 “en-US” 或 “zh-CN”。如果不指定,则使用默认的语言和地区。
timezone_id: 字符串,指定浏览器的时区,例如 “Asia/Shanghai” 或 “America/New_York”。如果不指定,则使用默认的时区。
geolocation: 字典,指定浏览器的地理位置,包括 latitude(纬度),longitude(经度)和 accuracy(精度),例如 {“latitude”: 31.2304, “longitude”: 121.4737, “accuracy”: 10}。如果不指定,则使用默认的地理位置。
permissions: 列表,指定浏览器的权限,例如 [“geolocation”, “notifications”, “camera”]。如果不指定,则使用默认的权限。
extra_http_headers: 字典,指定浏览器的额外 HTTP 头部,例如 {“x-foo”: “bar”}。如果不指定,则使用默认的 HTTP 头部。
offline: 布尔值,指定是否模拟离线状态,默认为 False。
http_credentials: 字典,指定浏览器的 HTTP 认证,包括 username(用户名)和 password(密码),例如 {“username”: “admin”, “password”:123456}。如果不指定,则使用默认的 HTTP 认证。
color_scheme: 字符串,指定浏览器的配色方案,可以是 “dark” 或 “light”。如果不指定,则使用默认的配色方案。
record_video: 字典,指定是否录制浏览器的视频,包括 dir(视频保存的目录)和 size(视频的宽度和高度),例如 {“dir”: “videos/, “size”: {“width”: 800, “height”: 600}}。如果不指定,则不录制视频。
proxy: 字典,指定代理设置,包括 server(代理服务器地址),bypass(要绕过代理的域名列表),username(代理用户名),password(代理密码)

context常用方法

context.pages :获取context所有page对象
context.new_page():生成一个新的page对象
context.close():关闭context
context.add_cookies():将cookie添加到此浏览器上下文中。此上下文中的所有页面都将安装这些cookie。
  只能传入列表  List[{name: str, value: str, url: Union[str, None], domain: Union[str, None], path: Union[str, None], expires: Union[float, None]

context.clear_cookies():清除context的cookie
context.grant_permissions():授予浏览器上下文的指定权限,具体见api
context.clear_permissions():清除授权

5.Page页面(浏览器标签页)

一个context下可以有多个page,一个page就代表一个浏览器的标签页或弹出窗口,用于进行页面操作。这个也是我们主要操作的对象。后续打开网页 、定位元素、页面操作都是基于page

#1.创建一个新的浏览器页面
page = context.new_page()
#2.打开一个网页
page.page.goto(url , **kwargs)
	#默认是在当前tab打开 page.goto(url)    如果你想要在同一个上下文中打开多个页面,重新创建page即可
	# url就是网址,需要包含访问协议,比如https://www.bing.com
	# **kwargs包括:
	# timeout = 10000  # 可选项,单位ms,超时时间,默认30秒,设为0则永不超时
	# wait_until = 'load'  # 可选项,等待页面状态符合指定值,默认为load,具体解释参加下方内容
#3.关闭
page.close()

#4.获取当前页面的URL
page.url
#5.在页面上执行JavaScript代码
page.evaluate('() => document.title')
#6.截取页面的屏幕截图
page.screenshot(path='screenshot.png')
async def new_page(
    self,
    viewport: ViewportSize = None,#为每个页面设置一致的窗口。默认为1280x720窗口
    screen: ViewportSize = None, # 通过“window.screen”模拟网页内可用的一致窗口屏幕大小 ,只能在viewport设置之后使用  
    noViewport: bool = None,# 不强制固定窗口,允许在标题模式下调整窗口大小
    ignoreHTTPSErrors: bool = None,
    javaScriptEnabled: bool = None, #禁用 javaScript
    bypassCSP: bool = None,
    userAgent: str = None, # 设置代理用于上下文
    locale: str = None, #指定用户区域,设置将影响Accept-Language'请求标头值以及数字和日期格式规则
    timezoneId: str = None, #设置时区
    geolocation: Geolocation = None,
    permissions: List[str] = None,
    extraHTTPHeaders: Dict[str, str] = None,
    offline: bool = None,
    httpCredentials: HttpCredentials = None,
    deviceScaleFactor: float = None,
    isMobile: bool = None, #设备相关,不用管
    hasTouch: bool = None,
    colorScheme: ColorScheme = None, #Union["dark", "light", "no-preference", "null", None]
    #设置颜色主题
    forcedColors: ForcedColors = None, #Union["active", "none", "null", None]
    
    reducedMotion: ReducedMotion = None,
    acceptDownloads: bool = None,
    defaultBrowserType: str = None,
    proxy: ProxySettings = None, #设置代理
    recordHarPath: Union[Path, str] = None,
    recordHarOmitContent: bool = None,
    recordVideoDir: Union[Path, str] = None,
    recordVideoSize: ViewportSize = None,
    storageState: Union[StorageState, str, Path] = None,
    baseURL: str = None,
    strictSelectors: bool = None,
    serviceWorkers: ServiceWorkersPolicy = None,
    recordHarUrlFilter: Union[Pattern[str], str] = None,
    recordHarMode: HarMode = None,
    recordHarContent: HarContentPolicy = None,
) -> Page:

四.页面元素定位

1.locator选择器

locator()方法支持所有的CSS选择器`,包括:

  • 基本选择器:如 div, span, .my-class, #my-id 等。
  • 属性选择器:如 [href], [class=“my-class”], [data-my-attr=“value”] 等。
  • 伪类选择器:如 :hover, :focus, :first-child, :last-of-type 等。
  • 结合选择器:如 div.my-class, div, span, div > p, div + p 等。
  1. 操作元素有两种方式

    1. 先定位元素再操作元素
    # 先定位再操作
    page.locator('#kw').fill("上海悠悠")
    page.locator('#su').click()
    
    1. 直接在操作元素的时候定位元素
      如:调用fill 和 click 方法,传入Selector选择器**
    page.fill('#kw', "欧阳博客")
    page.click('#su')
    
  2. locator()方法可以根据元素的CSS选择器来查找。您可以使用各种CSS选择器,包括但不限于:

    标签名:例如 page.locator('button')
    类名:例如 page.locator('.my-class')
    ID:例如 page.locator('#my-id')
    属性:例如 page.locator('[data-testid="my-test-id"]')
    文本内容:例如 page.locator(':text("My Text")')
    
    • 使用 locator 定位元素,不管元素存不存在,都会返回一个locator 对象,可以用到count() 方法统计元素的个数,如果元素个数是 0, 那么元素就不存在
    • locator 是定位当前页面上的元素,不会自动等待,如果用click等方法结合使用,会自动去等待元素处于可点击状态。

1.文本选择器

文本选择器是一个非常实用的定位方式,根据页面上看到的text文本就可以定位了 playwright 封装了text文本定位的方式,也可以支持2种文本定位方式

  • page.click('text=登录') 没有加引号(单引号或者双引号),模糊匹配,对大小写不敏感
  • page.click('text="登录"') 有引号,精确匹配,对大小写敏感
	text()查找第一个文本等于...的元素
	has_text():筛选包含指定文本的元素,匹配元素内或子元素中的文本内容。
	has_not_text():筛选不包含指定文本的元素。

比如:<article><div >Playwright</div></article>.

page.locator(':has_text("Playwright")').click()
# 也可以这样写,指定标签
page.locator('article:has_text("Playwright")').click()
# 也可以这样
page.locator(":text('Playwright')").click()
# 还可以这样
page.locator('article:has'text=Playwright').click()

 page.locator("div ").locator("text='Playwright'")

2.css选择器和Xpath 定位

page.locator('css=button').click() # 根据标签
page.locator('css=#nav-bar .contact-us-item').click() # 通过id +class
page.locator('css=[data-test=login-button]').click() # 属性定位
page.locator("css=[aria-label='Sign in']").click()

page.locator(xpath="//div[@id='myId']").click()
#不需要前面的前缀css= 和 xpath=, 它会自动判断你写的是css还是xpath语法,前提是你语法没有错误。

3.组合定位:text、css、xpath三者可以两组合定位

  1. css+text组合定位

    page.locator("article:has-text('Playwright')").click()
    page.locator("#nav-bar :text('Contact us')").click()
    
  2. css+css组合定位

    page.locator(".item-description:has(.item-promo-banner)").click()
    
  3. Xpath + css 组合定位

    page.fill('//div[@class="SignFlow-account"] >>css=[name="username"]',"0863")
    
  4. xpath+xpath组合定位

    page.fill('//div[@class="SignFlowInput"] >> //input[@name="password"]',"ma160065")
    
  5. 利用HTML元素的属性来定位,比如ID、name或其他自定义属性。

    page.locator("[data-testid='my-element']")
    
  6. 组合定位: 在复杂场景下,你可能需要结合多个条件来定位元素,Playwright 支持链式调用来实现这一需求。

    page.locator("div.container").locator("input[type='text']")
    

2.playwright推荐的内置定位——get_by

get_by_role: 根据元素在页面中扮演的角色(如按钮、链接、输入框等)进行定位 例如:page.get_by_role("button", name="Submit")
get_by_text: 通过使用文本内容来定位元素,适合于元素没有唯一标识符的情况。例如:page.get_by_text("Submit")
get_by_label: 根据label属性值查找元素,类似于HTML中的label标签和对应的for属性。

get_by_id: 通过元素的 id 属性来查找元素,例如:page.get_by_id("my-id")
get_by_name: 通过元素的 name 属性来查找元素,例如:page.get_by_name("my-name")

get_by_title: 通过元素的 title 属性来查找元素,例如:page.get_by_title("my-title")
get_by_placeholder: 通过元素的 placeholder 属性来查找元素,例如:page.get_by_placeholder("my-placeholder")
get_by_selector: 通过 CSS 选择器来查找元素,例如:page.get_by_selector("#submit-button")
get_by_xpath: 通过 XPath 表达式来查找元素,例如:page.get_by_xpath("//div[@class='my-class']")

3.html5的role属性与get_by_role

html5总能role的作用是 是增强html语义性,当现有的html标签不能充分表达语义性的时候,就可以借助role来说明。

常用role属性

1. button :表示一个按钮,用于触发某个操作。
2. link :表示一个链接,通常用于导航目的。
3. heading:表示一个标题,标识文档结构。
4.  textbox :表示一个文本框,用户可以在其中输入文本。
5. checkbox:表示一个复选框,用户可以选择或取消选择。
6. radiobutton :表示一个单选按钮,用户可以从一组选项中选择一个。
7. menu :表示一个菜单,用户可以从中选择一个选项。
8. list :表示一个列表,可以是有序或无序的。
9. progressbar :表示一个进度条,用于表示任务的进度。
10. dialog :表示一个对话框,用户可以与之交互。

在这里插入图片描述

get_by_role

#元素:<li class="el-menu-item" role="menuitem" tabindex="-1">队列管理</li>
page.get_by_role("menuitem", name="队列管理").click()

#元素:<button class="el-button el-button--primary el-button--default mr-24px mb-12px" aria-disabled="false" type="button"><span>新增队列</span></button>
page.get_by_role("button", name="新增队列").click()

五.浏览器操作

比如:输入文本、单选、多选、选择下拉框、点击按钮/文本、按下某个按键、上传文件、元素对应焦点、鼠标的拖拽、执行JS脚本等等。

1.Text input文本输入

对应的是input、textarea、contenteditable等元素

locator = page.get_by_label("Password")
locator.fill("mypassword")   # 输入一段文字
locator.fill("type")#一个字符一个字符地输入字段

2.Checkboxes and radio buttons 单选和多选

locator = page.get_by_label('I agree to the terms above')
locator.check()

3.Select options下拉选择

locator = page.get_by_label('Choose a color')
locator.select_option('blue')

4.Click鼠标单击和双击

# 点击
page.get_by_role("button").click()

# 双击
page.get_by_text("Item").dblclick()

# 右击
page.get_by_text("Item").click(button="right")

# Shift + 点击
page.get_by_text("Item").click(modifiers=["Shift"])

# 鼠标悬停在元素上
page.get_by_text("Item").hover()

# 点击左上角
page.get_by_text("Item").click(position={ "x": 0, "y": 0})

5.Press按下指定的键

# 按Enter键
page.get_by_text("Submit").press("Enter")  # locator上按回车键

# 按ctrl + 右方向键
page.get_by_role("textbox").press("Control+ArrowRight")  # ctrl + 右方向键

# 按键盘上的$符合
page.get_by_role("textbox").press("$")  # 按下$

上述特殊按键有:

Backquote, Minus, Equal, Backslash, Backspace, Tab, Delete, Escape,
ArrowDown, End, Enter, Home, Insert, PageDown, PageUp, ArrowRight,
ArrowUp, F1 - F12, Digit0 - Digit9, KeyA - KeyZ

可以组合按下指定的键

# <input id=name>
page.locator('#name').press('Shift+A')  # id为name的元素中按下shift + A

# <input id=name>
page.locator('#name').press('Shift+ArrowLeft')

6.Focus聚焦

page.get_by_label('password').focus()

7.Darg and Drop拖拉

效果是先将鼠标移动要操作的locator上,然后按住左键,移动鼠标到目标locator所在位置,松开鼠标

page.locator("#item-to-be-dragged").drag_to(page.locator("#item-to-drop-at"))
# 将一个locator拖到另一个locator上

8.鼠标移动到指定的locator上

这在处理一些隐藏菜单很有效,鼠标放到菜单上后,菜单显示,然后就可以操作

page.locator("#item-to-be-dragged").hover()
page.mouse.down()
page.locator("#item-to-drop-at").hover()
page.mouse.up()

9.运行JS脚本

这也是一个很有效的手段,比如某个日期输入框是只读的,无法直接录入想要的日期,只能通过日期选择框去选择,而通过日期选择框去选择效率会很低下,这个时候我们只需要通过运行JS脚本将该输入框的只读属性去掉,然后使用input方法录入日期即可

# 将id为txtStartDtate的元素去掉readonly属性
page.evaluate('document.getElementById("txtStartDate").removeAttribute("readonly");')

10.文件上传

# Select one file
page.get_by_label("Upload file").set_input_files('myfile.pdf')
# page.get_by_label("Upload file")为一个locator,选择一个文件myfile.pdf

# Select multiple files,选择多个文件
page.get_by_label("Upload files").set_input_files(['file1.txt', 'file2.txt'])

# Remove all the selected files,清楚选择的文件名
page.get_by_label("Upload file").set_input_files([])

# Upload buffer from memory
page.get_by_label("Upload file").set_input_files(
    files=[
        {"name": "test.txt", "mimeType": "text/plain", "buffer": b"this is a test"}
    ],
)

11.页面事件监听

Page 对象提供了一个 on 方法,它可以用来监听页面中发生的各个事件,比如 close、console、load、request、response 等等

from playwright.sync_api import sync_playwright
 
 
def run(playwright):
    chromium = playwright.chromium
    browser = chromium.launch()
    page = browser.new_page()
    # 监听请求和响应事件
    page.on("request", lambda request: print(">>", request.method, request.url))
    page.on("response", lambda response: print("<<", response.status, response.url))
    page.goto("https://example.com")
    browser.close()
 
 
with sync_playwright() as playwright:
    run(playwright)

12.获取元素文本

inner_text():获取元素的文本内容

六.断言

expect(locator).to_be_checked()  复选框可被选中
expect(locator).to_be_disabled()	元素处于禁用状态
expect(locator).to_be_editable()	元素为可编辑状态
expect(locator).to_be_empty()	容器为空
expect(locator).to_be_enabled()	元素状态为enabled
expect(locator).to_be_focused()	元素位于焦点
expect(locator).to_be_hidden()	元素不可见
expect(locator).to_be_visible()	元素可见
expect(locator).to_contain_text()	元素包含文本
expect(locator).to_have_attribute()	Element has a DOM attribute
expect(locator).to_have_class()	Element has a class property
expect(locator).to_have_count()	List has exact number of children
expect(locator).to_have_css()	Element has CSS property
expect(locator).to_have_id()	Element has an ID
expect(locator).to_have_js_property()	Element has a JavaScript property
expect(locator).to_have_text()	Element matches text
expect(locator).to_have_value()	Input has a value
expect(locator).to_have_values()	Select has options selected
expect(page).to_have_title()	Page has a title
expect(page).to_have_url()	Page has a URL
expect(response).to_be_ok()	Response has an OK status

可自定义一个不符合条件的错误信息:

# 不符合条件超时错误后,提示should be logged in
expect(page.get_by_text("Name"), "should be logged in").to_be_visible()

举例说明,访问bing页面,会根据所在的ip地理位置显示不同版本的页面

page.goto('https://www.bing.com')

try:
    # 可尝试更改'必应'为'bing',看是否会报错
    expect(page, '非中文页面').to_have_title('必应') # 显示的页面标题是否为必应,否则报错:非中文页面
    print('中文页面')
except Exception as e:
    print('进入非中文页面')
    print(e)

可设置全局expect超时时间,默认为5秒

expect.set_options(timeout=10_000)  # 超时为10

七.浏览器常见配置

1.截图

1.1. 截图整个页面

from playwright.sync_api import sync_playwright  
  
with sync_playwright() as p:  
    browser = p.chromium.launch(headless=False)  # headless=False表示以有头模式运行,可以看到浏览器界面  
    page = browser.new_page()  
    page.goto("https://example.com")  
    page.screenshot(path="screenshot.png")  # 截图并保存到当前目录下的screenshot.png  
    browser.close()
  • 如果你想要截取整个可滚动页面的屏幕截图(长截图),可以添加full_page=True参数:
page.screenshot(path="full_page_screenshot.png", full_page=True)

1.2. 截取整个页面并裁剪

首先,你可以截取整个页面,然后使用图像处理库(如Pillow(Python)、Sharp(Node.js)等)来裁剪出你需要的部分。

Python 示例(使用Pillow):

from PIL import Image  
from playwright.sync_api import sync_playwright  
  
with sync_playwright() as p:  
    browser = p.chromium.launch(headless=False)  
    page = browser.new_page()  
    page.goto("https://example.com")  
  
    # 截取整个页面  
    full_page_screenshot = page.screenshot(path="full_page.png", full_page=True)  
    # 截取整个可滚动页面的屏幕截图(长截图),可以添加full_page=True参数:

  
    # 使用Pillow裁剪图片(这里需要你指定裁剪的坐标和大小)  
    img = Image.open("full_page.png")  
    cropped_img = img.crop((left, top, right, bottom))  # left, top, right, bottom 是裁剪区域的坐标  
    cropped_img.save("cropped_image.png")  
  
    browser.close()
  • 注意:你需要根据页面内容和布局来手动计算裁剪区域的坐标(left, top, right, bottom)。

2. 设置窗口(Viewport)大小

另一种方法是调整浏览器窗口(Viewport)的大小,以匹配你想要截取的图片大小,然后截取整个页面。但是,这种方法可能不适用于需要滚动条来查看整个内容的页面。

from playwright.sync_api import sync_playwright  
  
with sync_playwright() as p:  
    browser = p.chromium.launch(headless=False)  
    context = browser.new_context(viewport_size={"width": 800, "height": 600})  # 设置窗口大小  
    page = context.new_page()  
    page.goto("https://example.com")  
  
    # 由于窗口大小已设置,截取的图片大小将与窗口大小相匹配  
    page.screenshot(path="viewport_screenshot.png")  
  
    browser.close()

4.截取特定元素并调整大小

Playwright还支持截取页面中的特定元素。你可以使用locator来定位元素,并调用其screenshot方法:

  • 如果你只需要截取页面上的特定元素,并且想要控制输出图片的大小,你可以先截取该元素,然后使用图像处理库来调整图片大小。
# ...(之前的代码,定位到特定元素)  
  
# 截取特定元素  
element_screenshot = page.locator("selector").screenshot(path="element_screenshot.png")  
  
# 使用Pillow调整图片大小(如果需要)  
# ...(与上面裁剪图片的代码类似,但使用resize方法)

5. 捕获截图为字节流

如果你不想将截图保存到文件,而是想将其捕获为字节流以便进一步处理(如发送到服务器或进行像素差异比较),可以这样做:

import base64  
 
screenshot_bytes = page.screenshot()  
screenshot_base64 = base64.b64encode(screenshot_bytes).decode()  
print(screenshot_base64)  # 输出截图的Base64编码

2.自定义Header

browser_context.set_extra_http_headers(headers)
from playwright.async_api import async_playwright  
  
async def run(playwright):  
    browser = await playwright.chromium.launch()  
    context = await browser.new_context()  
      
    # 设置额外的HTTP头部  
    extra_headers = {  
        "Authorization": "Bearer YOUR_ACCESS_TOKEN",  
        "Custom-Header": "HeaderValue"  
    }  
    await context.set_extra_http_headers(extra_headers)  
      
    # 现在,由这个上下文中的任何页面发起的请求都将包含这些额外的HTTP头部  
    page = await context.new_page()  
    await page.goto('https://example.com')  
      
    # ... 其他操作 ...  
      
    await browser.close()  
  
async_playwright().start(run)

3.自定义UserAgent

创建浏览器上下文(BrowserContext)时,通过new_context方法的user_agent参数来设置user-agent。这种方式该上下文创建的所有页面都会共用user_agent

from playwright.sync_api import Playwright, sync_playwright

with sync_playwright() as playwright:
    browser = playwright.chromium.launch()
    # 在浏览器上下文级别设置user-agent  
    context = browser.new_context(
        user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36')
    page = context.new_page()
    page.goto('https://example.com')
    # 后续操作...  
    browser.close()

4.设置浏览器代理

在使用Playwright进行自动化测试或浏览器操作时,设置代理是一个常见的需求。Playwright支持在多个层面上设置代理,包括全局代理、上下文代理以及页面级别的代理。以下是如何在Playwright中设置代理的几种方式:

1. 启动时设置全局代理

在启动浏览器时,可以通过launch方法的proxy参数来设置全局代理。这种方式会影响由该浏览器实例发起的所有请求。

示例代码:

from playwright.sync_api import Playwright, sync_playwright  
  
with sync_playwright() as playwright:  
    # 不需要身份验证的代理  
    browser = playwright.chromium.launch(proxy={'server': 'http://ip:port'})  
      
    # 需要身份验证的代理  
    # browser = playwright.chromium.launch(proxy={'server': 'http://ip:port', 'username': '用户名', 'password': '密码'})  
      
    # 后续操作...  
    # browser.close()

2. 上下文设置代理

通过为每个页面创建一个独立的context对象,并在该对象上设置代理,可以实现为特定页面或一组页面指定不同的代理设置。

from playwright.sync_api import Playwright, sync_playwright  
  
with sync_playwright() as playwright:  
    browser = playwright.chromium.launch()  
    # 设置代理  
    context = browser.new_context(proxy={'server': 'http://ip:port'})  
      
    # 基于该上下文创建页面  
    page = context.new_page()  
    # 后续操作...  
    # page.close()  
    # context.close()  
    # browser.close()

5.自动等待机制

1.自动等待和##可操作性校验(Auto-waiting)

Playwright具有内置的自动等待机制,该机制在执行某些操作(如点击、输入等)之前,会自动检查目标元素是否满足以下条件:

  • 元素加载DOM完成:确保元素已经被加载到DOM中。
  • 元素可见:确保元素在页面中可见,没有被其他元素遮挡。
  • 元素是稳定的:确保元素的状态已经稳定,不会因为页面的其他操作而发生变化。(非动画中或动画已完成)
  • 元素没有被其他元素遮挡:确保点击或交互的位置没有其他元素(如覆盖层)阻挡。
  • 元素是可操作的:确保元素是可交互的,没有被禁用。(例如,按钮的enabled属性为true)

如果以上校验在设定的超时时间内得没有通过,Playwright将继续执行下一步操作;否则,将抛出TimeoutError异常。这种自动等待机制大大简化了测试脚本的编写,并提高了测试的稳定性。

2.显式等待API(Explicit Waiting)

除了自动等待外,Playwright还提供了多种显式等待方法,API允许开发者在测试脚本中设置等待条件,从而避免手动等待的繁琐和不确定性。

wait_for_selector(selector, options):等待指定的选择器匹配到的元素出现在页面上。
wait_for_timeout(timeout):等待指定的时间。
wait_for_url(url, options):等待URL包含指定的字符串。
wait_for_navigation(options):等待页面完成导航。
wait_for_event:等待给定的事件被触发,如click、submit等。
wait_for_function:等待指定的JavaScript函数返回true。

详细介绍

  1. wait_for_selector(selector, options):等待指定的选择器匹配到的元素出现在页面上。(等待元素加载完毕)

    • selector:CSS选择器或XPath表达式,用于指定要等待的元素。

    • options(可选):一个对象,包含等待的选项,如timeout(超时时间,单位毫秒)。

      element = await page.wait_for_selector('#my-element', { timeout: 5000 })
      #这个示例将等待最多5秒钟,直到页面上出现ID为my-element的元素。
      
      • 在进行页面跳转后,可以加入wait_for_selector等待元素加载,以确保页面内容已经完全加载完成

      • state 参数可以设置等待状态,用四个状态:“attached”, “detached”, “hidden”, “visible”。
        在这里插入图片描述

    wait_for() 方法和>wait_for() 方法 和 wait_for_selector()使用区别:

    • page.locator(‘定位元素’).wait_for() 返回的是None,后面不能继续操作元素
    • page.wait_for_selector(“定位方法”)返回的是locator 对象,后面可以继续操作元素
  2. wait_for_timeout(timeout):等待指定的时间。这通常用于调试目的,但在生产环境中应谨慎使用。

    • timeout:等待的时间,单位毫秒。
      await page.wait_for_timeout(5000)
      
  3. wait_for_url(url, options) 等待当前页面的URL包含指定的字符串或完全匹配指定的URL。

    • url:要等待的URL字符串或包含部分URL的字符串。
    • options(可选):一个对象,包含等待的选项,如timeout。
      await page.wait_for_url('https://example.com', { timeout: 10000 })
      #等待最多10秒钟,直到当前页面的URL包含https://example.com。
      
  4. wait_for_navigation(options) 等待页面完成导航。

    • options(可选):一个对象,包含等待的选项,如waitUntil(指定等待的导航状态,如’networkidle’表示等待网络请求空闲)和timeout。

      await page.click('a.some-link')  
      await page.wait_for_navigation({ waitUntil: 'networkidle' })
      #首先点击一个链接,然后等待页面导航到新的URL,并等待直到网络请求空闲。
      

3.全局设置等待超时时间

  • 这些设置将影响所有接受timeout参数的方法。设置的优先级是:页面级别的优先于浏览器上下文级别。
browser_context.set_default_timeout(timeout):设置浏览器上下文级别的默认超时时间。
page.set_default_timeout(timeout):设置页面级别的默认超时时间。
browser_context.set_default_navigation_timeout(timeout):设置浏览器上下文级别的默认导航超时时间。
page.set_default_navigation_timeout(timeout):设置页面级别的默认导航超时时间。

6.处理新的窗口、弹窗,iframe

selenium处理iframe比较麻烦,但是playwright就比较简单,有不同方法

  1. 直接定位一个frame,在frame基础上操作
# ********同步*********
#根据iframe名字
frame = page.frame('frame-login')
#根据iframe的url
frame = page.frame(url=r'.*domain.*')
 
# Interact with the frame
frame.fill('#username-input', 'John')
 
 
 
 
# *********异步***********
#根据iframe名字
frame = page.frame('frame-login')
#根据iframe的url
frame = page.frame(url=r'.*domain.*')
# Interact with the frame
await frame.fill('#username-input', 'John')
  1. 直接定位到frame再定位到上面的元素,在元素基础上操作
username = page.frame_locator('.frame-class').locator('#username-input')
username.fill('jonas')

处理弹窗,一般注册、或者点击一些按钮容易出现弹窗,我们可以利用page.expect_popup()来获取新窗口的iframe

示例:

with page.expect_popup() as popup_info:
    # iframe中的id如果是动态的,所以我们只匹配关键字
    page.frame_locator("iframe[id^=x-URS-iframe]").locator("text=注册新帐号").click()
    register_page = popup_info.value
    
    # 点击邮箱地址输入框
    register_page.locator("input[id=\"username\"]").click()
    # 输入邮箱
    register_page.locator("input[id=\"username\"]").fill("TesterRoad")
    # 点击密码输入框
    register_page.locator("input[id=\"password\"]").click()
    # 输入密码
    register_page.locator("input[id=\"password\"]").fill("TesterRoad@126")

手动设置等待是为了确保接下来的操作可以成立,旧版本里使用了很多类似wait_for_selector的用法,但新版本推荐使用的是expect方法,就是期望某个条件成立,默认超时时间为5秒

7.支持Pytest框架

另外,还可以配合pytest插件一起使用,给出一段官网示例:

# 导入pytest和playwright的sync_api(也可以使用async_api)  
import pytest  
from playwright.sync_api import Page  
  
# 使用pytest的fixture来初始化浏览器页面  
@pytest.fixture(scope="function")  
def page(browser_type):  
    """启动浏览器并创建一个新页面"""  
    browser = browser_type.launch(headless=False)  # 如果想看到浏览器运行,可以设置为False  
    page = browser.new_page()  
    yield page  # 测试函数将在这里接收page对象  
    browser.close()  # 测试完成后关闭浏览器  
  
# 编写测试函数  
def test_example(page):  
    """示例测试:访问Playwright官网并验证标题"""  
    # 导航到Playwright官网  
    page.goto("https://playwright.dev/")  
      
    # 验证页面标题是否包含"Playwright"  
    title = page.title()  
    assert "Playwright" in title  
      
    # 进一步的操作,比如点击链接、填写表单等  
    # ...  
  
# 注意:上述代码中的browser_type是由pytest-playwright插件自动提供的fixture  
# 你可以通过它来启动浏览器(如Chromium、Firefox、WebKit等)  
# headless=False 表示以有头模式运行浏览器,这样你可以看到浏览器界面  
# 如果你想要以无头模式运行(即不显示浏览器界面),可以将headless设置为True或省略该参数(因为默认就是无头模式)

8.移动端浏览器支持

移动端支持Safari 浏览器、谷歌、不支持火狐,可以传入的设备有iPhone和Pixel 2 (Pixel 2 是谷歌的一款安卓手机)

  • 示例:模拟打开 iPhone 12 Pro Max 上的 Safari 浏览器,然后手动设置定位,并打开百度地图并截图

  • 故宫的经纬度是 39.913904, 116.39014,我们可以通过geolocation参数传递给 Webkit 浏览器并初始化

from playwright.sync_api import sync_playwright
 
 
with sync_playwright() as p:
    iphone_12_pro_max = p.devices['iPhone 12 Pro Max']
    browser = p.webkit.launch(headless=False)
    context = browser.new_context(
        **iphone_12_pro_max,
        locale='zh-CN',
        geolocation={'longitude': 116.39014, 'latitude': 39.913904},
        permissions=['geolocation']
    )
    page = context.new_page()
    page.goto('https://amap.com')
    page.wait_for_load_state(state='networkidle')
    page.screenshot(path='location-iphone.png')
    browser.close()

注意事项

  • 设置user-agent时,请确保它符合你的测试需求或目标网站的兼容性要求。
  • 如果你需要频繁地更改user-agent,考虑将设置user-agent的逻辑封装成一个函数或方法,以便在需要时重复使用。
  • 请注意,某些网站可能会根据user-agent的不同而返回不同的内容或执行不同的逻辑。因此,在自动化测试或爬虫开发过程中,合理地设置user-agent是非常重要的。

额外信息
– 除了user-agent之外,Playwright还允许你在创建浏览器上下文时设置其他许多浏览器级别的参数,如窗口大小(viewport)、地理位置(geolocation)、语言(locale)、时区(timezone)等。这些参数可以帮助你更精确地模拟不同用户的浏览器环境,从而提高自动化测试或爬虫开发的效率和准确性。

八.playwright如何绕过反爬虫检测

检测地址:https://intoli.com/blog/not-possible-to-block-chrome-headless/chrome-headless-test.html

  • 网站为了保护信息不被爬取会添加一些反爬虫策略,比如直接打开谷歌浏览器,在控制台输入window.navigator.webdriver,可以看到该属性为false
    在这里插入图片描述

  • 用selenium或者playWright打开浏览器,该属性为true
    在这里插入图片描述

  • 网站很容易在前端根据这些属性判断是否使用了playwright,从而阻止用户采用自动化工具获取信息

那么如何屏蔽掉这些属性,让网站无法识别是否使用了playwright呢?。

  1. 首先通过浏览器打开网站https://bot.sannysoft.com/,该网站列出了常用的一些反爬虫检测属性
    在这里插入图片描述

  2. 使用playwright打开这个网站, 发现webdriver属性被检测出异常

from playwright.sync_api import sync_playwright
import time
 
with sync_playwright() a playwright:

    browser  _playwright.chromium.launch(headless=False)
    page = browser.new_page()
    
    page.goto('https://bot.sannysoft.com/')
 
    time.sleep(100)
 
    browser.close()

在这里插入图片描述

1 防止webdriver属性被检测

from playwright.sync_api import sync_playwright
import time
 

with sync_playwright() as playwright:
    browser = playwright.chromium.launch(headless=False)
    page = browser.new_page()

    js="""
    Object.defineProperties(navigator, {webdriver:{get:()=>false}});
    """
    page.add_init_script(js)
    
    page.goto('https://bot.sannysoft.com/')
 
    time.sleep(1000)
 
    browser.close()
  • 运行后可以看到webdriver还是没有通过,打开控制台,输入navigator.webdriver
    在这里插入图片描述

  • 发现这个值的确为false,那么没有通过的原因是什么?找到网站检测的源码,有这么一几行代码

    // Webdriver Test
      const webdriverElement = document.getElementById('webdriver-result');
      if (navigator.webdriver || _.has(navigator, "webdriver")) {
        webdriverElement.classList.add('failed');
        webdriverElement.classList.remove('passed');
        webdriverElement.innerHTML = 'present (failed)';
      } else {
        webdriverElement.classList.add('passed');
        webdriverElement.classList.remove('failed');
        webdriverElement.innerHTML = 'missing (passed)';
      }
    
    from playwright.sync_api import sync_playwright
    import time
     
     
    with sync_playwright() as p:
        '''
        防止被浏览器检测的处理方法
        '''
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()
    	#加载stealth.min.js的javascript
        with open('stealth.min.js','r') as f:
            js=f.read()
        page.add_init_script(js)
        
        page.goto('https://bot.sannysoft.com/')
     
        time.sleep(1000)
     
        browser.close()
    
    • 运行后,这次检测已经通过了。
      在这里插入图片描述

2 headless=True无头浏览器如何绕过反爬虫

上面可以过检测用的是带界面浏览,当为无头浏览器是怎么样的呢,这里采用运行后进行截图的方式进行调试

from playwright.sync_api import sync_playwright
import time
 
 
with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()

    with open('stealth.min.js','r') as f:
        js=f.read()
    page.add_init_script(js)
    
    page.goto('https://bot.sannysoft.com/')
    #进行截图
    page.screenshot(path='bot_sannysoft.png',full_page=True)
 
    time.sleep(1000)
 
    browser.close()

在这里插入图片描述

  • 当用无头浏览器时user agent没有通过,因此需重新设置user agent
from playwright.sync_api import sync_playwright
import time
 
 
with sync_playwright() as p:
    browser = p.chromium.launch(headless=True,
                                args=['--disable-blink-features=AutomationControlled',
                                      '--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'])
    page = browser.new_page()

    with open('stealth.min.js','r') as f:
        js=f.read()
    page.add_init_script(js)
    # 设置网页大小也可以防止无头浏览器被检测
    page.set_viewport_size({'width': 1024, 'height': 768})
    
    page.goto('https://bot.sannysoft.com/')
    #进行截图
    page.screenshot(path='bot_sannysoft.png',full_page=True)
 
    time.sleep(1000)

    browser.close()
  • 运行后,发现无头浏览器所有检测也均能通过。
    在这里插入图片描述

3.stealth.min.js作用

‌stealth.min.js 这个JavaScript主要用于保护用户隐私和匿名性,同时也在自动化测试中隐藏自动化特征。
主要功能和应用场景:

  • 隐藏行为:可以通过模拟用户行为的方式来执行各种操作,比如点击、滚动、输入等,从而隐藏真实用户的操作轨迹使得网站无法准确追踪用户的行为。

  • 保护用户隐私和匿名性:通过修改‌User Agent、禁用浏览器指纹识别、隐藏浏览器插件信息、阻止第三方Cookie等方式,stealth.min.js可以帮助用户在使用网络时保持匿名,防止被网站或第三方服务追踪个人信息。‌

  • 防止爬虫检测:在自动化测试中,如使用‌Selenium进行网页测试时,stealth.min.js可以帮助隐藏自动化工具的特征,防止被网站识别为非法访问,从而提高测试的效率和准确性。‌

4.防止爬虫检测的方式

  1. 使用无头浏览器:无头浏览器不会显示图形界面,从而降低了被检测的风险。
  2. 设置User-Agent:通过模拟常见的浏览器User-Agent,可以减少被检测的可能性。
  3. 添加随机延迟:在爬虫中添加随机延迟,模拟真实用户的操作习惯,降低被检测的风险。
  4. 使用代理IP:通过代理IP来隐藏真实的IP地址,降低被识别和封锁的风险。

九.通过CDP(Chrome DevTools Protocol)连接现有浏览器

1.CDP(Chrome DevTools Protocol)和WDP (WebDriver Protocol)协议

WDP 和 CDP 是用于自动化浏览器的2个主要协议,大多数的浏览器自动化工具都是这两个协议实现和浏览器交互,通过代码来控制浏览器,完成浏览器的自动化行为(包括网页加载,点击、输入、按键、截图,导出pdf等)。

2.WebDriver Protocol

  • WebDriver 是一个用于控制浏览器的远程控制接口,由Selenium HQ开发,后来由 W3C 标准化。它提供了一个平台和语言中立的接口,支持几乎所有主流浏览器,如 Chrome、 Firefox、 Safari、 Edge、 Opera 等。
  • 它和浏览器的通信是通过 JSON Wire 协议完成的,提供了RESTful的web服务,该服务端就被被称为webdriver,例如chromeDriver、geckoDriver等。
    • webdriver的客户端可为任何语言由webdriver客户端和webdriver服务端交互,webdriver服务端和浏览器交互,从而操作浏览器
    • 常见的客户端就是selenium,nightwatch,webdriverio
      在这里插入图片描述
      加上我们的自己写的自动化测试代码之后,交互流程如下:
      在这里插入图片描述
    • 常见的调用流程如下
      1. webdriver客户端(Python代码) 通过调用自动化库API
      2. 自动化库通过对应的webdriver服务端(如selenium)基于WDP协议通过 JSON Wire 协议RESTful方式向浏览器发起操作请求(打开/关闭/点击/输入/按键/截图等等
      3. 浏览器接收到自动化工具请求后,完成相关操作然后返回响应给webdriver服务端
      4. webdriver服务器端接收到浏览器响应,返回响应给webdriver客户端

3.Chrome DevTools Protocol

  • ChromeDevTools Protocol (CDP)是一个基于Chromium的浏览器的调试协议,如 Chrome、 Edge、 Opera 等。通过它可以直接和浏览器交互。控制浏览器的行为。

  • 浏览器是由客户端使用 CDP 协议直接控制的,客户端和浏览器之间没有类似于WebDriver Protocol的服务端(webdriver),客户端通过WebSocket直接和浏览器连接 ,由客户端通过WebSocket发送命令给浏览器,浏览器执行并返回响应
    在这里插入图片描述

  • 常见的客户端就是:Puppeteer和Playwright。它们不依赖于webdriver,而是通过 Chrome DevTools Protocol (CDP)直接与浏览器通话。从而更加灵活稳定的控制浏览器。

4.WebDriver Protocol和Chrome DevTools Protocol对比

在这里插入图片描述

5.通过CDP控制本地原生谷歌浏览器

from playwright.sync_api import sync_playwright
import subprocess

"""

1、打开浏览器,指定调试端口
在 CMD 终端中通过命令行启动 Chrome 浏览器
cd C:\Program Files\Google\Chrome\Application && chrome.exe  --remote-debugging-port=9222 --user-data-dir=“C:/playwright/user_data”


复制地址C:\Program Files\Google\Chrome\Application 添加到环境变量Path下
chrome.exe --remote-debugging-port=12345 --user-data-dir="D:\playwright_chrome"

参数配置
在启动浏览器的时候,我们还可以带上一些其它参数
    --incognito 隐私模式打开
    -–start-maximized:窗口最大化
    --new-window:直接打开网址

--remote-debugging-port 是指定浏览器调试端口号,只要没被占用就行
--user-data-dir 指定运行浏览器的运行数据(用户配置文件目录),新建一个干净目录,不影响系统原来的数据(不存在会新建),如果不显式指定该参数,运行会污染浏览器默认的配置文件

"""
# 1.打开浏览器,指定调试端口 这个路径可以是Google浏览器的exe路径,也可以是快捷方式的路径
# 使用connect_over_cdp()方法接管前面已经打开的浏览器,获取到context 上下文,通过上下文再获取到page对象
chrome_path = r'"C:\Users\87772\AppData\Local\Google\Chrome\Application\chrome.exe"'
debugging_port = "--remote-debugging-port=9999"

command = f"{chrome_path} {debugging_port}"
subprocess.Popen(command, shell=True)


# 2.拦截请求
def intercept_xhr(route, request):
    route.continue_()
    response = route.fetch()
    json = response.json()
    print(json)


with sync_playwright() as p:
    # 2.连接到已经通过CDP启动的浏览器实例。
    browser = p.chromium.connect_over_cdp("http://localhost:9999")
    content = browser.contexts[0]
    page = content.new_page()

    # 设置拦截规则
    page.route("**/api/sns/web/v1/homefeed", lambda route, request: intercept_xhr(route, request))

    page.goto('https://www.xiaohongshu.com/')
    page.wait_for_selector('.feeds-container')

    #点击关闭
    page.locator(".close-button").click()

    # 获取页面内容高度
    page_height = page.evaluate('() => document.body.scrollHeight')

    # 模拟鼠标滚动操作,向下滚动到底部
    while page.evaluate('() => window.scrollY + window.innerHeight') < page_height:
        page.mouse.wheel(0, 100)  # 这里的参数可以根据需要进行调整

    page.wait_for_timeout(5000)

    page.close()
    content.close()
    browser.close()

十.拓展知识

屏幕坐标系,世界坐标系

在这里插入图片描述

屏幕坐标系,主要有两种,

  • 第一种:以左上角为原点。代表的操作系统有Windows,Android,Symbian,iOS 的Core Graphics

  • 第二种:以左下角为原点。比如iOS的CGContextDrawImage

    • IOS 零点在左上角
    • mac 零点在左下角

十一.Playwright 和 Selenium 的区别

在这里插入图片描述
在这里插入图片描述

  • Playwright的优点是简单方便、功能强大、稳定性高,缺点是相对新,用户群体少,学习资料少。只能看官网文档
  • Selenium的优点是灵活性高、用户群体大、学习资料多,缺点是需要自己封装或者导入其他模块来实现一些功能,启动速度慢,稳定性差。

如果你是新手,毫不犹豫应该直接学playwright


参考文章:《最新出炉》系列初窥篇-Python+Playwright自动化测试

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

墩墩分墩

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值