【Python实战指南】高效Web自动化测试利器:Python Playwright入门与自动化测试实战解析

文章目录

  • 前言
  • 一.playwright是什么
  • 二.python引入playwright
  • 1.安装
  • 2.playwright命令行参数
  • 3.playwright codegen自动生成代码
  • 4.Chrome和Chromium有什么关系?
  • 三.基本概念
  • 1. 无头浏览器(Headless Browser)
  • 2.同步和异步模式操作playwright
  • 2.1.同步(Sync)模式
  • 同步方式代码模板
  • 2.2.异步(Async)模式
  • 异步方式代码模板
  • 3.Browser(浏览器驱动)
  • 4.Context(浏览器上下文)
  • 5.Page页面(浏览器标签页)
  • 四.页面元素定位
  • 1.locator选择器
  • 1.文本选择器
  • 2.css选择器和Xpath 定位
  • 3.组合定位:text、css、xpath三者可以两组合定位
  • 2.playwright推荐的内置定位——get_by
  • 3.html5的role属性与get_by_role
  • 五.浏览器操作
  • 1.Text input文本输入
  • 2.Checkboxes and radio buttons 单选和多选
  • 3.Select options下拉选择
  • 4.Click鼠标单击和双击
  • 5.Press按下指定的键
  • 6.Focus聚焦
  • 7.Darg and Drop拖拉
  • 8.鼠标移动到指定的locator上
  • 9.运行JS脚本
  • 10.文件上传
  • 11.页面事件监听
  • 12.获取元素文本
  • 六.断言
  • 七.浏览器常见配置
  • 1.截图
  • 1.1. 截图整个页面
  • 1.2. 截取整个页面并裁剪
  • 2. 设置窗口(Viewport)大小
  • 4.截取特定元素并调整大小
  • 5. 捕获截图为字节流
  • 2.自定义Header
  • 3.自定义UserAgent
  • 4.设置浏览器代理
  • 1. 启动时设置全局代理
  • 2. 上下文设置代理
  • 5.自动等待机制
  • 1.自动等待和##可操作性校验(Auto-waiting)
  • 2.显式等待API(Explicit Waiting)
  • 3.全局设置等待超时时间
  • 6.处理新的窗口、弹窗,iframe
  • 7.支持Pytest框架
  • 8.移动端浏览器支持
  • 八.playwright如何绕过反爬虫检测
  • 1 防止webdriver属性被检测
  • 2 headless=True无头浏览器如何绕过反爬虫
  • 3.stealth.min.js作用
  • 4.防止爬虫检测的方式
  • 九.通过CDP(Chrome DevTools Protocol)连接现有浏览器
  • 1.CDP(Chrome DevTools Protocol)和WDP (WebDriver Protocol)协议
  • 2.WebDriver Protocol
  • 3.Chrome DevTools Protocol
  • 4.WebDriver Protocol和Chrome DevTools Protocol对比
  • 5.通过CDP控制本地原生谷歌浏览器
  • 十.拓展知识
  • 屏幕坐标系,世界坐标系
  • 十一.Playwright 和 Selenium 的区别
  • 前言

    本教程旨在引导读者从零开始,逐步掌握使用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/
    官方文档
  • github项目地址:https://github.com/microsoft/playwright-python
  • Python文档
  • PythonAPI文档
  • 其他鼎鼎大名的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+以上)

    2. 可在https://pypi.org/project/playwright/查看它的依赖版本信息。
    3. pip install playwright
      
    4. 官网推荐pip install pytest-playwright来安装,但没必要,会安装playwright及其他一堆测试所用的库,如果只是使用playwright,那么就没必要这样去安装。
    5. 自动下载使用的浏览器

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

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

    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参数问题导致的。‌

    2. 常见的反爬虫手段是通过检测当前user-agent是否为真实浏览器来区分当前请求是否来自真实用户。爬虫使用的常见user-agent类型为:
    3. user-agent为空。没有设置user-agent。
    4. 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")')
      
    3. 使用 locator 定位元素,不管元素存不存在,都会返回一个locator 对象,可以用到count() 方法统计元素的个数,如果元素个数是 0, 那么元素就不存在
    4. 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的作用是描述一个非标准的tag的实际作用。比如用div做button,那么设置div 的 role=“button”,辅助工具就可以认出这实际上是个button

  • 元素的role属性参考https://www.w3.org/TR/2014/REC-wai-aria-implementation-20140320/#mapping_role_tablehttp://

  • 常用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):等待指定的选择器匹配到的元素出现在页面上。(等待元素加载完毕)

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

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

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

    5. state 参数可以设置等待状态,用四个状态:“attached”, “detached”, “hidden”, “visible”。

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

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

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

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

    15. 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)';
      }
    
  • 属性 _.has(navigator, “webdriver”)true没有通过,这个表示navigator中有webdriver这个属性了,必须去掉才可以过这个检测。
  • 这个去掉该属性直接用GitHub上下载的一个stealth.min.js,地址为https://github.com/kingname/stealth.min.js/blob/main/stealth.min.js
  • 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 等。
  • 官网:https://w3c.github.io/webdriver/
  • 它和浏览器的通信是通过 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 等。通过它可以直接和浏览器交互。控制浏览器的行为。

  • 官网:https://chromedevtools.github.io/devtools-protocol/
  • 浏览器是由客户端使用 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自动化测试

    作者:墩墩分墩

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【Python实战指南】高效Web自动化测试利器:Python Playwright入门与自动化测试实战解析

    发表回复