最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

观察:实例讲解Playwright(二)

来源:博客园

实例讲解Playwright(二)

网址说明
https://playwright.dev/官网首页
https://playwright.dev/python/docs/introPython部分入口
https://github.com/microsoft/playwright-pythongithub,python入口
https://github.com/microsoft/playwright-python/releasespython部分的release notes

本文基于 playwright 1.32.1 发布于 2023-3-30转载请注明出处,这是第二篇学习前你得有html、css、xpath等基础,最好有selenium基础,我是对比来讲解的

④Page|Locator对象能力

自动等待

案例

  • 先看一段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会检查

    • 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
  • 完整的列表如下

    ActionAttachedVisibleStableReceives EventsEnabledEditable
    checkYesYesYesYesYes-
    clickYesYesYesYesYes-
    dblclickYesYesYesYesYes-
    setCheckedYesYesYesYesYes-
    tapYesYesYesYesYes-
    uncheckYesYesYesYesYes-
    hoverYesYesYesYes--
    scrollIntoViewIfNeededYes-Yes---
    screenshotYesYesYes---
    fillYesYes--YesYes
    selectTextYesYes----
    dispatchEventYes-----
    focusYes-----
    getAttributeYes-----
    innerTextYes-----
    innerHTMLYes-----
    pressYes-----
    setInputFilesYes-----
    selectOptionYesYes--Yes-
    textContentYes-----
    typeYes-----
  • 但你对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")

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_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)

鼠标操作

单击、双击、右击

  • 不像selenium,提供了一个context_click(右键),playwright是合并到click中的,如果要右击就提供button参数赋值为right即可
  • 甚至还支持middle(中间键)
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对话框

  • 示例网页

            alert    <script>        function show_confirm()            {            var r=confirm("请选择!");            if (r==true)              {              document.write("你选择了确定!");              }            else              {              document.write("你选择了取消!");              }            }    </script>    

alert处理

  • 示例代码

    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自己给你处理了

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, 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)
  • 总结下,playwright对alert是能自己处理的,alert只有确定,confirm和prompt都是取消!
  • 你要在confirm和prompt中点击确定或者输入内容,需要加个监听器
  • prompt的信息输入比较麻烦要在accept中加个字符串,还不能写参数名(好奇怪,没看太懂)

frame切换

  • 示例html

            Frame    
    用户名:
  • 示例代码

    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_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)
    • 上面的代码有个缺陷,你做个selenium应该知道,这样的新开窗口是有延迟的,你等多久是个问题
  • 解决方式二:

    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")

文件上传

input类型单文件上传

  • 示例代码

    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")

非input类型的文件上传

  • 错误的代码(跟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中是可以借助第三方库来处理的

    • pyautogui
    • pywinauto
    • pywin32
  • 但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)

关键词: