web自动化 - Playwrigh Python

介绍

官方文档 | Playwright Python
api文档
https://zhuanlan.zhihu.com/p/336679365
测试工程师成长之路公众号

GitHub - QIN2DIM/undetected-playwright: 隐藏playwright特征
Installation | Playwright Python

Playwright是一个强大的Python库,仅用一个API即可自动执行Chromium、Firefox、WebKit等主流浏览器自动化操作,并同时支持以无头模式、有头模式运行。

  • Playwright是微软开源的一个UI自动化测试工具。添加了默认等待时间增加脚本稳定性,并提供视频录制、网络请求支持、自定义的定位器、自带调试器等新特性。
  • Selenium需要通过WebDriver操作浏览器;Playwright通过开发者工具与浏览器交互,安装简洁,不需要安装各种Driver。
  • 跨语言:Playwright几乎支持所有语言,且不依赖于各种Driver,通过调用内置浏览器所以启动速度更快。
  • 跨平台: win ,linux,mac
  • Selenium基于HTTP协议(单向通讯),Playwright基于Websocket(双向通讯)可自动获取浏览器实际情况。
  • Playwright为自动等待:等待元素出现(定位元素时,自动等待30s,时间可以自定义,单位毫秒)
  • Playwright支持异步方式
  • 浏览器上下文:playwright为每个测试创建一个浏览器上下文,浏览器上下文相当于一个全新的浏览器配置文件,创建一个上下文只需要几毫秒
  • 登录一次:保存上下文的身份验证状态并在所有测试中重用,

环境配置

# 安装playwright库
pip install playwright

安装浏览器驱动

  • Playwright执行自动化操作依赖指定的浏览器驱动,Chromium/FireFox/WebKit。如果你本地已经下载过要使用的浏览器,就不用下载驱动了,如果遇到报错,说明你浏览器没正确安装,重新安装一次chrome浏览器,按默认的路径安装即可。
  • 也可以选择直接使用本地的浏览器,这样就不用下载驱动了,只需在浏览器对象中设置channel="chrome"他将会自动寻找本地的chrome context = p.chromium.launch_persistent_context(channel="chrome")
playwright -h 命令查看目前支持的浏览器


# 安装Chromium、Firefox、WebKit等浏览器的驱动文件
python -m playwright install

#安装特定浏览器驱动( C:\Users\wenke\AppData\Local\ms-playwright)
python -m playwright install chromium 
firefox

,默认情况下,Playwright从Microsoft和Google公共CDN将Chromium,WebKit和Firefox浏览器下载到特定于操作系统的缓存文件夹中:

(Windows)%USERPROFILE%\AppData\Local\ms-playwright
(MacOS)~/Library/Caches/ms-playwright
(Linux)~/.cache/ms-playwright

这些浏览器的大小:281M chromium-XXXXXX、187M firefox-XXXX、180M webkit-XXXX,另外还有一个几兆大小的FFMPEG(音视频库)

playwright install --dry-run

它会根据你当前安装的playwright ,给出对应的浏览器最近匹配版本,以及下载地址

打包

要打包的时候,浏览器也要放在打包的文件夹内,就牵涉到浏览器安装位置的问题,在playwright官网说明中就写了下载浏览器导指定位置,而不是默认位置的方法:

set PLAYWRIGHT_BROWSERS_PATH=绝对地址
playwright install chromium

使用pyinstaller打包

set PLAYWRIGHT_BROWSERS_PATH=0playwright install chromium
pyinstaller -F main.py
$env:PLAYWRIGHT_BROWSERS_PATH="0"playwright install chromium
pyinstaller -F main.py

使用Jupyter运行

Jupyter notebook使用asyncio事件循环,所以你应该使用async API。
但是我在使用async API好像也无法运行

录制脚本

python -m playwright codegen option
playwright codegen cr https://www.baidu.com
python -m playwright codegen --target python -o './demo.py' -b chromium https://www.baidu.com

options参数:
-o:将录制的脚本保存到一个文件
–target:规定生成脚本的语言,有JS和Python两种,默认为Python
-b:指定浏览器驱动
–color-scheme=dark:模拟配色
–viewport-size=800,600:设置为特定的宽度和高度
–device=“iPhone 11” 使用设置视口大小和用户代理等选项模拟移动设备时记录脚本和测试。模拟移动设备iPhone11,注意:device的值必须用双引号,并且区分大小写
模拟地理位置、语言和时区
–timezone=“Europe/Rome”
–geolocation=“41.890221,12.492348”
–lang=“it-IT” maps.google.com
–save-storage=auth.json:执行身份验证并关闭浏览器后,auth.json将包含存储状态。
–load-storage=auth.json:加载加载的save-storage

选项:
  -V, --version                          输出版本号
  -b, --browser <browserType>            浏览器类型
  --color-scheme <scheme>                更改主题 取值 "light" 或 "dark"
  --device <deviceName>                  模拟设备,例如  "iPhone 11"
  --geolocation <coordinates>            指定地理位置 例如 "37.819722,-122.478611"
  --lang <language>                      指定语言区域 "en-GB"
  --save-storage <filename>          保存浏览器状态到指定文件
  --load-storage <filename>              载入指定文件浏览器状态
  --proxy-server <proxy>                 指定代理服务器 例如 "http://myproxy:3128" 或 "socks5://myproxy:8080"
  --timezone <time zone>                 失去设置 例如 "Europe/Rome"
  --timeout <timeout>                    超时设置,单位毫秒 (default: "10000")
  --user-agent <ua string>               指定UA
  --viewport-size <size>                 指定浏览器像素 "1280, 720"
命令:
  open [url]                             打开URL或用-b, --browser指定浏览器
  cr [url]                               打开URL用Chromium
  ff [url]                               打开URL用Firefox
  wk [url]                               打开URL用WebKit
  codegen [options] [url]                打开页面生成代码
  screenshot [options] <url> <filename>  页面截图
  pdf [options] <url> <filename>         保存页面为pdf
  install                                确保安装必要的浏览器驱动
  help [command]                         帮助

启动后,电脑上出现2个窗口,左边是浏览器打开网站可以操作,右边是inspector 可以生成对应的脚本

点击target
可以选择生成异步代码,
如果你是写自动化测试用例,还能自动生成 pytest 框架的代码

page.pause() 断点调试

如果您想在某些非标准设置中使用 codegen(例如,使用browser_context.route()),可以调用page.pause(),这将打开一个带有 codegen 控件的单独窗口。


from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    # Make sure to run headed.
    browser = p.chromium.launch(headless=False)

    # Setup context however you like.
    context = browser.new_context() # Pass any options
    context.route('**/*', lambda route: route.continue_())

    # Pause the page, and start recording manually.
    page = context.new_page()
    page.pause()

同步和异步

Playwright支持API的两种编写模式:同步和异步。如果你的项目使用asyncio,你应该使用async API,否则使用sync API。

默认情况下,Playwright以非GUI模式运行浏览器。要查看浏览器UI,请在启动浏览器时传参数headless=False。还可以使用slow_mo来减慢执行速度,如slow_mo=50。
同步模式

import time

import pytest
from playwright.sync_api import sync_playwright

"""
使用同步模式打开博客园,并断言标题
"""
def test_sync_playwright():
with sync_playwright() as p:
	# headless=False表示使用GUI模式运行
	browser = p.chromium.launch(headless=False)
	page = browser.new_page()
	page.goto("https://www.cnblogs.com/mrjade/")
	time.sleep(5)
	print(page.title())
	// 截图并保存在当前目录下
	page.screenshot(path="cnblogs_ishot.png")
	browser.close()

if __name__ == '__main__':
	pytest.main("-v","test_sync_playwright.py")

异步模式

import asyncio
import time

from playwright.async_api import async_playwright

"""
1.使用异步模式分别打开chromium/firefox/webkit浏览器
2.浏览器输入https://www.cnblogs.com/mrjade/
3.截图保存在ishot目录下
"""

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('https://www.cnblogs.com/mrjade/')
			await page.screenshot(path=f'ishot/screenshot-{browser_type.name}.png')
			time.sleep(5)
			print(await page.title())
			await browser.close()

asyncio.run(main())
import asyncio
from playwright.async_api import async_playwright


async def run(url):
    async with async_playwright() as playwright:
        # create a chromium browser instance
        chromium = playwright.chromium
        # browser = await chromium.launch()
        #create a bowser instance which headless is true
        browser = await chromium.launch(headless=False)
        # create two isolated browser contexts
        user_context = await browser.new_context()
        page = await user_context.new_page()
        await page.goto(url)
        # await page2.goto('https://www.baidu.com')


async def main():
    tasks =[]
    urls = ['https://www.baidu.com','https://www.taobao.com','https://www.jd.com']
    for url in urls:
        task = asyncio.ensure_future(run(url))
        tasks.append(task)
    await asyncio.gather(*tasks)



if __name__ == '__main__':
    asyncio.run(main())


浏览器对象

launch

  • playwright.chromium.launch(channel=‘chrome’,headless=False, slow_mo=50) 返回Browser对象
    • headless : 无头模式
    • slow_mo : (单位是毫秒)减慢执行速度。全局作用,每个动作都会有等待间隔
    • channel : channel=‘chrome’,使用本地浏览器

Playwright 在查找元素的时候具有自动等待功能,如果你在调试的时候需要使用等待,你应该使用page.wait_for_timeout(5000) 代替 time.sleep(5)并且最好不要等待超时。


from playwright.sync_api import sync_playwright
from loguru import logger
from datetime import datetime
from pathlib import Path
# 初始化日志器
# enqueue=True: 表示使用异步写入日志,加速日志记录的速度。
logger.add(f"./logs/{datetime.now().strftime('%Y-%m-%d')}.log",
           format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> |<cyan>line:{line}</cyan> | <level>{message}</level>",
           rotation="1 day",
           encoding="utf-8",
           enqueue=True,
           retention="10 days",
           compression="zip")


state_path = Path("./state.json")
js = """
        Object.defineProperties(navigator, {webdriver:{get:()=>undefined}});
    """
# win11chrome.exe指定可执行文件路径
executable_path = f"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"


def main():
    with sync_playwright() as p:
        # headless=False表示使用GUI模式运行
        browser = p.chromium.launch(headless=False, executable_path=executable_path)
        url = "https://www.baidu.com/"

        if not state_path.exists():
            # auth_path.parent.mkdir(parents=True, exist_ok=True)
            # auth_path.touch()
            context = browser.new_context()
            page = context.new_page()
            # 绕过Webdriver检测的第二种方法
            page.add_init_script(js)
            page.goto(url)
            input('请登录后按回车键')
            storage = context.storage_state(path=state_path)  # 保存cookie保存登录状态
            logger.info('如果没有登录,请删除state.json文件,重新运行程序')

        else:
            context = browser.new_context(storage_state=state_path)  # 读取cookie
            logger.info(f'{url}已打开')
            page = context.new_page()  # 打开新页面
            # 绕过Webdriver检测的第二种方法
            page.add_init_script(js)
            page.goto(url)  # 打开网页
            logger.info(f'{url}已打开')

        context.close()
        browser.close()


main()



launch_persistent_context

playwright 启动Google Chrome 浏览器的时候默认用的是无痕模式

  • chromium.launch_persistent_context() 返回BrowserContext对象
    • user_data_dir:指定用户缓存文件,自动存储cookie,一个用户缓存文件只能打开一个浏览器进程
    • proxy=ProxySettings(server=“http://xxx.xxx.xxx.xxx:xxxx”) :使用代理
    • channel=‘chrome’ 指定本地浏览器打开,而不是驱动
    • executable_path:指定浏览器驱动文件路径,不使用这个,默认调用本地浏览器
    • record_video_dir=“videos/”:录制视频地址

List of Chromium Command Line Switches « Peter Beverloo


from playwright.sync_api import sync_playwright
from pathlib import Path


userdata_dir = Path("./userdata/").resolve()  # 这里要绝对路径
if not userdata_dir.is_dir():
    userdata_dir.mkdir()

with sync_playwright() as p:
    # 返回BrowserContext对象
    context = p.chromium.launch_persistent_context(
        # 指定本机用户缓存地址
        user_data_dir=userdata_dir,
	    channel="chrome", # 指定本地浏览器,默认路径
        # 设置 GUI 模式
        headless=False,
        #控制台输入window.navigator.webdriver查看是否跳过
        args=[
            #绕过Webdriver检测的第一种方法
            '--disable-blink-features=AutomationControlled', #绕过Webdriver检测
            # 禁用窗口大小
            '--start-maximized'
        ],
        # 禁用窗口大小
        no_viewport=True)

    page = context.new_page()
    page.goto("https://www.gitee.com")



args = [

    "--start-maximized",  # 启动浏览器时最大化窗口
    "--disable-notifications",  # 禁用浏览器通知
    "--disable-infobars",  # 禁用浏览器信息栏
    "--no-sandbox",  # 在 Linux 上禁用沙箱模式
    '--disable-blink-features=AutomationControlled', #绕过Webdriver检测
    #模拟 Chrome 浏览器的 User-Agent 字符串
    "--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36",

	#不常用
    "--disable-extensions",  # 禁用浏览器扩展
    "--disable-dev-shm-usage",  # 禁用共享内存
    '--lang=zh'    #设置浏览器的语言。
    "--disable-gpu",  # 禁用 GPU 加速
    '--headless',  #以无头模式运行浏览器,即在后台运行,没有图形界面。
    '--proxy-server=proxy_address:proxy_port',  #设置代理服务器的地址和端口。
]

启动一个指定路径浏览器

win11chrome.exe可执行文件路径
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"

import getpass
# 获取 google chrome 的本地缓存文件
USER_DIR_PATH = f"C:\\Users\\{getpass.getuser()}\\AppData\Local\Google\Chrome\\User Data"

打开正常浏览器(推荐)

使用chromium.connect_over_cdp方法打开一个正常的浏览器

  • endpoint_url
  • timeout
  • slow_mo
  • headers
    让playwright继续在你已经打开的浏览器上操作。

使用playwright自动化模拟浏览器操作的时候,会被识别到这是一个playwright启动的自动化程序,利用playwright 打开本地的浏览器的方式,可以避免反爬。

自动打开特定端口的浏览器

import subprocess
# 这个路径可以是Google浏览器的exe路径,也可以是快捷方式的路径
chrome_path = r'"C:\Program Files\Google\Chrome\Application\chrome.exe"'
command = f"{chrome_path} --remote-debugging-port=9999"
subprocess.Popen(command)


with sync_playwright() as p:
   # 创建一个连接
   browser = p.chromium.connect_over_cdp("http://localhost:9999")
   content = browser.contexts[0]
   page = content.new_page()

第一种方式
复制浏览器地址C:\Program Files\Google\Chrome\Application 添加到环境变量Path下

打开cmd输入命令启动chrome浏览器

  • remote-debugging-port 是指定运行端口,只要没被占用就行
  • user-data-dir 指定运行浏览器的运行数据,新建一个干净目录,不影响系统原来的数据
  • incognito 隐私模式打开
  • start-maximized: 窗口最大化
  • new-window:直接打开网址

chrome.exe --remote-debugging-port=9999 --incognito --start-maximized --user-data-dir="D:\demo" --new-window https://www.baidu.com

第二种方式:修改浏览器的属性
浏览器右键 - 属性 - 快捷方式 - 目标 - 添加下面的命令
--remote-debugging-port=9999

接管已打开的浏览器(在运行前必须只有打开自定义端口的浏览器)
当页面打开后,可以使用connect_over_cdp()方法接管前面已经打开的浏览器,获取到context 上下文,通过上下文再获取到page对象

from playwright.sync_api import sync_playwright


with sync_playwright() as p:
	browser = p.chromium.connect_over_cdp('http://localhost:9999/')
	# 获取page对象
	page = browser.contexts[0].pages[0]
	print(page.url)
	print(page.title())
	page.get_by_text('新随笔').click()

保存登录cookies

无痕模式就是通常情况下理解的浏览器不跟踪用户行为的模式,在此模式下无法保持登录状态。这也是Playwright打开浏览器窗口的默认模式。如果需要保持登录状态,那么需要使用Playwright提供的storage_stateAPI:

Playwright 提供了一种在测试中重用登录状态的方法。这样您就可以只登录一次,然后跳过所有测试的登录步骤。

Web 应用程序使用基于 cookie 或基于令牌的身份验证,其中经过身份验证的状态存储为cookie或本地存储。
Playwright 提供browserContext.storageState([options])方法,可用于从经过身份验证的上下文中检索存储状态,然后创建具有预填充状态的新上下文。
Cookie 和本地存储状态可以跨不同的浏览器使用。它们取决于您的应用程序的身份验证模型:某些应用程序可能需要 cookie 和本地存储。

这样在其它地方就可以使用本地的cookies,实现免登录了

from playwright.sync_api import sync_playwright

from pathlib import Path


def main():
	with sync_playwright() as p:
	# headless=False表示使用GUI模式运行
		browser = p.chromium.launch(channel="chrome", headless=False)
		url = "https://www.gitee.com/"
		auth_path = Path('auth/state.json')
		if auth_path.exists():
			context = browser.new_context(storage_state="auth/state.json")  # 读取cookie
			page = context.new_page()  # 打开新页面
			page.goto(url)  # 打开网页
		else:
			auth_path.parent.mkdir(parents=True, exist_ok=True)
			auth_path.touch()
			context = browser.new_context()
			page = context.new_page()
			page.goto(url)
			input('请登录后按回车键')
			storage = context.storage_state(path="auth/state.json")  # 保存cookie保存登录状态
		
		

main()


BrowserContext上下文

  • browser.new_context() 创建context对象,context之间是相互隔离的,它不会与其他浏览器上下文共享 cookies/缓存。可以理解为轻量级的浏览器实例. 相当于一个窗口
    • base_url:全局配置url

如需要不同用户登录同一个网页,不需要创建多个浏览器实例,只需要创建多个context即可

打开两个浏览器窗口:通过context 上下文操作多个浏览器实例,它不会与其他浏览器上下文共享 cookies/缓存, 适用于多用户同时登陆的场景。

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False, slow_mo=1000)
    context1 = browser.new_context()  # 创建上下文,浏览器实例1
    context2 = browser.new_context()  # 创建上下文,浏览器实例2
    context = browser.new_context(base_url='https://www.cnblogs.com')
    page1 = context1.new_page()    # 打开标签页1
    page1.goto("https://www.baidu.com/")

    # 操作第二个浏览器窗口
    page2 = context2.new_page()  # 打开标签页1
    page2.goto("https://www.baidu.com/")

打开两个窗口标签页

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False, slow_mo=1000)
    context = browser.new_context()  # 创建上下文,浏览器实例
    page = context.new_page()    # 打开标签页1
    page.goto("https://www.baidu.com/")
    page2 = context.new_page()  # 打开标签页2
    page2.goto("https://www.cnblogs.com/yoyoketang/")

    context.close()
    browser.close()

切换多个窗口标签页

page对象.bring_to_front() 激活当前选项卡

from playwright.sync_api import sync_playwright


def switch_to_page(context, title=None, url=None):
    """切换到指定title 名称 或 url 的 标签页"""
    for item_page in context.pages:
        if title:
            if title in item_page.title():
                # 激活当前选项卡
                item_page.bring_to_front()
                return item_page
        elif url:
            if url in item_page.url:
                # 激活当前选项卡
                item_page.bring_to_front()
                return item_page
    else:
        print("not found title or url")
    return context.pages[0]

with sync_playwright() as playwright:
    browser = playwright.chromium.launch(headless=False)
    context = browser.new_context()
    page = context.new_page()
    page.goto('https://www.baidu.com')

    # 点开多个标签页
    for link in page.locator('#s-top-left>a').all():
        link.click()

    # 打开多个tab 标签页, 切换
    page1 = switch_to_page(context, title='hao')
    print(page1.title())

窗口大小

禁用窗口大小
playwright 默认启动的浏览器窗口大小是1280x720, 我们可以通过设置no_viewport参数来禁用固定的窗口大小
设置args参数--start-maximized 并且设置no_viewport=True

from playwright.sync_api import sync_playwright


with sync_playwright() as p:
    browser = p.chromium.launch(
        headless=False,
        args=['--start-maximized']
    )
    context = browser.new_context(no_viewport=True)
    page = context.new_page()

    page.goto("https://www.cnblogs.com/yoyoketang/")
    page.pause()

viewport 指定窗口大小
如果你不是通过context 上下文创建的page对象,直接通过browser创建的page,那么参数直接写到new_page位置

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False, )

    page = browser.new_page(viewport={'width': 1920, 'height': 1080})

    page.goto("https://www.cnblogs.com/yoyoketang/")
    page.pause()

设定机型

只需配置您想要模拟的设备,Playwright 就会模拟浏览器行为,例如”userAgent”、”screenSize”以及”viewport”是否”hasTouch”启用。

from playwright.sync_api import sync_playwright

def run(playwright):
    iphone_12 = playwright.devices['iPhone 12']
    browser = playwright.chromium.launch(headless=False)
    context = browser.new_context(
        **iphone_12,
    )
    page = context.new_page()
    page.goto('https://m.baidu.com')
    page.pause()

with sync_playwright() as playwright:
    run(playwright)

页面(page)对象

我们从以往的例子中会看出,每个脚本都会使用page = context.new_page(),该代码相当于手动创建了一个新的浏览器tab页。如果我们想打开多个浏览器tab页,又该如何操作呢

  • context.pages 可以获取到所有的page对象
# create two pages
browser = playwright.chromium.launch(headless=False) 

context = browser.new_context()   #创建一个浏览器上下文
page_one = context.new_page()     #创建新的标签页
page_two = context.new_page()    

# 打开URL  
page.goto(url, **kwargs)  
 # 获取页面源代码
page_source = page.content()  
page.reload(**kwargs)     # 重新加载当前页面  
page.go_back(**kwargs)    # 回退  
page.go_forward(**kwargs)   # 前进  


#打开调试器
page.pause()
context.close()   #关闭浏览器上下文
browser.close()   #关闭浏览器


处理新页面

如果页面打开一个弹出窗口(例如通过链接打开的页面),可以通过监听页面上的事件target="_blank"来获得对它的引用,比如注册126邮箱,就用此方法,不知道的小伙伴可以查看下往期文章。


with page.expect_popup() as popup_info:
	page.locator("#open").click()  #打开新链接的操作
	popup = popup_info.value   #获取page对象
	# 等待页面加载到指定状态
	popup.wait_for_load_state()
	print(popup.title())

如果触发弹出窗口的操作未知,则可以使用以下模式。


def handle_popup(popup):
    popup.wait_for_load_state()
    print(popup.title())

page.on("popup", handle_popup)

监听对话框

我们在日常工作中,会经常遇到弹出警告框的问题,弹框无法绕过,必须处理才可以执行后续的测试
弹框通常分为3种

  • alert弹框:只有信息和确定按键
  • confirm弹框:在alert弹窗基础上增加了取消按钮
  • prompt弹框:在confirm的基础上增加了可输入文本内容的功能

网页上的alert 弹出框你不知道什么时候弹出来,selenium 处理alert弹出框的方式是先判断有没alert 再处理,并且只能处理这一次。
playwright 框架可以监听dialog事件,不管你alert 什么时候弹出来,监听到事件就自动处理了。

当没有page.on(“dialog”)侦听器存在时,所有对话框都会自动关闭。 即不用处理

当监听器存在时,它必须dialog.accept()或dialog.dismiss()对话框 - 否则页面将冻结等待对话框,并且单击等操作将永远不会完成。

from playwright.sync_api import sync_playwright

def handle_dialog(dialog):
    """监听后处理"""
    print(dialog.message)
    dialog.dismiss()

def run(playwright):
    chromium = playwright.chromium
    browser = chromium.launch(headless=False, slow_mo=3000)
    page = browser.new_page()
    #通过`page.on("dialog", handler)` 监听到dialog 事件,可以获取到dialog.message内容
    page.on("dialog", handle_dialog)
    page.evaluate("alert('hello world')")
    browser.close()

with sync_playwright() as playwright:
    run(playwright)

dialog 属性和方法

#accept()当对话框被接受时返回。
dialog.accept()
dialog.accept(**kwargs)
#参数 prompt_text(可选), 要在提示中输入的文本。如果对话框 type 没有提示,则不会产生任何影响.

#default_value, 如果对话框是提示的,则返回默认提示值。否则,返回空字符串。
dialog.default_value

#dismiss 关闭对话框
dialog.dismiss()

#message 获取对话框中显示的消息
dialog.message

#type返回对话框的类型,可以是alert, beforeunload, confirm或 prompt其中一个。
dialog.type

弹出框

页面上一闪而过的提示语,一般出现1-3秒就会消失,这种消息框如何定位呢?

F12后点开源代码,然后点暂停按钮,这时候页面处于调试暂停状态了,再回到检测元素,鼠标指上去,就能看到元素属性了

from playwright.sync_api import sync_playwright, expect

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False, slow_mo=2000)
    context = browser.new_context()
    page = context.new_page()
    page.goto('file:///C:/Users/dell/Desktop/a2/t.html')

    # 点击success按钮
    page.locator('.btn-success').click()

    # 断言
    expect(page.locator('.toast-message')).to_have_text("操作成功!")

    page.pause()

执行js脚本

js="""
Object.defineProperties(navigator, {webdriver:{get:()=>undefined}});
"""

#绕过Webdriver检测的第二种方法
page.add_init_script(js)

通用

加载扩展插件

chrome插件目录,下载后解压crx,扩展程序仅在 Chrome / Chromium GUI模式中使用。
在使用脚本加载扩展插件时,一定要解压crx文件,不要直接安装crx
Google Chrome 插件加载需配置args 参数,
所有的 args 参数列表可以在这里查询 https://peter.sh/experiments/chromium-command-line-switches/
path_to_extension = f"\\Users\\Downloads\\extension\\xxx"



with sync_playwright() as p:
	# 返回BrowserContext对象
	context = p.chromium.launch_persistent_context(
		# 设置 GUI 模式
		headless=False,
		channel="chrome",
		加载扩展插件
		args=[
		    f"--disable-extensions-except={path_to_extension}",
		    f"--load-extension={path_to_extension}"
		],

	)


全局修改超时时间

browser_context.set_default_navigation_timeout(timeout)
browser_context.set_default_timeout(timeout)

page.set_default_navigation_timeout(timeout)
page.set_default_timeout(timeout)

set_default_timeout设置的时间对所有接受timeout参数的方法都有效

set_default_navigation_timeout设置的时间只对以下方法有效:

  • page.go_back()
  • page.go_forward()
  • page.goto()
  • page.reload()
  • page.set_content()
  • page.expect_navigation()

获取html内容

from playwright.sync_api import sync_playwright



with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()

page.goto("https://www.cnblogs.com/yoyoketang/")
#获取整个页面的HTML
print(page.content())
	# 获取某个元素的HTML
blog = page.locator('#blogTitle')
print(blog.inner_html())

元素.inner_html() 获取某个元素的HTML
元素.text_content() 用来获取某个元素内所有文本内容,包含子元素内容,隐藏元素也能获取。返回值不会被格式化,返回值依赖于代码的内容
元素.inner_text() 的返回值会被格式化,inner_text()返回的值, 依赖于页面的显示,

all_inner_texts() 和 all_text_contents() 也是用于获取页面上的文本,但是返回的是list列表

HTTP 代理

Playwright 还可以很方便地设置代理。

# 全局代理
PROXY_HTTP = "127.0.0.1:60002"
browser = playwright.chromium.launch(channel="chrome", headless=False, proxy={
"server": PROXY_HTTP,
"username": "",
"password": ""
})

在为每个上下文单独指定代理时,Windows 上的 Chromium需要提示将设置代理。这是通过将非空代理服务器传递给浏览器本身来完成的。

# 上下文的代理
PROXY_HTTP = "127.0.0.1:60002"
browser = playwright.chromium.launch(proxy={"server": "per-context"})
context = browser.new_context(proxy={"server": PROXY_HTTP})

设置浏览器请求头

set_extra_http_headers() 方法设置浏览器请求头部参数

page对象设置

from playwright.sync_api import sync_playwright, expectwith sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    context = browser.new_context()
    page = context.new_page()    # 设置请求头部(仅供参考示例)
    page.set_extra_http_headers(
        headers={  "Authorization": "Bearer "
        }
    )

在context对象设置

from playwright.sync_api import sync_playwright, expectwith sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    context = browser.new_context()    # 设置请求头部
    context.set_extra_http_headers(
        headers={ "Authorization": "Bearer "
        }
    )
    page = context.new_page()

iframe页面

iframe 是web自动化里面一个比较头疼的场景,在Selenium中处理 iframe 需要切换来切换去非常麻烦。
在playwright中,让其变得非常简单,我们在使用中无需切换iframe,直接定位元素即可。

frame_locator定位

FrameLocator | Playwright Python

要等待加载出来,聚焦

iframe 定位:可以使用page.frame_locator()或locator.frame_locator()方法创建 FrameLocator 捕获足该 iframe 中检索和定位元素。
iframe 定位器是严格的。这意味着如果有多个元素与给定的选择器匹配,则对 iframe 定位器的所有操作都会抛出异常。

=注意=:使用frame_locator() 定位到iframe上,然后继续在上面使用locator方法定位元素


frame1 = page.frame_locator("my-frame").get_by_text("Submit")  
frame1.click()



('.result-frame').first.get_by_role('button').click()



page.frame_locator('mainframe').last
page.frame_locator('mainframe').first # first 匹配第一个
page.frame_locator('mainframe').nth(index)  #还可以使用index索引

Frame objects

Frame | Playwright Python

还有一种frame()定位方法 ,可以根据name属性和url属性匹配

frame = page.frame(name="frame-name")
frame = page.frame(url=r".*domain.*")
# Interact with the frame
frame.fill('#username-input', 'John')

page.frame 和 page.frame_locator 使用差异

page.frame_locator(‘’) 返回的对象只能用locator() 方法定位元素然后click()等操作元素
page.frame() 返回的对象能直接使用fill() 和 click() 方法

# Get frame using the frame's name attribute
frame = page.frame('frame-login')

# Get frame using frame's URL
frame = page.frame(url=r'.*domain.*')

# Interact with the frame
frame.fill('#username-input', 'John')
frame.content() #获取框架的完整 HTML 内容,包括 doctype。
frame.title() #返回页面标题。
frame.name  #返回标记中指定的帧的 name 属性。,如果名称为空,则改为返回 id 属性。
frame.url #返回帧的 url。

获取所有iframe

page.frames 获取页面上所有的iframe

from playwright.sync_api import sync_playwright
  


with sync_playwright() as p:
    browser = p.chromium.launch(headless=False, slow_mo=1000)
    page = browser.new_page()
    page.goto("https://mail.163.com/")
    print(page.frames)
    for f in page.frames:
        print(f)

    page.wait_for_timeout(5000)
    browser.close()

模糊匹配

由于iframe 元素 id 属性是动态可变的id="x-URS-iframe1676960382133.3657" 可以使用xpath的contains 模糊匹配,或者css的正则匹配

from playwright.sync_api import sync_playwright


with sync_playwright() as p:
    browser = p.chromium.launch(headless=False, slow_mo=1000)
    page = browser.new_page()
    page.goto("https://mail.163.com/")

    # 操作 iframe 上的元素
    # frame = page.frame_locator("iframe[id^=x-URS-iframe]")
    # xpath 模糊匹配
    frame = page.frame_locator('//iframe[contains(@id, "x-URS-iframe")]')
    frame.locator('[name="email"]').fill('yoyoketang')
    frame.locator('[name="password"]').fill("123456")
    frame.locator('#dologin').click()

    page.wait_for_timeout(5000)
    browser.close()

测试

控制台调试

我们可以在 用代码打开的浏览器上f12 打开console页面,输入playwright.$(selector) 调试定位,selector 语法可以支持 playwright 的selector 定位的语法。

playwright.$('text=登录')

其它相关操作

  • playwright.$(selector)  使用实际的 Playwright 查询引擎查询 Playwright 选择器
  • playwright.$$(selector)   类似于 playwright.$,但是返回全部的匹配元素
  • playwright.inspect(selector) 在元素面板中显示元素(如果相应浏览器的 DevTools 支持)。
  • playwright.locator(selector) 使用实际的 Playwright 查询引擎查询 Playwright 元素
  • playwright.selector(element) 为给定元素生成选择器。

inspector

还可以在 playwright inspector 工具上点开启录制按钮,在页面上点点点,就可以生成对应的元素和操作

点击 Pick locator 后在浏览器上选择需要定位的元素,即可生成对应的 locator

page.pause()

Trace Viewer测试追踪

在执行自动化用例的过程中,出现一些不稳定偶然性的bug,需要复现bug, 还原bug出现的过程。于是需要追踪用例执行的过程。
Playwright Trace Viewer 是一个 GUI 工具,可让您探索记录的 Playwright 测试跟踪,这意味着您可以在测试的每个操作中前后移动,并直观地查看每个操作期间发生的情况。

from playwright.sync_api import Playwright, sync_playwright, expect

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

    # Start tracing before creating / navigating a page.
    # Trace Viewer追踪开始
    context.tracing.start(screenshots=True, snapshots=True, sources=True)
    # 打开页面
    page = context.new_page()
    page.goto('http://127.0.0.1:8000/login.html')
    page.get_by_label("用 户 名:").fill("yoyo")
    page.get_by_label("密     码:").fill("123456")
    page.locator("text=立即登录").click()

    context.tracing.stop(path="trace.zip")  # 结束Trace Viewer追踪,添加 tracing 的结束配置。
    context.close()
    browser.close()

with sync_playwright() as playwright:
    run(playwright)

这将记录跟踪并将其放入名为trace.zip.运行结束后在本地保存一个 trace.zip 功能,并且文件只有100K左右,占用空间很小

您可以使用 Playwright CLI 或在您的浏览器中打开保存的跟踪trace.playwright.dev。

playwright show-trace trace.zip

api测试

每个Playwright浏览器上下文都有与其关联的APIRequestContext实例,该实例与浏览器上下文共享cookie存储,可以通过browser_context.request或page.request访问。也可以通过调用api_request.new_context()手动创建一个新的APIRequest上下文实例。

expect 断言

playwright 提供了一个 expect方法 用于断言,还可以设置超时时间。

默认情况下,断言超时时间是5秒钟, 你也可以自定义超时时间.

from playwright.sync_api import expect

expect(page.get_by_text("操作成功")).to_be_visible(timeout=3000)

设置全局断言超时时间

from playwright.sync_api import expect

expect.set_options(timeout=8000)

expect 中添加第二个参数,设置自定义报错消息内容

expect(page.get_by_text("Name"), "should be logged in").to_be_visible()

作用

在 Selenium 中,断言(Assertion)是一种用于验证测试结果的技术。通过断言,你可以在自动化测试过程中检查网页内容、元素状态等是否与预期一致。断言在自动化测试中非常重要,因为它们允许你在测试过程中捕获问题和错误,从而确保你的应用程序在不同情况下都能正常工作。

以下是断言在 Selenium 中的作用:

  1. 验证网页内容:你可以使用断言来验证特定网页的文本内容、标题、链接等是否符合预期。这有助于确保页面显示的信息正确无误。

  2. 确认元素是否存在:断言可以用来确认页面上是否存在特定的元素,例如按钮、文本框、下拉列表等。这有助于验证页面的结构和布局是否正确。

  3. 验证元素属性和状态:你可以使用断言来验证元素的属性值是否正确,比如确认一个按钮的文本是否正确,或者确认一个复选框是否被选中。

  4. 验证页面跳转:在页面跳转的情况下,你可以使用断言来验证是否成功跳转到了目标页面,或者页面跳转后的URL是否正确。

  5. 确认特定元素是否可见或可操作:断言可以帮助你确认特定的元素是否在页面中可见、可点击或可输入,从而验证用户界面的交互性。

  6. 检测错误和异常:通过断言,你可以捕获测试中的错误和异常情况,例如页面没有加载完全、元素找不到等情况。

节点断言

断言描述
expect(locator).to_be_checked()Checkbox is checked
expect(locator).to_be_disabled()Element is disabled
expect(locator).to_be_editable()Element is enabled
expect(locator).to_be_empty()Container is empty
expect(locator).to_be_enabled()Element is enabled
expect(locator).to_be_focused()Element is focused
expect(locator).to_be_hidden()Element is not visible
expect(locator).to_be_visible()Element is visible
expect(locator).to_contain_text()Element contains 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(api_response).to_be_ok()Response has an OK status

页面断言

主要有四个断言方法

  • to_have_title
  • not_to_have_title
  • to_have_url
  • not_to_have_url

参数:

  • title_or_reg_exp  预期的标题或正则表达式。
  • timeout  (可选) 超时时间
import re
from playwright.sync_api import expect

# ...
expect(page).to_have_title(re.compile(r".*checkout"))
#not_to_have_title 与expect(page).to_have_title()相反。
expect(page).not_to_have_title(title_or_reg_exp)
expect(page).not_to_have_title(title_or_reg_exp, **kwargs)

url断言

import re
from playwright.sync_api import expect

#to_have_url 确保页面导航到给定的 URL。
expect(page).to_have_url(re.compile(".*checkout"))

#not_to_have_url 与expect(page).to_have_url()相反。
expect(page).not_to_have_url(url_or_reg_exp)
expect(page).not_to_have_url(url_or_reg_exp, **kwargs)

列表断言

<ul>
  <li>apple</li>
  <li>banana</li>
  <li>orange</li>
</ul>
#使用 count 断言可确保列表包含 3 个项目。
expect(page.get_by_role("listitem")).to_have_count(3)
#使用 expect(locator).to_have_text() 确保列表包含文本“apple”、“banana”和“orange”。
expect(page.get_by_role("listitem")).to_have_text(["apple", "banana", "orange"])

Mock 接口返回

web 自动化主要测前端UI 的功能,有很多异常的场景,我们很难造真实的场景去触发,比如服务器异常时候,前端的提示语。
这时候就可以使用mock 功能,模拟接口的返回,测试前端的功能。

模拟网络请求

Web API 通常作为 HTTP 端点实现。Playwright 提供 API 来模拟和修改网络流量,包括 HTTP 和 HTTPS。页面执行的任何请求,包括XHR和获取请求,都可以被跟踪、修改和模拟。

以下代码将拦截对的所有调用https://dog.ceo/api/breeds/list/all并将返回测试数据。不会向https://dog.ceo/api/breeds/list/all端点发出任何请求。

async def handle(route):
    json = { message: { "test_breed": [] } }
    route.fulfill(json=json)

page.route("https://dog.ceo/api/breeds/list/all", handle)

修改 API

有时,必须发出 API 请求,但需要修补响应以允许可重现的测试。在这种情况下,可以执行请求并使用修改后的响应来完成请求,而不是模拟请求。

def handle(route):
    response = route.fulfill()
    json = response.json()
    json["message"]["big_red_dog"] = []
    # Fulfill using the original response, while patching the response body
    # with the given JSON object.
    route.fulfill(response=response, json=json)

page.route("https://dog.ceo/api/breeds/list/all", handle)

模拟登录

模拟登录时候,服务器异常的场景

当登录的接口返回状态码是500 的时候,前端才会触发:服务器异常!

于是可以用 page.route() 拦截请求,修改状态码即可触发

from playwright.sync_api import Playwright, sync_playwright, expect
  


def handle(route):
    # 状态码改成500 模拟服务器异常
    route.fulfill(status=500)

def run(playwright: Playwright) -> None:
    browser = playwright.chromium.launch(headless=False)
    context = browser.new_context()
    page = context.new_page()
    page.goto("http://127.0.0.0:8000/login.html")
    page.get_by_placeholder("请输入用户名").click()
    page.get_by_placeholder("请输入用户名").fill("yoyo")
    page.get_by_placeholder("请输入密码").click()
    page.get_by_placeholder("请输入密码").fill("aa123456")

    page.route("http://47.108.155.10/api/login", handle)

    page.get_by_role("button", name="立即登录 >").click()
    page.pause()  # 断点
    # ---------------------
    context.close()
    browser.close()

with sync_playwright() as playwright:
    run(playwright)

事件

Playwright 允许监听网页上发生的各种类型的事件,例如网络请求、子页面的创建、 dedicated workers等。

等待特定事件

大多数时候,脚本需要等待特定事件的发生。下面是一些典型的事件等待模式。

使用page.expect_request()等待具有指定 url 的请求:


with page.expect_request("**/*logo*.png") as first:  
	page.goto("https://wikipedia.org")
print(first.value.url)

等待弹出窗口:

with page.expect_popup() as popup:  
	page.get_by_text("open the popup").click()
popup.value.goto("https://wikipedia.org")

添加/删除事件

有时,事件在随机时间发生,而不是等待它们,它们需要被处理。Playwright 支持用于订阅和取消订阅事件的传统语言机制:

添加事件使用page.on('event', handle)

def print_request_sent(request):
  print("Request sent: " + request.url)

def print_request_finished(request):
  print("Request finished: " + request.url)

page.on("request", print_request_sent)
page.on("requestfinished", print_request_finished)
page.goto("https://wikipedia.org")

删除事件使用 page.remove_listener("event", print_request_finished)

page.remove_listener("requestfinished", print_request_finished)
page.goto("https://www.openstreetmap.org/")

添加一次性事件

如果某个事件需要处理一次,有一个方便的 API:

page.once("dialog", lambda dialog: dialog.accept("2021"))
page.evaluate("prompt('Enter a number:')")

网络事件

Playwright还可以监控所有网络请求和响应。

我们以博客园以例,可以看到所有GET和POST请求以及响应均被打印了出来。

import time

from playwright.sync_api import Playwright, sync_playwright


def run(playwright: Playwright):
browser = playwright.chromium.launch(channel="chrome", headless=False)
context = browser.new_context()
page = context.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://www.cnblogs.com/mrjade")
page.close()


with sync_playwright() as playwright:
run(playwright)

外设操作

鼠标操作

mouse.move() 起点或终点坐标位置

mouse.down() 按住鼠标

mouse.up()  释放鼠标


# 鼠标悬停  
元素.hover()
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
browser = p.chromium.launch(headless=False, slow_mo=1000)
context = browser.new_context()
page = context.new_page()

page.goto('file:///C:/Users/dell/Desktop/slider.html')

# 滑块
slider = page.locator('.slider').bounding_box()
page.mouse.move(x=slider['x'], y=slider['y']+slider['height']/2)
page.mouse.down()   # 按住
page.mouse.move(x=slider['x']+240, y=slider['y']+slider['height']/2)
page.mouse.up() # 释放
page.pause()

键盘操作

按住Shift以选择和删除某些文本的示例:

page.keyboard.type("Hello World!")
page.keyboard.press("ArrowLeft")   #按下按键,比如'Enter'
page.keyboard.down("Shift")
for i in range(6):
    page.keyboard.press("ArrowLeft")
page.keyboard.up("Shift")
page.keyboard.press("Backspace")
# result text will end up saying "Hello!"

按大写字母A 的例子

page.keyboard.press("Shift+KeyA")
# or
page.keyboard.press("Shift+A")

按Ctrl+A选择全部

# on windows and linux
page.keyboard.press("Control+A")
# on mac_os
page.keyboard.press("Meta+A")

设置麦克风权限

有些场景在使用的时候,会弹出一些权限框,比如麦克风和摄像头等,通过监听alert 是没法捕获的。
正确做法是给浏览器设置默认允许麦克风和摄像头等权限,不让弹窗出来。使用context 的 grant_permissions 方法加权限。

from playwright.sync_api import sync_playwright

with sync_playwright() as playwright:
    browser = playwright.chromium.launch(headless=False)
    context = browser.new_context()
    # 设置允许 'camera', 'microphone' 权限
    context.grant_permissions(['camera', 'microphone'])
  

文件相关

文件上传

如果你之前用过selenium,肯定遇到过文件上传头疼的事,有些控件是input输入框,可以直接传本地文件地址,然而有些需要弹出本地文件选择器的时候就不好处理了。
playwright 控件优雅的处理了文件上传操作,在这里一切都变得如此简单了。

# Select one file
page.get_by_label("Upload file").set_input_files('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"}
    ],
)

如果您手头没有输入元素(它是动态创建的),您可以处理 page.on("filechooser") 事件或在您的操作中使用相应的等待方法:
如果不是input输入框,必须点开本地文件框的情况(selenium上没法实现的操作)



# 弹出文件框继续操作
    with page.expect_file_chooser() as fc_info:
        page.get_by_label("选择文件").click()
    page.pause()
    file_chooser = fc_info.value
    file_chooser.set_files(r"D:\tou.png")

在运行过程中你是感知不到文件选项框弹出来的

异步代码示例

async with page.expect_file_chooser() as fc_info:
    await page.get_by_text("Upload file").click()
file_chooser = await fc_info.value
await file_chooser.set_files("myfile.pdf")

几个操作方法

  • file_chooser.element  返回与此文件选择器关联的输入元素。
  • file_chooser.is_multiple() 返回此文件选择器是否接受多个文件。
  • file_chooser.page  返回此文件选择器所属的页面。

set_files()参数

  • files    pathlib.Path
  • no_wait_after  启动导航的操作正在等待这些导航发生并等待页面开始加载。您可以通过设置此标志来选择退出等待。您仅在特殊情况下才需要此选项,例如导航到无法访问的页面。默认为false.
  • timeout 以毫秒为单位的最长时间,默认为 30 秒,传递0以禁用超时。可以使用browser_context.set_default_timeout()或page.set_default_timeout()方法更改默认值。

文件下载

with sync_playwright() as p:
	# 返回BrowserContext对象
	self.context = p.chromium.launch_persistent_context(

		# 设置 GUI 模式
		headless=False,
		   # 允许下载文件
		accept_downloads=True,
		downloads_path ='./下载',

)

#监听下载事件
self.page.on("download", lambda download: download.save_as(f'./下载/{self.firm.strip()}.xlsx'))
<body>
  <h1>下载文件</h1>
  <a href="https://www.python.org/ftp/python/3.10.10/python-3.10.10-embed-amd64.zip">点我下载</a>
</body>
from playwright.sync_api import sync_playwright

def run(playwright):
    chromium = playwright.chromium
    browser = chromium.launch(headless=False, slow_mo=3000)
    page = browser.new_page()
    page.goto(r'*************down.html')
    with page.expect_download() as download_info:
        page.get_by_text("点我下载").click()
    download = download_info.value
    # wait for download to complete
    print(download.url)  # 获取下载的url地址
    # 这一步只是下载下来,生成一个随机uuid值保存,代码执行完会自动清除
    print(download.path())
    # 最终可以用save_as 保存到本地
    download.save_as(download.suggested_filename)

    browser.close()

with sync_playwright() as playwright:
    run(playwright)

download相关操作

#1.取消下载。如果下载已经完成或取消,则不会失败。成功取消后,download.failure()将解析为’canceled’.
download.cancel()

#2.删除下载的文件。如有必要,将等待下载完成。
download.delete()

#3.返回下载错误(如果有)。如有必要,将等待下载完成。
download.failure()

#4.获取下载所属的页面。

download.page


#5.下载路径  
#如果下载成功,则返回下载文件的路径。如有必要,该方法将等待下载完成。该方法在远程连接时抛出。  
#请注意,下载的文件名是随机 GUID,使用download.suggested_filename获取建议的文件名。
download.path()

#返回NoneType|pathlib.Path 类型  
#6.将下载复制到用户指定的路径。在下载仍在进行时调用此方法是安全的。如有必要,将等待下载完成。
download.save_as(path)


#返回此下载的建议文件名。  它通常由浏览器根据`Content-Disposition`响应标头或`download`属性计算得出。
download.suggested_filename

#返回下载的 url
download.url

截图

用无头模式也可以生成图片
当我们自动化脚本在运行中出现错误时,需要对当前操作页面进行截图,以便我们查找问题。

  • fullPage为True时,截取全屏,默认为False
  • clip截取特定部分的图片,{“x”: float, “y”: float, “width”: float, “height”: float}
  • type指定图片类型,支持[‘jpeg’, ‘png’],默认为png
  • quality图片质量,值为0-100,不适用于png图像
  • timeout超时时间,默认为30s
  • omitBackground隐藏默认的白色背景,允许透明截图。不适用于jpeg图像。默认False
#1.当前页面截图
page.screenshot(path=f"screenshot/{datetime.now().strftime('%Y-%m-%d %H-%M-%S')}.png")


#2.截长图
page.screenshot(path="screenshot.png", full_page=True)

#3.截取指定元素的截图
page.locator("#page >> text=2").screenshot(path="screenshot.png")

## 捕获图片数据流
#您可以获取包含图像的缓冲区并对其进行后处理或将其传递给第三方像素差异工具,而不是写入文件。
screenshot_bytes = page.screenshot()
print(base64.b64encode(screenshot_bytes).decode())

录制视频

使用 Playwright,您可以为测试录制视频。

视频在测试结束时浏览器上下文关闭时保存。如果您手动创建浏览器上下文,请确保browser_context.close(), 会在调用close的时候保存视频。

context = browser.new_context(record_video_dir="videos/")
video_path = page.video.path()  ## 获取保存视频路径
print(f'已录制视频{video_path}')
# 确保调用 close,  videos视频才会保存
context.close()

您还可以指定视频大小。视频大小默认为缩小以适合 800x800 的视口大小。
视口的视频放置在输出视频的左上角,必要时按比例缩小以适合。您可能需要设置视口大小以匹配您想要的视频大小。

context = browser.new_context(
    record_video_dir="videos/",
    record_video_size={"width": 640, "height": 480}
)

节点元素定位

id > css定位 > xpath定位

Playwright和Selenium的元素定位方法有相似之处,因为都是对相同的WEB页面元素进行定位,所以差别不大,区别在于它们的定位方式,Selenium对每种选择器提供了对应的方法,而Playwright只需要写定位表达式就可以了,不需要指定具体方法。从这一点来看,Playwright是非常方便的。
locator对象

Locators | Playwright Python — 定位器|剧作家巨蟒

建议使用文本定位器来查找非交互式元素,如div, span, p 等。
对于交互式元素,如请button, a, input, 使用角色定位器.

内置定位器

这些是 playwright 推荐的内置定位器

  • page.get_by_role()角色定位器 通过显式和隐式可访问性属性进行定位。
    • 角色定位器包括按钮、复选框、标题、链接、列表、表格
  • page.get_by_text()通过文本内容定位。
  • page.get_by_label()通过关联标签的文本定位表单控件。
  • page.get_by_placeholder()按占位符定位输入。
  • page.get_by_alt_text()通过替代文本定位元素,通常是图像。
  • page.get_by_title()通过标题属性定位元素
  • page.get_by_test_id()根据data-testid属性定位元素(可以配置其他属性)。

角色定位器

#<button>sign in</button>
#button通过名称为“登录”的角色定位元素。
page.get_by_role("button", name="Sign in").click()
page.get_by_role("listitem")   #定位列表
#<span title="Issues count">Welcome, John</span>
page.get_by_title("Issues count").to_have_text("25 issues")

#<img src="" alt="logo"> 所有图像都应该有一个alt描述图像的属性
page.get_by_alt_text("logo").click()

page.get_by_label("Password").fill("secret-password")



expect(page.get_by_text("Welcome, John!")).to_be_visible()

文本定位

• selector 选择器方式 page.click(“text=登录”)
• 内置定位器page.get_by_text(登录)
• 原生xpath 文本定位 //*[text()="登录"]

<button type="submit" class=" Button--primary ">登录</button>
<input type="button" value="百度一下">
page.click("text=登录") #模糊匹配
page.click("text=‘登录’")   #精准匹配, 精准匹配需使用单引号

#get_by_text() 内置定位器
page.get_by_text('登录') #默认也是模糊匹配的
page.get_by_text("博客", exact=True)  #精准匹配

#xpath文本定位
 //*[text()="登录"]   #完全匹配文本
 //*[contains(text(),"登录")]    #包含某个文本

根据元素所包含的文本查找元素。使用page时,可以通过子字符串、精确字符串或正则表达式进行匹配。

  • exact=True : exact参数设置精准匹配,文本解决复杂的定位元素层级定位,dom树变化也不影响定位元素
# <span>Welcome, John</span>
expect(page.get_by_text("Welcome, John")).to_be_visible()

#设置精确匹配:
expect(page.get_by_text("Welcome, John", exact=True)).to_be_visible()

#与正则表达式匹配:
expect(page
    .get_by_text(re.compile("welcome, john", re.IGNORECASE)))
    .to_be_visible()

过滤定位器

对于ul-li的元素,可以用listitem 的角色定位方式,配合 locator.filter() 过滤选择器一起使用

<ul>
	<li>
		<h1>product 1</h1>
	</li>
	<li>
		<h1>product 2</h1>
	</li>
</ul>

是否含有文本过滤

可以使用locator.filter()方法按文本过滤定位器。它将在元素内部某处搜索特定字符串,可能在后代元素中不区分大小写。您还可以传递正则表达式。

#通过包含文本筛选
page.get_by_role("listitem").filter(has_text="Product 2").get_by_role("button", name="Add to cart"
)

#通过不包含文本进行筛选
expect(page.get_by_role("listitem").filter(has_not_text="Out of stock")).to_have_count(5)

#使用正则表达式
page.get_by_role("listitem").filter(has_text=re.compile("Product 2")).get_by_role(
"button", name="Add to cart")


#在locator 传 has_text参数和使用`filter(has_text="新闻")`作用是等价的
page.locator('#s-top-left>a', has_text="新闻").click()

是否有后代筛选

定位器支持仅选择具有或不具有与另一个定位器匹配的子代的元素的选项。因此,您可以按任何其他定位器(如定位器)进行过滤。get_by_role(),locator.get_by_test_id(),locator.get_by_text()等

page.get_by_role("listitem").filter(
    has=page.get_by_role("heading", name="Product 2")
).get_by_role("button", name="Add to cart").click()


#可以断言产品卡以确保只有一个
expect(  
page.get_by_role("listitem").filter(  
has=page.get_by_role("heading", name="Product 2")  
)  
).to_have_count(1)

#我们也可以通过内部没有匹配元素来进行过滤
expect(
    page.get_by_role("listitem").filter(
        has_not=page.get_by_role("heading", name="Product 2")
    )
).to_have_count(1)

链接多个过滤器以缩小选择范围

<ul>
  <li>
    <div>John</div>
    <div><button>Say hello</button></div>
  </li>
  <li>
    <div>Mary</div>
    <div><button>Say hello</button></div>
  </li>
  <li>
    <div>John</div>
    <div><button>Say goodbye</button></div>
  </li>
  <li>
    <div>Mary</div>
    <div><button>Say goodbye</button></div>
  </li>
</ul>
row_locator = page.get_by_role("listitem")

row_locator
    .filter(has_text="Mary")
    .filter(has=page.get_by_role("button", name="Say goodbye"))
    .screenshot(path="screenshot.png")

其他定位

常用方法定位

#使用文本定位时,直接使用text=文本,
page.locator("text=登录").click()

#css定位,元素的id=nav-bar class=contact-us-item
page.locator("#nav-bar .contact-us-item").click()


# 元素属性定位
#<div value='百度一下' id=a>
page.locator("[value='百度一下']").click()
#定位元素的多种属性
submit_btn = page1.locator('button[data-testid="submit-btn"][disabled].btn')


#测试id定位
#<button data-testid="directions"></button>
page.get_by_test_id("directions").click()

使用 locator  定位元素,不管元素存不存在,都会返回一个locator 对象,可以用到count() 方法计算元素的个数,如果元素个数是 0, 那么元素就不存在
locator 是定位当前页面上的元素,不会自动等待,如果用click等方法结合使用,会自动去等待元素处于可点击状态。

from playwright.sync_api import sync_playwright

with sync_playwright() as pw:
	browser = pw.chromium.launch()
	page = browser.new_page()

	page.goto("https://www.baidu.com/")
	
	# 元素存在
	loc1 = page.locator("id=kw")
#  用到count() 方法计算元素的个数,如果元素个数是 0, 那么元素就不存在
    if loc1.count() > 0:
        loc1.fill("playwright")
        page.locator("id=su").click()
    else:
        print("元素不存在")

定位器内部匹配

save_button = page.get_by_role("button", name="Save")
# ...
dialog = page.get_by_test_id("settings-dialog")
dialog.locator(save_button).click()

两个定位器

  • 同时匹配两个: locator.and_() 通过匹配其他定位器来缩小现有定位器的范围。
  • 匹配两个备选定位器之一:使用 locator.or_() 创建与所有备选元素匹配的定位器。
button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))



new_email = page.get_by_role("button", name="New")
dialog = page.get_by_text("Confirm security settings")
expect(new_email.or_(dialog).first).to_be_visible()
if (dialog.is_visible()):
  page.get_by_role("button", name="Dismiss").click()
new_email.click()

在Shadow DOM中定位

<x-details role=button aria-expanded=true aria-controls=inner-details>
  <div>Title</div>
  #shadow-root
    <div id=inner-details>Details</div>
</x-details>
page.get_by_text("Details").click()
page.locator("x-details", has_text="Details" ).click()
#要确保 `<x-details>` 包含文本“详细信息”:
expect(page.locator("x-details")).to_contain_text("Details")

布局选择器定位

有时,当目标元素缺乏独特的特征时,很难为它想出一个好的选择器。在这种情况下,使用 Playwright 布局选择器可能会有所帮助。这些可以与常规 CSS 结合使用。

  • :right-of(inner > selector) - 匹配 inner selector的任意右边元素
  • :left-of(inner > selector) - 匹配inner selector左边元素
  • :above(inner > selector) - 匹配inner selector上面的元素
  • :below(inner > selector) - 匹配inner selector下面的元素
  • :near(inner > selector) - 匹配inner selector附近 ( 50 pixels以内) 的元素。

例如如下代码匹配文本“用户名”右侧的输入字段 - 当页面有多个难以相互区分的输入时很有用。
page.locator("input:right-of(:text('Username'))").click()

# 在“Username”右侧输入TesterRoad  
page.locator("input:right-of(:text(\"Username\"))").fill(TesterRoad)  
  
# 点击 promo card 附近的一个按钮  
page.locator("button:near(.promo-card)").click()  
  
# 点击 "Label 3"左侧最近的单选按钮  
page.locator("[type=radio]:left-of(:text(\"Label 3\"))").first.click()

注意:布局选择器取决于页面布局,当页面布局发生变化时,元素定位可能会失败。

元素可见性定位

注意: 通常最好找到一种更可靠的方法来唯一标识元素,而不是检查可见性。

Playwright有两种方法定位可见元素:

  • 1.:visible CSS 选择器中的伪类
  • 2.visible=选择器引擎

如下页面有两个按钮,第一个按钮不可见,第二个按钮可见

<button style='display: none'>Invisible</button>
<button>Visible</button>

点击第二个按钮

page.locator("button:visible").click()
page.locator("button >> visible=true").click()

链式定位器



product = page.get_by_role("listitem").filter(has_text="Product 2")

product.get_by_role("button", name="Add to cart").click()

svg 元素定位

SVG英文全称为Scalable vector Graphics,意思为可缩放的矢量图,这种元素比较特殊,需要通过 name() 函数来进行定位。

用普通的标签定位,是无法定位的,如xpath的//svg,只能通过 name() 函数来定位 //*[name()="svg"]

<svg  width="500" height="580">

链接定位器

您可以链接创建定位器的方法,如page。get_by_text()或locator。get_by_role(),将搜索范围缩小到页面的特定部分。
我们首先通过定位其角色 listitem 来创建一个名为product的定位器。然后我们通过文本进行过滤。我们可以再次使用产品定位器来获取按钮的角色,并单击它,然后使用断言来确保只有一个带有文本“Product 2”的产品。

product = page.get_by_role("listitem").filter(has_text="Product 2")

product.get_by_role("button", name="Add to cart").click()

您还可以将两个定位器链接在一起,例如在特定对话框中查找“保存”按钮:

save_button = page.get_by_role("button", name="Save")

dialog = page.get_by_test_id("settings-dialog")
dialog.locator(save_button).click()

组合定位


#1.css与css组合
page.click('.SignFlow-tabs >>css=div.SignFlow-tab:last-child') 
#2,xpath与css组合
page.fill('//div[@class="SignFlow-account"] >>css=[name="username"]',"0863")
#3,xpath与xpath组合
page.fill('//div[@class="SignFlowInput"] >> //input[@name="password"]',"ma160065")

page.locator("//button[@type='submit'][text()='登录']").click()

 #4css 与 text 组合
page.locator("#login-form >> text=立即登录").click()
# 等价于
page.locator("#login-form").locator("text=立即登录").click()
#xpath ,css, text 三种定位方式可以任意组合

xpath定位

如果必须使用CSS或XPath定位器,可以使用page。locator()创建一个定位器,它接受一个选择器,描述如何在页面中查找元素。

Playwright支持CSS和XPath选择器,会自动检测它们,所以可以省略 css= 或 xpath= 前缀

不推荐使用CSS和XPath,因为DOM经常会发生变化,导致非弹性测试。

XPath是XML路径语言,是一种查询语言,使用路径表达式浏览XML文档中的元素和属性

语法:Xpath=//tagname[@attribute=‘value’]

  • // : 选择当前节点
  • Tagname: 节点标签名
  • @: 选择属性
  • Attribute: 节点属性名
  • Value: 属性值

绝对定位使用绝对路径,缺点是路径太长,只要一个节点变动就无法定位。以单斜杠(/)开始,表示从根节点开始选择元素。
相对路径以双斜杠(//)开始,可以从HTML文档的任何位置开始,

/html/body/div[1]/div[1]/div[5]/div[1]/div/form/span[2]/input

//input[@id="su"]

page.locator("css=button").click()
page.locator("xpath=//button").click()

page.locator("button").click()
page.locator("//button").click()
# 百度首页,使用 xpath 定位输入框,并输入“mrjade”
page.fill("//input[@name='wd']", "mrjade")
# 或者
page.fill("xpath=//*[@id='kw']","mrjade")

CSS定位

属性值定位
只支持id, data-testid, data-test-id, data-test这4种属性值,其它属性需要使用css表达式。

page.fill("id=kw", "test")
page.click("id=su")
page.fill('css=[id="kw"]', "test")
page.fill('css=[class="s_ipt"]', "test")
page.fill('css=[name="wd"]', "test")
# 也可以简化为
page.fill('[id="kw"]', "test")
page.fill('[class="s_ipt"]', "test")
page.fill('[name="wd"]', "test")

:has-text()

has-text() 伪类可以在 css 选择器中使用。它匹配任何包含指定文本的元素,这些文本可能位于子元素中。匹配不区分大小写,并搜索子字符串。

#<span><input>百度一下</input></span>

page.locator("span:has-text('百度一下')").click()

伪类:has()

父元素包含子元素,如点击id为s-top-left的元素下包含的第二个a标签

<div id="s-top-lef" >
	<a href="#" </a>
	<a href="#" </a>
</div>
page.click("#s-top-left:has(a) > a:nth-child(2)")

伪类:is()

选择其中任何一个元素,点击【新闻】,如果“新闻”不存在,则点击“News”

page.click(':is(a:has-text("新闻"), a:has-text("News"))')

input表单相关

get_by_label()

大多数表单控件通常都有专用标签,可以方便地用于与表单交互。在这种情况下,您可以使用page.get_by_label()通过其关联标签定位控件。

#输入框标签定位 
# <label >Password <input type="password "></label>
page.get_by_label("Password").fill("secret")


get_by_placeholder()

get_by_placeholder()按占位符定位输入。

#输入框占位符定位
# <input type="email" placeholder="请输入邮箱">
page.get_by_placeholder("请输入邮箱").fill("playwright@microsoft.com")

checkbox和radio

   <div>
   <label>性别:
   <input type="radio" name="sex" id="man" checked><input type="radio" name="sex" id="woman"></label>
   </div>
   <div>
   <label>标签:
   <input type="checkbox" id="a1"> 旅游
   <input type="checkbox" id="a2">看书
   <input type="checkbox" id="a3" checked >学习
   <input type="checkbox" id="a4" >学python
   </label>
   </div>
单选框和复选框相关操作总结
locator.click()  点击操作
locator.check()   选中
locator.uncheck()  #设置成不选中,本身就是选择状态,去设置unchecked 状态,会报错
locator.set_checked()  设置选中状态
locator.is_checked()  判断是否被选中

radio 单选操作

# radio 单选,多种方法

page.locator('#woman').click()   
page.locator('#woman').check()
page.locator('#woman').set_checked(checked=True)   
page.check('#woman')page.set_checked('#woman', checked=True)  #调用page对象相关方法

checkbox 复选框

checkbox 复选框跟 radio 操作的区别在于,未选中的时候,点击就会被选中。如果已经被选择了,再点击会被取消选中

# checkbox 复选框操作
page.locator('#a1').click()
#如果想让元素必须是选择状态(不管之前有没被选中),可以使用check() 或 set_checked() 方法
page.locator('#a1').check()
page.locator('#a1').set_checked(checked=True)


#定位全部CheckBox 批量选中checkbox
box = page.locator('[type="checkbox"]')
for item in box.all():
	item.check()

表格

定位table 表格内容以及获取table 表格数据。
table页面有这几个明显的标签:table、tr、th、td

  • <table>标示一个表格
  • <tr> 标示这个表格的一行
  • </th> 定义表头单元格
  • </td> 定义单元格标签,一组<td>标签将将建立一个单元格,<td>标签必须放在<tr>标签内

//*[@id="表格id"]/tbody/tr[m]/td[n]
其中m表示第几行,n表示第几列。

获取当前表格总数:可以定位全部的tr 元素,计算tr 的个数,就是总行数了

n = page.locator('//*[@id="table"]/tbody/tr')
print(n.count())   # 统计个数

#获取表格第1行的数据
n = page.locator('//*[@id="table"]/tbody/tr[1]')
print(n.inner_text())   # 获取第一行数据

# 获取第3列数据  
a = page.locator('//*[@id="table"]/tbody/tr/td[3]')  
for td in a.all():  
print(td.inner_text())

#获取第1行第3列数据
b = page.locator('//*[@id="table"]/tbody/tr[1]/td[3]')  
print(b.inner_text()) # 登录

select 下拉框操作

<select multiple id='s'>
  <option value="red">Red</div>
  <option value="green">Green</div>
  <option value="blue">Blue</div>
</select>

方法一:先定位select元素,再定位选项
<select> 使用locator.select_option()选择元素中的一个或多个选项。您可以指定选项value,或label选择。可以选择多个选项。

# single selection matching the value or label
element.select_option("blue")
# single selection matching the label
element.select_option(label="blue")
# multiple selection for blue, red and second option
element.select_option(value=["red", "green", "blue"])

方法二:通过page对象直接调用,类似于page.fill和page.click的用法

page.select_option("select#s", "test")

日历控件

遇到输入框是弹出日历控件,选一个日期的这种场景,可以直接在输入框输入内容。

    page.locator('#date_day').fill('2021-04-01')

如果输入框是readonly的时候,可以用js改变输入框的属性

<input type="text" id="birth_day" name="birthday" value="2023-04-02" readonly="readonly">
   # 去掉元素的readonly属性
    js1 = 'document.getElementById("birth_day").removeAttribute("readonly");'
    page.evaluate(js1)
    # 直接给输入框赋值
    js2 = 'document.getElementById("birth_day").value="2021-04-01";'
    page.evaluate(js2)

ElementHandle 元素句柄

ElementHandle 表示页内 DOM 元素,ElementHandle 实例可以用作page.eval_on_selector()和page.evaluate()方法中的参数。
可以使用page.query_selector()方法创建。

href_element = page.query_selector("a")
href_element.click()

在下面的示例中,句柄指向页面上的特定 DOM 元素。如果那个元素改变了文本或者被 React 用来渲染一个完全不同的组件,句柄仍然指向那个 DOM 元素。这可能会导致意外行为。

# 元素存在
loc1 = page.query_selector('#kw')
print(loc1)  # JSHandle@node

# 元素不存在
loc2 = page.query_selector('#yoyo')
print(loc2)  # None

也可以用query_selector_all 复数定位方式返回一个list

# 元素存在
loc1 = page.query_selector_all('#kw')
print(loc1)  # [<JSHandle preview=JSHandle@node>]

# 元素不存在
loc2 = page.query_selector_all('#yoyo')
print(loc2)  # []

学到这里大家也就明白了,为什么用selenium去循环操作页面元素的时候,只有第一次能操作,当页面刷新时,你定位的对象是上个页面的DOM元素对象。
所以虽然你定位对了,但是句柄发生了改变,也就无法找到指定的元素了。

selenium 采用的是 http 协议,获取的元素句柄是固定的,不能实时去获取页面上的元素
playwright 采用的是webscoket 协议,可以实时去获取页面元素,当DOM结构有更新的时候,也能重新获取到
所以不鼓励使用 ElementHandle,而是使用Locator对象和网络优先断言定位机制不一样,所以 playwright 定位元素的时候比 selenium 更稳定。

元素操作

获取元素信息

locator.inner_html()  #获取某个元素的HTML
locator.text_content() #用来获取某个元素内所有文本内容,包含子元素内容,隐藏元素也能获取。返回值不会被格式化,返回值依赖于代码的内容
locator.inner_text()    #的返回值会被格式化,inner_text()返回的值, 依赖于页面的显示, 
locator.count()  #获取元素个数
all_inner_texts() 和 all_text_contents()  #也是用于获取页面上的文本,但是返回的是list列表

输入

  • locator.fill() 是填写表单字段的最简单方法。它聚焦元素并input使用输入的文本触发事件。它适用于<input>,<textarea>[contenteditable]元素。这可能会导致意外行为。
  • Type 输入:一个字符一个字符地输入字段,就好像它是一个使用locator.type()的真实键盘的用户。此方法将发出所有必要的键盘事件,所有keydown, keyup,keypress事件就位。您甚至可以指定delay按键之间的可选操作来模拟真实的用户行为。 大多数时候,page.fill()会正常工作
# Text 文本框输入
page.get_by_role("textbox").fill("Peter")

# 根据label 定位 Date 日期输入
page.get_by_label("Birth date").fill("2020-02-02")

# Time input
page.get_by_label("Appointment time").fill("13:15")

# Local datetime input
page.get_by_label("Local time").fill("2020-03-02T05:15")


page.locator('#area').type('Hello World!')

点击元素


# 定位可见元素
page.click("button:visible")
page.click("button >> visible=true")


# Generic click
page.get_by_role("button").click()

# Double click
page.get_by_text("Item").dblclick()

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

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

# Hover over element
page.get_by_text("Item").hover()

# Click the top left corner
page.get_by_text("Item").click(position={ "x": 0, "y": 0})

在幕后,这个和其他与指针相关的方法:

  • 等待具有给定选择器的元素出现在 DOM 中 (不用自己去写轮询等待了
  • 等待它显示出来,即不为空,不display:none,不visibility:hidden (这个太人性化了,不用去判断元素是否隐藏
  • 等待它停止移动,例如,直到 css 转换完成
  • 将元素滚动到视图中 (这个太人性化了,不用自己去滚动了
  • 等待它在动作点接收指针事件,例如,等待直到元素变得不被其他元素遮挡
  • 如果元素在上述任何检查期间分离,则重试

由此可见,click() 方法优化了selenium 点击元素的遇到的一些痛点问题,比如元素遮挡,不在当前屏幕,元素未出现在DOM中或隐藏不可见等不可点击的状态。

聚焦高亮

locator对象.focus() #聚焦
locator对象.highlight()  #高亮

drag_to元素拖动

您可以使用locator.drag_to()执行拖放操作。此方法将:

  • 将鼠标悬停在要拖动的元素上。
  • 按鼠标左键。
  • 将鼠标移动到将接收放置的元素。
  • 松开鼠标左键。
#先定位元素,调用drag_to方法到目标元素
page.locator("#item-to-be-dragged").drag_to(page.locator("#item-to-drop-at"))

#page对象直接调用
page.drag_and_drop(source: str,  target: str)

如果您想精确控制拖动操作,请使用较低级别的方法,如locator.hover()mouse.down()mouse.move()mouse.up()

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

如果您的页面依赖于dragover正在调度的事件,则您至少需要移动两次鼠标才能在所有浏览器中触发它。要可靠地发出第二次鼠标移动,请重复mouse.move()或locator.hover()两次。操作顺序是:悬停拖动元素,鼠标向下,悬停放置元素,第二次悬停放置元素,鼠标向上。

元素可视区域

playwright 在操作元素的时候,都会自动去让元素出现在可视窗口。大部分情况不需要我们去操作滚动条。

from playwright.sync_api import sync_playwright


with sync_playwright() as pw:
	browser = pw.chromium.launch(headless=False, slow_mo=2000)
	page = browser.new_page()
	
	page.goto("https://www.runoob.com/")
	
	# 滚动元素到屏幕可视窗口
	page.get_by_text('【学习 Django】').scroll_into_view_if_needed()

hover 方法是把鼠标放到元素上,它也会自动去页面上找到元素,让它出现在可视窗口

from playwright.sync_api import sync_playwright


with sync_playwright() as pw:
	browser = pw.chromium.launch(headless=False, slow_mo=2000)
	page = browser.new_page()
	
	page.goto("https://www.runoob.com/")
	
	# 鼠标放到元素上
	page.get_by_text('【学习 Django】').hover()

遍历节点

定位第N个元素

locator可以定位一个或多个元素

a1 = page.locator('[type="checkbox"]')
page.locator().all()
a1.count()   #返回元素个数.用于判断元素是否存在
a1.first.click()  # 点第一个
a1.last.click()   # 点最后个
a1.nth(0).click()  #按照索引

有时页面包含许多相似的元素,很难选择一个特定的元素。例如:

<section>
	<button>Buy</button> 
</section>
<article><div> 
	<button>Buy</button>
</div></article>
<div><div>
	<button>Buy</button> 
</div></div>

我们可以使用:nth-match(:text(“Buy”), 3)从上面的HTML中选择第三个按钮。

# 点击第三个按钮 Bug
page.locator(":nth-match(:text('Buy'), 3)").click()

等待元素

强制等待

Playwright 在查找元素的时候具有自动等待功能

请使用 page.wait_for_timeout(5000) 代替 time.sleep(5)并且最好不要等待超时。这是因为我们内部依赖于异步操作,并且在使用时time.sleep(5)无法正确处理它们。

page.pause()断点

playwright 的page.pause() 断点功能出现,让打开可以愉快的在页面上调试了,我们甚至可以直接使用 playwright.$(selector) 直接支持playwright选择器的方法。

在代码中加入page.pause()进入断点状态,运行后会弹出 playwright inspector  工具

判断页面元素状态

判断元素是否存在

   # 元素存在
    loc1 = page.locator("id=kw")
    print(loc1)
    print(loc1.count())

    # 元素不存在
    loc2 = page.locator('id=yoyo')
    print(loc2)
    print(loc2.count())
  • page对象调用的判断方法, 传一个selector 定位参数
    • page.is_checked(selector: str)  # checkbox or radio 是否选中
    • page.is_disabled(selector: str)  # 元素是否可以点击或编辑
    • page.is_editable(selector: str)  # 元素是否可以编辑
    • page.is_enabled(selector: str)# 是否可以操作
    • page.is_hidden(selector: str)# 是否隐藏
    • page.is_visible(selector: str)   # 是否可见
  • locator 对象调用的判断方法示例locator.is_visible()
  • 元素句柄可调用的判断方法示例element_handle.is_visible()

元素句柄(element_handle)是通过page.query_selector()方法调用返回的ElementHandle ,这种一般不常用.
关于元素句柄和locator 定位的区别

wait_for_selector()

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

  • page.locator(‘定位元素’).wait_for()  返回的是None,后面不能继续操作元素
  • page.wait_for_selector(“定位方法”) 返回的是locator 对象,后面可以继续操作元素

在延迟加载的页面中,使用locator.wait_for()等待元素可见是很有用的。

# Navigate and wait for element
page.goto("https://example.com")
page.get_by_text("example domain").wait_for()

  • wait_for_selector()
    • timeout:超时时间
    • state 参数可以设置等待状态:”attached”, “detached”, “hidden”, “visible”。
# click/fill这样的页面交互会自动等待元素。
page.goto("https://example.com")
page.get_by_text("example domain").click()

#等待元素可见
page.wait_for_selector("定位方法", state="visible")
page.wait_for_selector("定位方法") #默认是等待元素可见

#等待元素出现在DOM中
page.wait_for_selector("定位方法", state='attached')
#等待从DOM中移除
page.wait_for_selector("定位方法", state='detached')
#等待元素不可见
page.wait_for_selector("定位方法", state='hidden')

反爬

#爬虫/反爬
还有一些网站隐藏Webdriver检测后,也无法进入

例如:符合评估审查要求的国家或地区输华肉类产品名单 (customs.gov.cn)

隐藏Webdriver检测

人工正常打开的浏览器 window.navigator.webdriver属性 为false

js="""
Object.defineProperties(navigator, {webdriver:{get:()=>undefined}});
"""

with sync_playwright() as p:
	# 返回BrowserContext对象
	context = p.chromium.launch_persistent_context(

		#绕过Webdriver检测的第一种方法
		#控制台输入window.navigator.webdriver查看是否跳过
		args=['--disable-blink-features=AutomationControlled']    
	)
	page1 = context.new_page()
	page1.add_init_script(js)  #绕过Webdriver检测的第二种方法

设置浏览器请求头

set_extra_http_headers() 方法设置浏览器请求头部参数

page对象设置

from playwright.sync_api import sync_playwright, expectwith sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    context = browser.new_context()
    page = context.new_page()    # 设置请求头部(仅供参考示例)
    page.set_extra_http_headers(
        headers={  "Authorization": "Bearer "
        }
    )

在context对象设置

from playwright.sync_api import sync_playwright, expectwith sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    context = browser.new_context()    # 设置请求头部
    context.set_extra_http_headers(
        headers={ "Authorization": "Bearer "
        }
    )
    page = context.new_page()
  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值