网址 | 说明 |
---|---|
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基础,我是对比来讲解的
(资料图片仅供参考)
先看一段python代码
from selenium import webdriverdriver = 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 sleepsleep(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 WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECWebDriverWait(driver,5,0.5).until(EC.visibility_of_element_located(code_input_locator)).send_keys("admin")
做了这么多铺垫来看看playwright的优势
from playwright.sync_api import sync_playwrightwith 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会检查
完整的列表如下
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_playwrightwith 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_playwrightwith 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_playwrightwith 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")
这就可以用来验证码处理了
示例代码
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_playwrightwith 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_playwrightwith 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)
from playwright.sync_api import sync_playwrightwith 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_playwrightwith 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 <script> function show_confirm() { var r=confirm("请选择!"); if (r==true) { document.write("你选择了确定!"); } else { document.write("你选择了取消!"); } } </script>
示例代码
from playwright.sync_api import sync_playwrightwith 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自己给你处理了
示例代码
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较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, Dialogdef 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)
示例html
Frame 用户名:
示例代码
page.frame_locator("#if").locator("#ls_username").fill("admin")
这比之selenium,简单很多
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_playwrightwith 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_playwrightwith 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_playwrightwith 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
NewWin 论坛
示例代码3: 点击超链接后产生新的页面:论坛,在论坛中输入用户名
from playwright.sync_api import sync_playwrightwith 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)
解决方式二:
from playwright.sync_api import sync_playwrightwith 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)) # newpage.locator("#ls_username").fill("admin")
示例代码
from playwright.sync_api import sync_playwrightwith 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")
错误的代码(跟selenium是一样的)
from playwright.sync_api import sync_playwrightwith 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中是可以借助第三方库来处理的
但playwright中是有自己的api来操作这部分的
示例代码
from playwright.sync_api import sync_playwrightwith 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_playwrightwith 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)
关键词: