实例讲解Playwright
Posted 老吴的博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实例讲解Playwright相关的知识,希望对你有一定的参考价值。
实例讲解Playwright(二)
网址 | 说明 |
---|---|
https://playwright.dev/ | 官网首页 |
https://playwright.dev/python/docs/intro | Python部分入口 |
https://github.com/microsoft/playwright-python | github,python入口 |
https://github.com/microsoft/playwright-python/releases | python部分的release notes |
本文基于 playwright 1.32.1 发布于 2023-3-30
转载请注明出处,这是第二篇
学习前你得有html、css、xpath等基础,最好有selenium基础,我是对比来讲解的
④Page|Locator对象能力
自动等待
案例
-
先看一段python代码
from selenium import webdriver driver = webdriver.Chrome() driver.maximize_window() driver.get(\'http://121.5.150.55:8090/forum.php\') driver.find_element(\'css selector\',\'.pn.vm\').click() driver.find_element(\'css selector\',"input[id^=\'seccodeverify_cSA\']").send_keys(\'admin\')
-
这样是会报错的
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: "method":"css selector","selector":"input[id^=\'seccodeverify_cSA\']"
-
因为第五行的click会弹出一个对话框,第六行要直接操作弹框中的input输入内容,而元素实际还没展现出来
-
在selenium中可以用等待来处理
-
三种等待均可
-
强制等待
... driver.find_element(\'css selector\',\'.pn.vm\').click() from time import sleep sleep(1) driver.find_element(\'css selector\',"input[id^=\'seccodeverify_cSA\']").send_keys(\'admin\')
-
隐式等待
driver = webdriver.Chrome() # 这行后 driver.implicitly_wait(5)
-
显式等待
code_input_locator = \'css selector\',"input[id^=\'seccodeverify_cSA\']" from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC WebDriverWait(driver,5,0.5).until(EC.visibility_of_element_located(code_input_locator)).send_keys(\'admin\')
-
做了这么多铺垫来看看playwright的优势
from playwright.sync_api import sync_playwright with sync_playwright() as pw: browser = pw.chromium.launch(headless=False) page = browser.new_page() page.goto(\'http://121.5.150.55:8090/forum.php\') page.locator(\'.pn.vm\').click() page.locator("input[id^=\'seccodeverify_cSA\']").fill(\'admin\')
-
是的,你在代码中看不到任何的等待的
-
这是playwright给你做了,
特性
-
Playwright在对元素进行操作的时候会做一系列的可行性检查来确保这些前置条件能得到满足。
-
它会自动等待那些相关的条件通过,并只执行所需的动作。如果条件不满足,会提示超时,动作失败抛出TimeoutError
Playwright performs a range of actionability checks on the elements before making actions to ensure these actions behave as expected. It auto-waits for all the relevant checks to pass and only then performs the requested action. If the required checks do not pass within the given timeout, action fails with the TimeoutError
-
举个例子来说,点击一个元素之前,playwright会检查
- element is Attached to the DOM
- element is Visible
- element is Stable as in not animating or completed animation
- element Receives Events, as in not obscured by other elements
- element is Enabled
-
完整的列表如下
Action Attached Visible Stable Receives Events Enabled Editable check Yes Yes Yes Yes Yes - click Yes Yes Yes Yes Yes - dblclick Yes Yes Yes Yes Yes - setChecked Yes Yes Yes Yes Yes - tap Yes Yes Yes Yes Yes - uncheck Yes Yes Yes Yes Yes - hover Yes Yes Yes Yes - - scrollIntoViewIfNeeded Yes - Yes - - - screenshot Yes Yes Yes - - - fill Yes Yes - - Yes Yes selectText Yes Yes - - - - dispatchEvent Yes - - - - - focus Yes - - - - - getAttribute Yes - - - - - innerText Yes - - - - - innerHTML Yes - - - - - press Yes - - - - - setInputFiles Yes - - - - - selectOption Yes Yes - - Yes - textContent Yes - - - - - type Yes - - - - - -
但你对Attached、Visible、Stable、Receives Events、Enabled、Editable是否有了解呢?
-
请参考附录
-
默认的自动等待时长是30s
-
示例代码
from playwright.sync_api import sync_playwright with sync_playwright() as pw: browser = pw.chromium.launch(headless=False) page = browser.new_page() page.goto(\'http://121.5.150.55:8090/forum.php\') page.locator(\'.pn.vm\').click() page.locator("input[id=\'se\']").fill(\'admin\') # 这个表达式无法定位到元素
-
最后你会看到报错
playwright._impl._api_types.TimeoutError: Timeout 30000ms exceeded. =========================== logs =========================== waiting for locator("input[id=\'se\']") ============================================================
-
默认的30s实在有点太长了,怎么改呢
-
我走了一个弯路
def launch( ... timeout: typing.Optional[float] = None, ... timeout : Union[float, None] Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
-
说的好好的,但没有作用
browser = pw.chromium.launch(headless=False,timeout=5000)
-
还是30s超时了
-
答案是fill中也有个timeout,在这里去处理即可
page.locator("input[id=\'se\']").fill(\'admin\',timeout=1000)
-
包括click也是有的
-
但问题是,全局的(应该有)找不到,那你写项目代码的时候就要浪费很多的参数
截图
这部分能力,页面对象有,元素对象也有
页面截图
-
示例代码
from playwright.sync_api import sync_playwright with sync_playwright() as pw: browser = pw.chromium.launch(headless=False) page = browser.new_page() page.goto(\'https://www.runoob.com/\') page.screenshot(path=\'runoob.png\')
-
注意screenshot的参数必须以关键字方式传递,因为它是这样定义的。星号之后必须关键字方式传递
def screenshot( self, *, timeout: typing.Optional[float] = None, type: typing.Optional[Literal["jpeg", "png"]] = None, path: typing.Optional[typing.Union[str, pathlib.Path]] = None,
-
但这样的截图只有当前屏幕尺寸大小
-
像菜鸟教程这样的是长网页,就可以用到screenshot的参数了
全屏
-
示例代码
page.screenshot(path=\'runoob.png\',full_page=True)
元素截图
-
实例代码
from playwright.sync_api import sync_playwright with sync_playwright() as pw: browser = pw.chromium.launch(headless=False) page = browser.new_page() page.goto(\'http://114.116.2.138:8090/forum.php\') page.locator(\'.pn.vm\').screenshot(path=\'loginbutton.png\')
bytes对象
-
这就可以用来验证码处理了
-
示例代码
login_button_bytes = page.locator(\'.pn.vm\').screenshot() with open(\'loginbutton.png\',\'wb\') as f: f.write(login_button_bytes)
键盘操作
-
示例代码
from playwright.sync_api import sync_playwright with sync_playwright() as pw: browser = pw.chromium.launch(headless=False) page = browser.new_page() page.goto(\'https://www.toptal.com/developers/keycode\',timeout=80000) page.keyboard.press(\'Enter\') page.wait_for_timeout(5000)
-
核心API是keyboard
-
它提供了以下API
方法 说明 down 键按下 up 键抬起 insert_text 插入文本 press 按键 type 输入 -
press的时候支持的按键有
F1 - F12, Digit0- Digit9, KeyA- KeyZ, Backquote, Minus, Equal, Backslash, Backspace, Tab, Delete, Escape, ArrowDown, End, Enter, Home, Insert, PageDown, PageUp, ArrowRight, ArrowUp 等等 Shift, Control, Alt, Meta, ShiftLeft
-
示例代码1:演示了按键的典型操作
from playwright.sync_api import sync_playwright with sync_playwright() as pw: browser = pw.chromium.launch(headless=False) page = browser.new_page() page.goto(\'http://114.116.2.138:8090/forum.php\') page.click(\'#ls_username\') page.keyboard.type(\'d\') page.wait_for_timeout(3000) page.keyboard.press(\'ArrowLeft\') page.wait_for_timeout(3000) page.keyboard.down(\'a\') page.keyboard.up(\'a\') page.wait_for_timeout(3000) page.keyboard.press(\'Control+A\') page.wait_for_timeout(3000) page.keyboard.press(\'ArrowRight\') page.wait_for_timeout(3000) page.keyboard.type(\'min\',delay=100) page.wait_for_timeout(3000)
鼠标操作
单击、双击、右击
- 不像selenium,提供了一个context_click(右键),playwright是合并到click中的,如果要右击就提供button参数赋值为right即可
- 甚至还支持middle(中间键)
from playwright.sync_api import sync_playwright
with sync_playwright() as pw:
browser = pw.chromium.launch(headless=False)
page = browser.new_page()
page.goto(\'http://sahitest.com/demo/clicks.htm\')
page.locator("[value=\'dbl click me\']").dblclick()
page.locator("[value=\'click me\']").click()
page.locator("[value=\'right click me\']").click(button=\'right\')
page.wait_for_timeout(5000)
-
参考官方的代码,你还可以shift点击右键(不清楚具体的含义了)
await page.locator(\\"canvas\\").click( button=\\"right\\", modifiers=[\\"Shift\\"], position=\\"x\\": 23, \\"y\\": 32 )
悬停
- 参考
实例六
,元素上做个hover即可
拖拽
-
就是drag_to,不过提供了target
-
示例代码
from playwright.sync_api import sync_playwright with sync_playwright() as pw: browser = pw.chromium.launch(headless=False) page = browser.new_page() page.goto(\'http://sahitest.com/demo/dragDropMooTools.htm\') items = page.locator(\'.item\').all() for item in items: page.locator("#dragger").drag_to(item) page.wait_for_timeout(1000) page.wait_for_timeout(5000)
Alert对话框
-
示例网页
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>alert</title> <script> function show_confirm() var r=confirm("请选择!"); if (r==true) document.write("你选择了确定!"); else document.write("你选择了取消!"); </script> </head> <body> <div id="div1"> <input type="button" id="alert" value=\'alert\' onclick="alert(\'欢迎来到松勤软件测试\')"> <input type="button" id="confirm" value=\'confirm\' onclick="show_confirm(\'请选择你的操作\')"> <input type="button" id="prompt" value=\'prompt\' onclick="var name = prompt(\'请输入你的名字:\');document.write(name)"> </div> </body> </html>
alert处理
-
示例代码
from playwright.sync_api import sync_playwright with sync_playwright() as pw: browser = pw.chromium.launch(headless=False) page = browser.new_page() page.goto(r\'D:\\pythonProject\\AutoTest\\DemoPlaywright0413\\demos\\demo_alert.html\') page.locator(\'#alert\').click() page.wait_for_timeout(50000)
-
代码运行看你是没有点击的?
-
其实是点的,只是你看不到,playwright自己给你处理了
confirm处理
-
示例代码
page.locator(\'#confirm\').click() page.wait_for_timeout(50000)
-
你可以看到网页
你选择了取消!
-
所以他是点的,但没有看到过程
-
但他默认是点击的取消,如果要点击确定呢?
-
示例代码
page.on(\'dialog\',lambda dialog: dialog.accept()) page.locator(\'#confirm\').click() page.wait_for_timeout(50000)
-
注意,区别很大的!
-
按照官方的说法
If there is no listener for page.on("dialog"), all dialogs are automatically dismissed.
-
这就可以介绍
alert处理
中为何看不到了,以及confirm处理
中一开始点击的是取消 -
page.on是必须的,它一方面是做了一个监听,另外一方面是告诉你遇到了对话框后的动作
dialog.accept()
prompt处理
-
prompt较alert多了一个处理,要输入点内容
-
但搜下也没合适的答案,只能看看源码
-
源码
site-packages\\playwright\\_impl\\_dialog.py
@property def type(self) -> str: return self._initializer["type"] @property def message(self) -> str: return self._initializer["message"] @property def default_value(self) -> str: return self._initializer["defaultValue"] async def accept(self, promptText: str = None) -> None: await self._channel.send("accept", locals_to_params(locals())) async def dismiss(self) -> None: await self._channel.send("dismiss")
-
所以我一开始想到的代码是
page.on(\'dialog\',lambda dialog: dialog.accept(promptText="hello")) page.locator(\'#prompt\').click() page.wait_for_timeout(50000)
TypeError: accept() got an unexpected keyword argument \'promptText\'
-
只能修改为
page.on(\'dialog\',lambda dialog: dialog.accept("hello")) page.locator(\'#prompt\').click() page.wait_for_timeout(50000)
-
解决了!界面上显示了
hello
消息文本的输出
-
对alert提示的文本要输出也比较简单
-
示例代码1
page.on(\'dialog\',lambda dialog: print(dialog.message,dialog.type,dialog.default_value)) # 欢迎来到松勤软件测试 alert page.on(\'dialog\',lambda dialog: dialog.accept()) page.locator(\'#alert\').click() page.wait_for_timeout(3000)
-
这样是2个监听,有点多余
-
合并到一起,自定义一个函数
from playwright.sync_api import sync_playwright, Dialog def handle_alert(dialog:Dialog): print(\'对话框类型:\',dialog.type) print(\'对话框文本:\',dialog.message) dialog.accept() with sync_playwright() as pw: browser = pw.chromium.launch(headless=False) page = browser.new_page() page.goto(r\'D:\\pythonProject\\AutoTest\\DemoPlaywright0413\\demos\\demo_alert.html\') page.on(\'dialog\',handle_alert) page.locator(\'#alert\').click() page.wait_for_timeout(3000)
- 总结下,playwright对alert是能自己处理的,alert只有确定,confirm和prompt都是取消!
- 你要在confirm和prompt中点击确定或者输入内容,需要加个监听器
- prompt的信息输入比较麻烦要在accept中加个字符串,还不能写参数名(好奇怪,没看太懂)
frame切换
-
示例html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Frame</title> </head> <body> <div id="div1"> 用户名: <input id="username" type="text"> <br> </div> <iframe id=\'if\' name=\'nf\' src="http://121.5.150.55:8090/forum.php" ></iframe> </body> </html>
-
示例代码
page.frame_locator(\'#if\').locator(\'#ls_username\').fill(\'admin\')
-
这比之selenium,简单很多
window管理
-
BrowserContexts提供了一个方式来操作多个独立的浏览器会话
-
如果一个页面打开另外一个页面(比如_blank属性的超链接),那么新打开的会依附于页面浏览器的上下文
-
Playwright通过browser.new_context()创建无痕浏览器上下文,无痕意味着不会写任何浏览器数据到磁盘中。
BrowserContexts provide a way to operate multiple independent browser sessions. If a page opens another page, e.g. with a window.open call, the popup will belong to the parent page\'s browser context. Playwright allows creating "incognito" browser contexts with browser.new_context() method. "Incognito" browser contexts don\'t write any browsing data to disk
-
示例代码1
from playwright.sync_api import sync_playwright with sync_playwright() as pw: browser = pw.chromium.launch(headless=False) context = browser.new_context() page = context.new_page() page.goto(\'https://www.baidu.com\')
-
对比之前的代码
from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=False) page = browser.new_page() page.goto("http://114.116.2.138:8090/forum.php")
-
你可以看到运行效果是一样的,但是,你现在用context来打开一个new_page
-
而
.new_context()
的返回就是一个BrowserContext类 -
示例代码2: 你可以看到在一个浏览器界面中打开了2个TAB,这2个是相互独立的
from playwright.sync_api import sync_playwright with sync_playwright() as pw: browser = pw.chromium.launch(headless=False) context = browser.new_context() page1 = context.new_page() page1.goto(\'https://www.baidu.com\') page2 = context.new_page() page2.goto(\'https://cn.bing.com\') page1.wait_for_timeout(5000) page2.wait_for_timeout(5000)
-
如何做页面切换呢?其实是非常简单的事情,关键在于context有个属性pages存储了你打开的这些页面
-
只是说往往我们新开的页面并不是你用context.new_page()产生的,而是你操作了网页(比如_blank属性的超链接)
-
示例HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>NewWin</title> </head> <body> <a href="http://114.116.2.138:8090/forum.php" target="_blank">论坛</a> </body> </html>
-
示例代码3: 点击超链接后产生新的页面:论坛,在论坛中输入用户名
from playwright.sync_api import sync_playwright with sync_playwright() as pw: browser = pw.chromium.launch(headless=False) context = browser.new_context() page = context.new_page() page.goto(r\'D:\\pythonProject\\AutoTest\\DemoPlaywright0413\\demos\\demo_window.html\') page.locator(\'a\').click() context.pages[-1].locator(\'#ls_username\').fill(\'admin\') page.wait_for_timeout(5000)
-
这样会报错,默认超时时间30s,找不到元素,playwright的自动等待在这个地方就失效了
playwright._impl._api_types.TimeoutError: Timeout 30000ms exceeded. =========================== logs =========================== waiting for locator("#ls_username") ============================================================
-
问题其实是
context.pages
并没有把新的窗口纳入进来,[-1]是假的其实就是[0],你如果写[1]就会在这部分代码上报错了# context.pages[1].locator(\'#ls_username\').fill(\'admin\') IndexError: list index out of range
-
-
解决方式一:加强等
page.locator(\'a\').click() page.wait_for_timeout(1000) context.pages[-1].locator(\'#ls_username\').fill(\'admin\') page.wait_for_timeout(5000)
- 上面的代码有个缺陷,你做个selenium应该知道,这样的新开窗口是有延迟的,你等多久是个问题
-
解决方式二:
from playwright.sync_api import sync_playwright with sync_playwright() as pw: browser = pw.chromium.launch(headless=False) context = browser.new_context() page = context.new_page() page.goto(r\'D:\\pythonProject\\AutoTest\\DemoPlaywright0413\\demos\\demo_window.html\') with context.expect_page() as new_page: page.locator(\'a\').click() newpage = new_page.value # print(type(newpage)) # <class \'playwright.sync_api._generated.Page\'> newpage.locator(\'#ls_username\').fill(\'admin\')
文件上传
input类型单文件上传
-
示例代码
from playwright.sync_api import sync_playwright with sync_playwright() as pw: browser = pw.chromium.launch(headless=False) page = browser.new_page() page.goto(\'http://121.41.14.39:8088/index.html\') page.locator(\'#username\').fill(\'sq1\') page.locator(\'#password\').fill(\'123\') page.locator(\'#code\').fill(\'999999\') page.locator(\'#submitButton\').click() page.wait_for_timeout(1000) page.reload() page.locator("xpath=//span[text()=\'文件上传\']").click() page.locator("//li[contains(text(),\'单文件上传(input)\')]").click() page.locator("#cover").set_input_files(r\'d:\\1.jpg\') page.wait_for_timeout(5000)
-
页面的刷新page.reload()
-
文件上传page.locator("#cover").set_input_files(r\'d:\\1.jpg\')
非input类型的文件上传
-
错误的代码(跟selenium是一样的)
from playwright.sync_api import sync_playwright with sync_playwright() as pw: browser = pw.chromium.launch(headless=False) page = browser.new_page() page.goto(\'http://121.41.14.39:8088/index.html\') page.locator(\'#username\').fill(\'sq1\') page.locator(\'#password\').fill(\'123\') page.locator(\'#code\').fill(\'999999\') page.locator(\'#submitButton\').click() page.wait_for_timeout(1000) page.reload() page.locator("xpath=//span[text()=\'文件上传\']").click() page.locator("//li[contains(text(),\'单文件上传(非input)\')]").click() page.locator(".el-icon-upload").set_input_files(r\'d:\\1.jpg\') page.wait_for_timeout(5000)
-
提示信息
playwright._impl._api_types.Error: Error: Node is not an HTMLInputElement
-
在selenium中是一样的,也不可以直接操作。
-
selenium中是可以借助第三方库来处理的
- pyautogui
- pywinauto
- pywin32
-
但playwright中是有自己的api来操作这部分的
-
示例代码
from playwright.sync_api import sync_playwright with sync_playwright() as pw: browser = pw.chromium.launch(headless=False) page = browser.new_page() page.goto(\'http://121.41.14.39:8088/index.html\') page.locator(\'#username\').fill(\'sq1\') page.locator(\'#password\').fill(\'123\') page.locator(\'#code\').fill(\'999999\') page.locator(\'#submitButton\').click() page.wait_for_timeout(1000) page.reload() page.locator("xpath=//span[text()=\'文件上传\']").click() page.locator("//li[contains(text(),\'单文件上传(非input)\')]").click() with page.expect_file_chooser() as fc_info: page.locator(".el-icon-upload").click() file_chooser = fc_info.value file_chooser.set_files(r\'d:\\1.jpg\') page.wait_for_timeout(5000)
多文件上传
-
多文件就简单了
-
示例代码
from playwright.sync_api import sync_playwright with sync_playwright() as pw: browser = pw.chromium.launch(headless=False) page = browser.new_page() page.goto(\'http://121.41.14.39:8088/index.html\') page.locator(\'#username\').fill(\'sq1\') page.locator(\'#password\').fill(\'123\') page.locator(\'#code\').fill(\'999999\') page.locator(\'#submitButton\').click() page.wait_for_timeout(1000) page.reload() page.locator("xpath=//span[text()=\'文件上传\']").click() page.locator("//li[contains(text(),\'多文件上传(非input)\')]").click() with page.expect_file_chooser() as fc_info: page.locator(".el-upload-dragger").click() file_chooser = fc_info.value file_chooser.set_files( files = [r\'d:\\1.jpg\',r\'d:\\1.txt\'] ) page.wait_for_timeout(5000)
无法安装 playwright:找不到使用 Playwright 的项目。确保项目或解决方案存在于
【中文标题】无法安装 playwright:找不到使用 Playwright 的项目。确保项目或解决方案存在于【英文标题】:Cannot install playwright: Couldn't find project using Playwright. Ensure a project or a solution exists in 【发布时间】:2021-12-25 14:58:53 【问题描述】:我正在尝试在我的部署目标机器上安装 playwright 以运行 UI 测试。
# Install the CLI once.
dotnet tool install --global Microsoft.Playwright.CLI
playwright install
但是当使用 playwright CLI 包括 playwright install 时,我得到:
找不到使用 Playwright 的项目。确保项目或解决方案 存在于 C:\users\myuser 中,或者使用 -p 提供另一个路径。
如何在虚拟机上安装 playwright?
编辑:
不幸的是,用于剧作家的 .NET nuget 包设计得不是很好。虽然 API 很棒,但部署却是一场噩梦。
不仅您无法在部署服务器上使用 CLI 安装浏览器,而且该软件包会为您的项目以及所有引用它的项目添加 3x NodeJS 运行时 (200MB)。
阻止这些文件被发布是很重要的,并且您的构建工件可以轻松地增长到每个构建的 1GB!
您无法配置 NodeJS 或剧作家本身的路径。
您可以在此处投票支持解决此问题: https://github.com/microsoft/playwright-dotnet/issues/1850
【问题讨论】:
【参考方案1】:需要在csproj
所在的文件夹中执行playwright install
或者使用-p
指定工程文件
# Create project
dotnet new console -n PlaywrightDemo
cd PlaywrightDemo
# Install dependencies, build project and download necessary browsers.
dotnet add package Microsoft.Playwright
dotnet build
playwright install
查看文档了解更多详情:https://playwright.dev/dotnet/docs/intro#first-project
您也可以使用以下代码从代码安装浏览器:
using Microsoft.Playwright;
var exitCode = Microsoft.Playwright.Program.Main(new[] "install" );
更多详情:https://www.meziantou.net/distributing-applications-that-depend-on-microsoft-playwright.htm
【讨论】:
但这对部署虚拟机没有任何意义。只有编译的二进制文件,没有项目。我不是在创建剧作家项目,我想执行它。见playwright.dev/dotnet/docs/cli#install-browsers Playwright CLI 只是从Microsoft.Playwright.dll
调用Microsoft.Playwright.Program.Run
的助手。此 dll 位于 NuGet 包 Microsoft.Playwright
中。此方法调用位于.playwright
文件夹中的exe。在构建项目时,此文件夹会复制到 build 文件夹中。这就是为什么在调用playwright install
之前需要一个项目文件并构建项目的原因。或者,您可以从代码安装浏览器。我已经用一个例子更新了答案。以上是关于实例讲解Playwright的主要内容,如果未能解决你的问题,请参考以下文章
python+playwright 学习-5.new_context上下文与新窗口操作
python+playwright 学习-10.pytest-playwright插件编写测试用例