Playwright for Python

简介

  • Playwright 是由 Microsoft 开发的,最初版本发布于 2020 年初。它是在 Google 的 Puppeteer(另一种流行的浏览器自动化库)的基础上构建的,并且由相同的团队成员开发,这些成员后来加入了 Microsoft。Playwright 旨在解决跨浏览器测试的兼容性和一致性问题,提供一个统一的 API 来支持多个浏览器,这使得它很快成为自动化测试和 Web 开发者社区中受欢迎的工具。
  • Playwright 是一个开源的自动化库和工具,用于 Web 测试和自动化。它允许开发人员通过使用相同的应用编程接口 (API) 在多个浏览器中(如 Chromium, Firefox, 和 WebKit)编写脚本以模拟用户操作。Playwright 支持多种编程语言,包括 JavaScript、TypeScript、Python、C# 和 Java,这使得它可以集成到不同的开发环境中。
  • 使用 Playwright,开发人员可以执行各种浏览器自动化任务,如页面导航、元素选择、文本输入、文件上传下载、执行 JavaScript 等,以及创建端到端的测试用例。此外,Playwright 能够处理现代 Web 应用程序中的高级用例,包括对单页应用程序 (SPA) 的支持、执行网络请求拦截和模拟、捕获浏览器控制台的日志,以及生成页面截图和 PDF 文件等。
  • Playwright 支持当前所有主流浏览器,包括 ChromeEdge(基于 Chromium)、FirefoxOperaSafari(基于 WebKit) ,提供完善的自动化控制的 API。
  • Playwright 可以在 Windows、Linux 和 macOS 上进行本地或 CI、无头或有头测试。
  • Playwright 可以在 TypeScriptJavaScriptPython.NETJava 中使用 Playwright API 。
  • Playwright 支持移动端页面测试,使用设备模拟技术可以使我们在移动 Web 浏览器中测试响应式 Web 应用程序。
  • Playwright 提供了自动等待相关的 API,当页面加载的时候会自动等待对应的节点加载,大大简化了 API 编写复杂度。
  • Playwright 为每个测试创建一个浏览器上下文。浏览器上下文相当于一个全新的浏览器配置文件。这提供了零开销的完整测试隔离。创建一个新的浏览器上下文只需要几毫秒。
  • Playwright 可以录制我们在浏览器中的操作并将代码自动生成出来。
  • Playwright 的安装和配置非常简单,安装过程中会自动安装对应的浏览器和驱动,不需要额外配置 WebDriver 等。
  • Pip

    1
    2
    3
    pip install --upgrade pip
    pip install playwright
    playwright install
  • Conda

    1
    2
    3
    4
    conda config --add channels conda-forge
    conda config --add channels microsoft
    conda install playwright
    playwright install
  • 说明

    • 要使用 Playwright,需要 Python 3.7 版本及以上,请确保 Python 的版本符合要求。
    • playwright install 是初始化操作,这时候 Playwrigth 会安装 Chromium、Firefox、WebKit 浏览器并配置一些驱动,我们不必关心中间配置的过程,Playwright 会为我们配置好。
    • 安装完成之后,我们便可以使用 Playwright 启动 Chromium 或 Firefox 或 WebKit 浏览器来进行自动化操作了。
  • 说明

    Playwright 支持两种编写模式,一种是类似 Pyppetter 一样的异步模式,另一种是像 Selenium 一样的同步模式,我们可以根据实际需要选择使用不同的模式。

  • 同步

    1
    2
    3
    4
    5
    6
    7
    8
    from playwright.sync_api import sync_playwright

    with sync_playwright() as p:
    browser = p.webkit.launch()
    page = browser.new_page()
    page.goto("http://whatsmyuseragent.org/")
    page.screenshot(path="example.png")
    browser.close()

    默认情况下,Playwright 以无头模式运行浏览器。

  • 异步

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import asyncio
    from playwright.async_api import async_playwright

    async def main():
    async with async_playwright() as p:
    browser = await p.chromium.launch(headless=False)
    page = await browser.new_page()
    await page.goto("http://playwright.dev")
    print(await page.title())
    await browser.close()

    asyncio.run(main())

    要使用带界面的模式,请在启动浏览器时传递 headless=False 参数。

  • 代码生成

    • 说明

      Playwright 的代码生成功能,可以录制我们在浏览器中的操作并将代码自动生成出来,有了这个功能,我们甚至都不用写任何一行代码,这个功能可以通过 playwright 命令行调用 codegen 来实现,我们先来看看 codegen 命令都有什么参数,输入如下命令:

      1
      playwright codegen --help

      结果如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      Usage: playwright codegen [options] [url]

      open page and generate code for user actions

      Options:
      -o, --output <file name> saves the generated script to a file
      --target <language> language to generate, one of javascript, test, python, python-async, csharp (default: "python")
      -b, --browser <browserType> browser to use, one of cr, chromium, ff, firefox, wk, webkit (default: "chromium")
      --channel <channel> Chromium distribution channel, "chrome", "chrome-beta", "msedge-dev", etc
      --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"
      --ignore-https-errors ignore https errors
      --load-storage <filename> load context storage state from the file, previously saved with --save-storage
      --lang <language> specify language / locale, for example "en-GB"
      --proxy-server <proxy> specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"
      --proxy-bypass <bypass> comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"
      --save-storage <filename> save context storage state at the end, for later use with --load-storage
      --save-trace <filename> record a trace for the session and save it to a file
      --timezone <time zone> time zone to emulate, for example "Europe/Rome"
      --timeout <timeout> timeout for Playwright actions in milliseconds (default: "10000")
      --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

      Examples:

      $ codegen
      $ codegen --target=python
      $ codegen -b webkit https://example.com
    • 参数

      参数 说明 默认值
      -o 将生成的脚本保存到一个文件中
      --target 生成的代码语言 python
      -b 使用的浏览器 chromium
      --channel Chromium 发行渠道
      --color-scheme 模仿首选的配色方案,“浅色” 或 “深色”
      --device 模拟设备,例如:“iPhone 11”
      --geolocation 指定地理位置坐标
      --ignore-https-errors 忽略 https 错误
      --load-storage 从文件中加载上下文存储状态
      --lang 指定语言 / 区域设置,例如:"en-GB"
      --proxy-server 指定代理服务器
      --proxy-bypass 逗号分隔的域可以绕过代理
      --save-storage 在末尾保存上下文存储状态
      --save-trace 记录会话的跟踪,并将其保存到文件中
      --timezone 要模拟的时区
      --timeout Playwright 动作的超时时间(以毫秒为单位) 10000
      --user-agent 指定用户代理字符串
      --viewport-size 指定浏览器视窗大小 (以像素为单位),如:"1280, 720"
      -h, --help 显示命令帮助
    • 示例

      1
      playwright codegen -o script.py -b firefox
      • 这时候就弹出了一个 Firefox 浏览器,同时右侧会输出一个脚本窗口,实时显示当前操作对应的代码。
      • 我们可以在浏览器中做任何操作,操作完毕之后,关闭浏览器,Playwright 会生成一个 script.py 文件。
      • 运行该文件,就可以看到它又可以复现我们刚才所做的操作了。
  • 移动端浏览器支持

    • Playwright 另外一个特色功能就是可以支持移动端浏览器的模拟,比如模拟打开 iPhone 12 Pro Max 上的 Safari 浏览器,然后手动设置定位,并打开百度地图并截图。首先我们可以选定一个经纬度,比如故宫的经纬度是 39.913904, 116.39014,我们可以通过 geolocation 参数传递给 Webkit 浏览器并初始化。
    • 示例代码如下:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      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()
  • 文本选择器

    • 同步
      1
      page.locator("text=Log in").click()
    • 异步
      1
      await page.locator("text=Log in").click()
    • 说明
      • 默认匹配不区分大小写并搜索子字符串
  • CSS 选择器

    • 同步
      1
      page.locator("button").click()
    • 异步
      1
      await page.locator("button").click()
    • 说明
      • css 默认情况下,引擎会穿透打开的影子 DOM。
      • Playwright 添加了自定义伪类,例如 :visible:text 等等。
  • XPath 选择器

    • 同步
      1
      page.locator("xpath=//button").click()
    • 异步
      1
      await page.locator("xpath=//button").click()
    • 说明
      • // 或者 .. 开头的选择器被假定为 xpath 选择器。
      • 需要在开头指定 xpath= 字符串,代表后面是一个 XPath 表达式。
  • 文字输入

    • 说明
      这是填写表单域的最简单方法。input 它聚焦元素并使用输入的文本触发事件。它适用于 <input><textarea>[contenteditable]<label> 等与输入或文本区域相关联的标签。
    • 同步
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      # Text input
      page.fill('#name', 'Peter')

      # Date input
      page.fill('#date', '2020-02-02')

      # Time input
      page.fill('#time', '13:15')

      # Local datetime input
      page.fill('#local', '2020-03-02T05:15')

      # Input through label
      page.fill('text=First Name', 'Peter')
    • 异步
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      # Text input
      await page.fill('#name', 'Peter')

      # Date input
      await page.fill('#date', '2020-02-02')

      # Time input
      await page.fill('#time', '13:15')

      # Local datetime input
      await page.fill('#local', '2020-03-02T05:15')

      # Input through label
      await page.fill('text=First Name', 'Peter')
  • 复选框和单选按钮

    • 说明
      这是选中和取消选中复选框或单选按钮的最简单方法。此方法可与 input[type=checkbox]input[type=radio][role=checkbox]label 与复选框或单选按钮相关联的标签一起使用。
    • 同步
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      # Check the checkbox
      page.check('#agree')

      # Assert the checked state
      assert page.is_checked('#agree') is True

      # Uncheck by input <label>.
      page.uncheck('#subscribe-label')

      # Select the radio button
      page.check('text=XL')
    • 异步
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      # Check the checkbox
      await page.check('#agree')

      # Assert the checked state
      assert await page.is_checked('#agree') is True

      # Uncheck by input <label>.
      await page.uncheck('#subscribe-label')

      # Select the radio button
      await page.check('text=XL')
  • 选择选项

    • 说明
      选择 <select> 元素中的一个或多个选项。您可以指定选项 valuelabelelementHandle 选择。可以选择多个选项。
    • 同步
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      # Single selection matching the value
      page.select_option('select#colors', 'blue')

      # Single selection matching the label
      page.select_option('select#colors', label='Blue')

      # Multiple selected items
      page.select_option('select#colors', ['red', 'green', 'blue'])

      # Select the option via element handle
      option = page.query_selector('#best-option')
      page.select_option('select#colors', option)
    • 异步
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      # Single selection matching the value
      await page.select_option('select#colors', 'blue')

      # Single selection matching the label
      await page.select_option('select#colors', label='Blue')

      # Multiple selected items
      await page.select_option('select#colors', ['red', 'green', 'blue'])

      # Select the option via element handle
      option = await page.query_selector('#best-option')
      await page.select_option('select#colors', option)
  • 鼠标点击

    • 说明
      执行简单的人工点击。
    • 同步
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      # Generic click
      page.click('button#submit')

      # Double click
      page.dblclick('#item')

      # Right click
      page.click('#item', button='right')

      # Shift + click
      page.click('#item', modifiers=['Shift'])

      # Hover over element
      page.hover('#item')

      # Click the top left corner
      page.click('#item', position={ 'x': 0, 'y': 0})
    • 异步
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      # Generic click
      await page.click('button#submit')

      # Double click
      await page.dblclick('#item')

      # Right click
      await page.click('#item', button='right')

      # Shift + click
      await page.click('#item', modifiers=['Shift'])

      # Hover over element
      await page.hover('#item')

      # Click the top left corner
      await page.click('#item', position={ 'x': 0, 'y': 0})
  • 输入字符

    • 说明
      一个字符一个字符地输入字段,就好像它是一个使用真正键盘的用户一样。
    • 同步
      1
      2
      # Type character by character
      page.type('#area', 'Hello World!')
    • 异步
      1
      2
      # Type character by character
      await page.type('#area', 'Hello World!')
  • 键和快捷键

    • 说明
      此方法聚焦所选元素并产生单个击键。它接受在键盘事件的 keyboardEvent.key 中发出的逻辑键名。
    • 同步
      1
      2
      3
      4
      5
      6
      7
      8
      # Hit Enter
      page.press('#submit', 'Enter')

      # Dispatch Control+Right
      page.press('#name', 'Control+ArrowRight')

      # Press $ sign on keyboard
      page.press('#value', '$')
    • 异步
      1
      2
      3
      4
      5
      6
      7
      8
      # Hit Enter
      await page.press('#submit', 'Enter')

      # Dispatch Control+Right
      await page.press('#name', 'Control+ArrowRight')

      # Press $ sign on keyboard
      await page.press('#value', '$')
  • 上传文件

    • 说明
      您可以使用 age.set_input_files(selector, files, **kwargs) 方法选择在该页面要上传的输入文件。它期望第一个参数是指向类型为 "file"输入元素 。可以在数组中传递多个文件。如果某些文件路径是相对的,则它们将相对于当前工作目录进行解析。空数组清除所选文件。
    • 同步
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      # Select one file
      page.set_input_files('input#upload', 'myfile.pdf')

      # Select multiple files
      page.set_input_files('input#upload', ['file1.txt', 'file2.txt'])

      # Remove all the selected files
      page.set_input_files('input#upload', [])

      # Upload buffer from memory
      page.set_input_files(
      "input#upload",
      files=[
      {"name": "test.txt", "mimeType": "text/plain", "buffer": b"this is a test"}
      ],
      )
    • 异步
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      # Select one file
      await page.set_input_files('input#upload', 'myfile.pdf')

      # Select multiple files
      await page.set_input_files('input#upload', ['file1.txt', 'file2.txt'])

      # Remove all the selected files
      await page.set_input_files('input#upload', [])

      # Upload buffer from memory
      await page.set_input_files(
      "input#upload",
      files=[
      {"name": "test.txt", "mimeType": "text/plain", "buffer": b"this is a test"}
      ],
      )
  • 焦点元素

    • 说明
      对于处理焦点事件的动态页面,您可以聚焦给定元素。
    • 同步
      1
      page.focus('input#name')
    • 异步
      1
      await page.focus('input#name')