Playwright的基本操作和元素定位

原文链接和视频,本文大部分内容来自此,仅整理增补些许内容方便个人理解和学习。

https://www.byhy.net/etc/playwright/01/#h_4

出错啦! - bilibili.com

pip install playwright
playwright install

示例代码

from playwright.sync_api import sync_playwright

input('1....')
# 启动 playwright driver 进程
p = sync_playwright().start()

input('2....')
# 启动浏览器,返回 Browser 类型对象
browser = p.chromium.launch(headless=False)

# 创建新页面,返回 Page 类型对象
page = browser.new_page()
page.goto("https://www.byhy.net/cdn2/files/selenium/stock1.html")
print(page.title()) # 打印网页标题栏

# 输入通讯,点击查询。这是定位与操作,是自动化重点,后文详细讲解
page.locator('#kw').fill('通讯')  # 输入通讯
page.locator('#go').click()      # 点击查询

# 打印所有搜索内容
lcs = page.locator(".result-item").all()
for lc in lcs:
    print(lc.inner_text())

input('3....')
# 关闭浏览器
browser.close()
input('4....')
# 关闭 playwright driver 进程
p.stop()

切换浏览器启动

# 启动firefox浏览器
browser = p.firefox.launch(headless=False)

# 启动webkit浏览器
browser = p.webkit.launch(headless=False)

可以通过参数 <font style="color:rgb(45, 132, 172);">executable_path</font> 指定使用安装的其它浏览器

# 加上 executable_path参数
browser = p.chromium.launch(headless=False,
  executable_path='c:\Program Files\Google\Chrome\Application\chrome.exe')

可以使用 <font style="color:rgb(45, 132, 172);">with as</font> 会话管理,我们的代码简化,不需要调用 <font style="color:rgb(45, 132, 172);">start()</font><font style="color:rgb(45, 132, 172);">stop()</font>

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()
    page.goto("https://www.byhy.net/cdn2/files/selenium/stock1.html")
    print(page.title())
    page.locator('#kw').fill('通讯\n')
    page.locator('#go').click()
    # 打印所有搜索内容
    lcs = page.locator(".result-item").all()
    for lc in lcs:
        print(lc.inner_text())
    browser.close()

页面等待

page.wait_for_timeout(1000)

脚本录制

打开自动化助手

playwright codegen

报错可以尝试

 python -m playwright codegen

可以录制操作,但是无法获取信息和做一些后续处理

跟踪功能

Playwright 有个特色功能: 跟踪(tracing)

启用跟踪功能后, 可以在执行自动化后,通过记录的跟踪数据文件, 回看自动化过程中的每个细节。

下面的的代码进行了自动化搜索股票,并打开跟踪功能,保存 跟踪数据文件 为 <font style="color:rgb(45, 132, 172);">trace.zip</font>

from playwright.sync_api import sync_playwright

p = sync_playwright().start()
browser = p.chromium.launch(headless=False)

# 创建 BrowserContext对象
context = browser.new_context()
# 启动跟踪功能
context.tracing.start(snapshots=True, sources=True, screenshots=True)

page = context.new_page()
page.goto("https://www.byhy.net/cdn2/files/selenium/stock1.html")

# 搜索名称中包含 通讯 的股票
page.locator('#kw').fill('通讯') 
page.locator('#go').click()

page.wait_for_timeout(1000) # 等待1秒

lcs = page.locator(".result-item").all()
for lc in lcs:
    print(lc.inner_text())

# 搜索名称中包含 软件 的股票
page.locator('#kw').fill('软件')  
page.locator('#go').click()    

page.wait_for_timeout(1000) # 等待1秒

lcs = page.locator(".result-item").all()
for lc in lcs:
    print(lc.inner_text())

# 结束跟踪
context.tracing.stop(path="trace.zip")

browser.close()
p.stop()

执行完以后,我们发现,当前工作目录下面多了 trace.zip 这个跟踪数据文件。

怎么查看这个跟踪文件呢?有2种方法:

  • 直接访问 trace.playwright.dev 这个网站,上传 跟踪文件
  • 执行命令 <font style="color:rgb(45, 132, 172);">playwright show-trace trace.zip</font>

元素定位

一些常见的操作

  • 点击元素, <font style="color:rgb(45, 132, 172);">click()</font> 方法
  • 元素内输入文本, <font style="color:rgb(45, 132, 172);">fill()</font> 方法
  • 获取元素内部文本, <font style="color:rgb(45, 132, 172);">inner_text()</font> 方法

css定位

Locator对象

Playwright 中,根据 CSS Selector 选择元素,就是使用 Locator 类型的对象

比如,前文中, Page 对象的 locator方法就会创建一个 <font style="color:rgb(45, 132, 172);">Locator</font> 类型对象,参数就可以是 CSS Selector 表达式

page.locator('#kw').fill('通讯')
page.locator('#go').click()

Page对象的 locator 定位到的如果是唯一的 html元素,就可以调用 Locator 对象的 方法,比如 <font style="color:rgb(45, 132, 172);">fill</font> , <font style="color:rgb(45, 132, 172);">click</font> , <font style="color:rgb(45, 132, 172);">inner_text</font> 等等对元素进行操作了。

具体使用方法见下文

根据 tag名、id、class 选择元素

CSS Selector 可以根据 <font style="color:rgb(45, 132, 172);">tag名</font> <font style="color:rgb(45, 132, 172);">id 属性</font> <font style="color:rgb(45, 132, 172);">class属性</font> 来 选择元素,

根据 tag名 选择元素的 CSS Selector 语法非常简单,直接写上tag名即可,

比如 要选择 所有的tag名为div的元素,就可以是这样

locators = page.locator('div').all()

然后可以这样,打印所有的tag名为div的元素的内部可见文本

for one in locators:
    print(one.inner_text())

要获取 所有的tag名为div的元素的内部可见文本,也可以直接调用 <font style="color:rgb(45, 132, 172);">all_inner_texts</font>

texts = page.locator('div').all_inner_texts()

注意,如果 locator调用 <font style="color:rgb(45, 132, 172);">匹配的结果是多个元素</font> , 调用 <font style="color:rgb(45, 132, 172);">针对单个元素的方法</font> ,比如 <font style="color:rgb(45, 132, 172);">inner_text</font> ,会有错误抛出:

page.locator('div').inner_text()

根据id属性 选择元素的语法是在id号前面加上一个井号: <font style="color:rgb(45, 132, 172);">#id值</font>

比如 ,有下面这样的元素:

<input  type="text" id='searchtext' />

就可以使用 <font style="color:rgb(45, 132, 172);">#searchtext</font> 这样的 CSS Selector 来选择它。

比如,我们想在 <font style="color:rgb(45, 132, 172);">id 为 searchtext</font> 的输入框中输入文本 <font style="color:rgb(45, 132, 172);">你好</font> ,完整的Python代码如下

lct = page.locator('#searchtext')
lct.fill('你好')

根据class属性 选择元素的语法是在 class 值 前面加上一个点: <font style="color:rgb(45, 132, 172);">.class值</font>

要选择 class 属性值为 animal的元素 动物,可以这样写

page.locator('.animal')

一个 学生张三 可以定义有 <font style="color:rgb(45, 132, 172);">多个</font> 类型: <font style="color:rgb(45, 132, 172);">中国人</font> <font style="color:rgb(45, 132, 172);">学生</font>

<font style="color:rgb(45, 132, 172);">中国人</font> <font style="color:rgb(45, 132, 172);">学生</font> 都是 张三 的 类型。

元素也可以有 <font style="color:rgb(45, 132, 172);">多个class类型</font> ,多个class类型的值之间用 <font style="color:rgb(45, 132, 172);">空格</font> 隔开,比如

<span class="chinese student">张三</span>

注意,这里 span元素 有两个class属性,分别 是 chinese 和 student, 而不是一个 名为 <font style="color:rgb(45, 132, 172);">chinese student</font> 的属性。

我们要用代码选择这个元素,可以指定任意一个class 属性值,都可以匹配到这个元素,如下

page.locator('.chinese')

或者

page.locator('.student')

而不能这样写

page.locator('.chinese student')

如果要表示同时具有两个class 属性,可以这样写

page.locator('.chinese.student')

验证选择元素

那么我们怎么验证 CSS Selector 的语法是否正确选择了我们要选择的元素呢?

当然可以像下面这样,写出Python代码,运行看看,能否操作成功

page.locator('#searchtext').fill('输入的文本')

如果成功,说明选择元素的语法是正确的。

但是这样做的问题就是:太麻烦了。

当我们进行自动化开发的时候,有大量选择元素的语句,都要这样一个个的验证,就非常耗时间。

由于 CSS Selector 是浏览器直接支持的,可以在浏览器 开发者工具栏 中验证。

比如我们使用Chrome浏览器打开 https://www.byhy.net/cdn2/files/selenium/sample1.html

按F12 打开 开发者工具栏, 点击 Elements 标签后, 同时按 Ctrl 键 和 F 键, 就会出现下图箭头处的 搜索框

我们可以在里面输入任何 CSS Selector 表达式 ,如果能选择到元素, 右边的的红色方框里面就会显示出类似 <font style="color:rgb(45, 132, 172);">2 of 3</font> 这样的内容。

<font style="color:rgb(45, 132, 172);">of 后面</font> 的数字表示这样的表达式 <font style="color:rgb(45, 132, 172);">总共选择到几个元素</font>

<font style="color:rgb(45, 132, 172);">of 前面</font> 的数字表示当前黄色高亮显示的是 <font style="color:rgb(45, 132, 172);">其中第几个元素</font>

上图中的 <font style="color:rgb(45, 132, 172);">2 of 3</font> 就是指当前的 选择语法, 在当前网页上共选择到 3 个元素, 目前高亮显示的是第2个。

如果我们输入 <font style="color:rgb(45, 132, 172);">.plant</font> 就会发现,可以选择到3个元素

匹配多个元素

前面已经说, 如果一个 locator表达式匹配多个元素,要获取所有的元素对应的 locator 对象,使用 <font style="color:rgb(45, 132, 172);">all方法</font>

locators = page.locator('.plant').all()

有时,我们只需要得到某种表达式对应的元素数量 ,可以使用 <font style="color:rgb(45, 132, 172);">count方法</font>,如下

count = page.locator('.plant').count()

返回结果就是匹配的元素数量。 可以根据返回结果是否为0 判断元素是否存在

有时,我们只需要得到某种表达式对应的第一个,或者最后一个元素。

可以使用 <font style="color:rgb(45, 132, 172);">first</font> <font style="color:rgb(45, 132, 172);">last</font> 属性 , 如下

lct = page.locator('.plant')
print(lct.first.inner_text(), lct.last.inner_text())

也可以,通过 <font style="color:rgb(45, 132, 172);">nth</font> 方法,获取指定次序的元素,参数0表达第一个, 1 表示第2个,

比如

lct = page.locator('.plant')
print(lct.nth(1).inner_text())

元素内部定位

前面都是通过 <font style="color:rgb(45, 132, 172);">Page</font> 对象调用的 locator 方法, 定位的范围是整个网页。

如果我们想在某个元素内部定位,可以通过 <font style="color:rgb(45, 132, 172);">Locator</font> 对象 调用 locator 方法。

比如

lct = page.locator('#bottom')

# 在 #bottom 对应元素的范围内 寻找标签名为 span 的元素。
eles = lct.locator('span').all()
for e in eles:
    print(e.inner_text())

子元素和后端元素的CSS选择

  • 直接子元素
    • 父元素直接包裹,中间没有任何元素隔开(亲儿子)。
  • 后代元素
    • 嵌套在父元素内部所有层级的元素(儿子、孙子、重孙全部)。
  • PS:后代元素 包含 直接子元素

选择器语法

子元素选择使用 >

后代元素选择使用 [空格] (选择所有子孙)

属性选择

id、class 都是web元素的 <font style="color:rgb(45, 132, 172);">属性</font> ,因为它们是很常用的属性,所以css选择器专门提供了根据 id、class 选择的语法。

那么其他的属性呢?

比如

<a href="http://www.miitbeian.gov.cn">苏ICP备88885574号</a>

里面根据 href选择,可以用css 选择器吗?

当然可以!

css 选择器支持通过任何属性来选择元素,语法是用一个方括号 <font style="color:rgb(45, 132, 172);">[]</font>

比如要选择上面的a元素,就可以使用 <font style="color:rgb(45, 132, 172);">[href="http://www.miitbeian.gov.cn"]</font>

这个表达式的意思是,选择 属性href值为 <font style="color:rgb(45, 132, 172);">http://www.miitbeian.gov.cn</font> 的元素。

完整代码如下

from playwright.sync_api import sync_playwright

p = sync_playwright().start()
browser = p.chromium.launch(headless=False, slow_mo=50)
page = browser.new_page()
page.goto("https://www.byhy.net/cdn2/files/selenium/sample1.html")

# 根据属性选择元素
element = page.locator('[href="http://www.miitbeian.gov.cn"]')
# 打印出元素文本
print(element.inner_text())

当然,前面可以加上标签名的限制,比如 <font style="color:rgb(45, 132, 172);">a[href="http://www.miitbeian.gov.cn"]</font> 表示 选择所有 标签名为 <font style="color:rgb(45, 132, 172);">a</font> ,且 属性 href值为 <font style="color:rgb(45, 132, 172);">http://www.miitbeian.gov.cn</font> 的元素。

属性值用单引号,双引号都可以。

根据属性选择,还可以不指定属性值,比如 <font style="color:rgb(45, 132, 172);">[href]</font> , 表示选择 所有 具有 属性名 为href 的元素,不管它们的值是什么。

CSS 还可以选择 属性值 <font style="color:rgb(45, 132, 172);">包含</font> 某个字符串 的元素

比如, 要选择a节点,里面的href属性包含了 miitbeian 字符串,就可以这样写

a[href*="miitbeian"]

还可以 选择 属性值 以某个字符串 <font style="color:rgb(45, 132, 172);">开头</font> 的元素

比如, 要选择a节点,里面的href属性以 http 开头 ,就可以这样写

a[href^="http"]

还可以 选择 属性值 以某个字符串 <font style="color:rgb(45, 132, 172);">结尾</font> 的元素

比如, 要选择a节点,里面的href属性以 gov.cn 结尾 ,就可以这样写

a[href$="gov.cn"]

如果一个元素具有多个属性

<div class="misc" ctype="gun">沙漠之鹰</div>

CSS 选择器 可以指定 选择的元素要 同时具有多个属性的限制,像这样 <font style="color:rgb(45, 132, 172);">div[class=misc][ctype=gun]</font>

混合选择

CSS selector的另一个强大之处在于: 选择语法 可以 <font style="color:rgb(45, 132, 172);">联合使用</font>

比如, 我们要选择 如下网页中 html 中的元素 <font style="color:rgb(45, 132, 172);">版权1</font> 对应的 <font style="color:rgb(45, 132, 172);">span</font>

<div id='bottom'>
    <div class='footer1'>
        <span class='copyright' name='cp1'>版权1</span>
        <span class='copyright' name='cp2'>版权2</span>
        <span class='copyright1' name='cp1'>版权3</span>
    </div>
    <div class='footer2'>
        <span class='copyright' name='cp1'>版权4</span>
    </div>        
</div>

CSS selector 表达式 可以这样写:

.footer1 > .copyright[name=cp1]

组选择

注意,之前我们有学到如下的选择法

.plant .animal

这样说选择含有两个class的元素

而组选择是含有任意一个都会被选中


如果我们要 同时选择所有class 为 plant <font style="color:rgb(45, 132, 172);">和</font> class 为 animal 的元素。怎么办?

这种情况,css选择器可以 使用 <font style="color:rgb(45, 132, 172);">逗号</font> ,称之为 组选择 ,像这样

.plant , .animal

再比如,我们要同时选择所有tag名为div的元素 <font style="color:rgb(45, 132, 172);">和</font> id为BYHY的元素,就可以像这样写

div,#BYHY

对应的Playwright代码如下

elements = wd.find_elements(By.CSS_SELECTOR, 'div,#BYHY')
for element in elements:
    print(element.text)

我们再看一个例子

打开这个网址 请点击打开这个网址

我们要选择所有 唐诗里面的作者和诗名, 也就是选择所有 id 为 t1 里面的 <font style="color:rgb(45, 132, 172);">span 和 p 元素</font>

我们是不是应该这样写呢?

#t1 > span,p

不行哦,这样写的意思是 选择所有 <font style="color:rgb(45, 132, 172);">id 为 t1 里面的 span</font> <font style="color:rgb(45, 132, 172);">所有的 p 元素</font>

只能这样写

#t1 > span , #t1 > p

另外注意:组选择结果列表中,选中元素排序, 不是 组表达式的次序, 而是符合这些表达式的元素,在HTML文档中的出现的次序。

按次序选择节点

请点击打开这个网址

对应的html如下,关键信息如下

<body>  
       <div id='t1'>
           <h3> 唐诗 </h3>
           <span>李白</span>
           <p>静夜思</p>
           <span>杜甫</span>
           <p>春夜喜雨</p>              
       </div>      
        
       <div id='t2'>
           <h3> 宋词 </h3>
           <span>苏轼</span>
           <p>赤壁怀古</p>
           <p>明月几时有</p>
           <p>江城子·乙卯正月二十日夜记梦</p>
           <p>蝶恋花·春景</p>
           <span>辛弃疾</span>
           <p>京口北固亭怀古</p>
           <p>青玉案·元夕</p>
           <p>西江月·夜行黄沙道中</p>
       </div>             

    </body>

父元素的第n个子节点

我们可以指定选择的元素 <font style="color:rgb(45, 132, 172);">是父元素的第几个子节点</font>

使用 <font style="color:rgb(45, 132, 172);">nth-child</font>

比如,

我们要选择 唐诗 和宋词 的第一个 作者,

也就是说 选择的是 第2个子元素,并且是span类型

所以这样可以这样写 <font style="color:rgb(45, 132, 172);">span:nth-child(2)</font>

如果你不加节点类型限制,直接这样写 <font style="color:rgb(45, 132, 172);">:nth-child(2)</font>

就是选择所有位置为第2个的所有元素,不管是什么类型

父元素的第几个某类型的子节点🏷️

我们可以指定选择的元素 是父元素的第几个 <font style="color:rgb(45, 132, 172);">某类型的</font> 子节点

使用 <font style="color:rgb(45, 132, 172);">nth-of-type</font>

比如,

我们要选择 唐诗 和宋词 的第一个 作者,

可以像上面那样思考:选择的是 第2个子元素,并且是span类型

所以这样可以这样写 <font style="color:rgb(45, 132, 172);">span:nth-child(2)</font>

还可以这样思考,选择的是 <font style="color:rgb(45, 132, 172);">第1个span类型</font> 的子元素

所以也可以这样写 <font style="color:rgb(45, 132, 172);">span:nth-of-type(1)</font>

父元素的倒数第几个某类型的子节点🏷️

当然也可以反过来, 选择父元素的 <font style="color:rgb(45, 132, 172);">倒数第几个某类型</font> 的子节点

使用 <font style="color:rgb(45, 132, 172);">nth-last-of-type</font>

像这样

p:nth-last-of-type(2)

奇数节点和偶数节点

如果要选择的是父元素的 <font style="color:rgb(45, 132, 172);">偶数节点</font>,使用 <font style="color:rgb(45, 132, 172);">nth-child(even)</font>

比如

p:nth-child(even)

如果要选择的是父元素的 <font style="color:rgb(45, 132, 172);">奇数节点</font>,使用 <font style="color:rgb(45, 132, 172);">nth-child(odd)</font>

p:nth-child(odd)

如果要选择的是父元素的 <font style="color:rgb(45, 132, 172);">某类型偶数节点</font>,使用 <font style="color:rgb(45, 132, 172);">nth-of-type(even)</font>

如果要选择的是父元素的 <font style="color:rgb(45, 132, 172);">某类型奇数节点</font>,使用 <font style="color:rgb(45, 132, 172);">nth-of-type(odd)</font>

兄弟元素选择

两种方式,+就是选择紧跟后面的元素,~就是后面的所有元素

Playwright推荐定位方式

以上都是根据网页特征进行定位的方式

在官方的优先推荐的方式是基于用户视觉进行定位的,因为HTML写法很容易就会改变

根据文本内容进行定位

有时我们想获取页面包含某些文字的元素, 这用 css selector 不好选择,可以使用 Page/Locator 对象的 get_by_text 方法

比如,点击打开这个网页

如果要获取 所有 文本内容包含 <font style="color:rgb(45, 132, 172);">11</font> 的元素,就可以这样

from playwright.sync_api import sync_playwright
p = sync_playwright().start()

browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://www.byhy.net/cdn2/files/selenium/stock1.html")

# 根据文本内容选择所有元素
elements = page.get_by_text('11').all()

# 打印出元素文本
for ele in elements:
    print(ele.inner_text())

运行发现,打印结果为

600111
600113
600115

如果,你希望包含的内容是以 <font style="color:rgb(45, 132, 172);">11</font> 结尾的,就可以使用正则表达式对象 作为参数,如下

import re
elements = page.get_by_text(re.compile("11$")).all()

正则表达式 <font style="color:rgb(45, 132, 172);">11$</font> 表示以 <font style="color:rgb(45, 132, 172);">11</font> 结尾,通过正则表达式,我们可以进行各种复杂的基于文本模式的定位。

关于详细的 Python 正则表达式 的用法,点击这里学习

元素Role定位

Playwright 支持根据 元素 <font style="color:rgb(45, 132, 172);">角色 role)</font> 定位。

这是个很让人迷糊的定位方式。

什么叫 元素的 <font style="color:rgb(45, 132, 172);">角色</font>

web应用现在有一种标准 称之为: <font style="color:rgb(45, 132, 172);">ARIA (Accessible Rich Internet Applications)</font>

ARIA 根据web界面元素的用途,为这些元素定义了一套 角色( Role ) 信息,添加到页面中,

从而 让 残疾人士,或者 普通人在 某种环境下(比如夜里,太空中),不方便使用常规方法操作应用,使用辅助技术工具,来操作web应用的。

ARIA 为web元素增加了一些 Role相关的属性定义,方便 <font style="color:rgb(45, 132, 172);">辅助技术工具</font> 识别和操作。

比如:当我们完成系统注册时,弹出提示信息,html通常会这样

<div class="alert-message">
  您已成功注册,很快您将收到一封确认电子邮件
</div>

但是,这样写的内容不方便辅助技术识别出这是一个重要的信息,需要读给用户听。

这时,可以加上 ARIA role 属性设置,如下

<div class="alert-message" role="alert">
  您已成功注册,很快您将收到一封确认电子邮件
</div>

因为 <font style="color:rgb(45, 132, 172);">role="alert"</font> 是 ARIA规范里面的属性, 辅助系统(比如读屏系统)会特别注意,就会侦测到,并且实时读出来。

所以,直接可以根据如下代码 定位该元素

# 根据 role 定位
lc = page.get_by_role('alert')

# 打印元素文本
print(lc.inner_text())

html元素中,有些 特定语义元素(semantic elements)被ARIA规范认定为自身就包含 ARIA role 信息,并不需要我们明显的加上 ARIA role 属性设置,

比如

<progress value="75" max="100">75 %</progress>

就等于隐含了如下信息

<progress value="75" max="100"
  role="progressbar"
  aria-valuenow="75"
  aria-valuemax="100">75 %</div>

所以,直接可以根据如下代码 定位该元素

# 根据 role 定位
lc = page.get_by_role('progressbar')

# 打印元素属性 value 的值
print(lc.get_attribute('value'))

再比如 <font style="color:rgb(45, 132, 172);">search</font> 类型的输入框,默认就有 <font style="color:rgb(45, 132, 172);">searchbox</font> role,

<input type="search">

所以,直接可以根据如下代码 定位该元素

lc = page.get_by_role('searchbox')
print(lc.fill('白月黑羽'))

ARIA Role

Playwright 支持根据 元素 <font style="color:rgb(45, 132, 172);">角色 role)</font> 定位。

这是个很让人迷糊的定位方式。

什么叫 元素的 <font style="color:rgb(45, 132, 172);">角色</font>

web应用现在有一种标准 称之为: <font style="color:rgb(45, 132, 172);">ARIA (Accessible Rich Internet Applications)</font>

ARIA 根据web界面元素的用途,为这些元素定义了一套 角色( Role ) 信息,添加到页面中,

从而 让 残疾人士,或者 普通人在 某种环境下(比如夜里,太空中),不方便使用常规方法操作应用,使用辅助技术工具,来操作web应用的。

ARIA 为web元素增加了一些 Role相关的属性定义,方便 <font style="color:rgb(45, 132, 172);">辅助技术工具</font> 识别和操作。

比如:当我们完成系统注册时,弹出提示信息,html通常会这样

<div class="alert-message">
  您已成功注册,很快您将收到一封确认电子邮件
</div>

但是,这样写的内容不方便辅助技术识别出这是一个重要的信息,需要读给用户听。

这时,可以加上 ARIA role 属性设置,如下

<div class="alert-message" role="alert">
  您已成功注册,很快您将收到一封确认电子邮件
</div>

因为 <font style="color:rgb(45, 132, 172);">role="alert"</font> 是 ARIA规范里面的属性, 辅助系统(比如读屏系统)会特别注意,就会侦测到,并且实时读出来。

所以,直接可以根据如下代码 定位该元素

# 根据 role 定位
lc = page.get_by_role('alert')

# 打印元素文本
print(lc.inner_text())

html元素中,有些 特定语义元素(semantic elements)被ARIA规范认定为自身就包含 ARIA role 信息,并不需要我们明显的加上 ARIA role 属性设置,

比如

<progress value="75" max="100">75 %</progress>

就等于隐含了如下信息

<progress value="75" max="100"
  role="progressbar"
  aria-valuenow="75"
  aria-valuemax="100">75 %</div>

所以,直接可以根据如下代码 定位该元素

# 根据 role 定位
lc = page.get_by_role('progressbar')

# 打印元素属性 value 的值
print(lc.get_attribute('value'))

再比如 <font style="color:rgb(45, 132, 172);">search</font> 类型的输入框,默认就有 <font style="color:rgb(45, 132, 172);">searchbox</font> role,

<input type="search">

所以,直接可以根据如下代码 定位该元素

lc = page.get_by_role('searchbox')
print(lc.fill('白月黑羽'))

ARIA Attribute

ARIA规范除了可以给元素添加 <font style="color:rgb(45, 132, 172);">ARIA role</font> ,还可以添加其它 ARIA属性(ARIA attributes) ,比如

<div role="heading" aria-level="1">白月黑羽标题1</div>
<div role="heading" aria-level="2">白月黑羽标题2</div>

<font style="color:rgb(45, 132, 172);">aria-level</font> 就是一个 ARIA 属性,表示 role 为 <font style="color:rgb(45, 132, 172);">heading</font> 时的 <font style="color:rgb(45, 132, 172);">等级</font> 信息

上面的定义,其实和下面的 html 元素 h1/h2 等价

<h1>白月黑羽标题1</h1>
<h2>白月黑羽标题2</h2>

<font style="color:rgb(45, 132, 172);">h1</font> 隐含了 <font style="color:rgb(45, 132, 172);">role="heading" aria-level="1"</font> 属性

<font style="color:rgb(45, 132, 172);">h2</font> 隐含了 <font style="color:rgb(45, 132, 172);">role="heading" aria-level="2"</font> 属性

Playwright 对常见的 ARIA 属性 ,有额外的参数对应,

比如 <font style="color:rgb(45, 132, 172);">aria-checked/aria-disabled/aria-expanded/aria-level</font> 等等

上例中,h2 元素,隐含了 <font style="color:rgb(45, 132, 172);">role="heading" aria-level="2"</font>, 所以可以用下面代码定位

lc = page.get_by_role('heading',level=2)
print(lc.inner_text())

Accessible Name

只根据 <font style="color:rgb(45, 132, 172);">ARIA role</font> <font style="color:rgb(45, 132, 172);">ARIA属性</font> 往往并不能唯一定位元素。

role定位最常见的组合是 <font style="color:rgb(45, 132, 172);">ARIA role</font> Accessible Name

因为,<font style="color:rgb(45, 132, 172);">Accessible Name</font> 就像元素的 <font style="color:rgb(45, 132, 172);">名字</font> 一样,往往可以唯一定位。

html 元素标准属性 <font style="color:rgb(45, 132, 172);">name</font> 是浏览器内部的,用户看不到,比如

<a name='link2byhy' href="https://www.byhy.net">白月黑羽教程</a>

<font style="color:rgb(45, 132, 172);">Accessible Name</font> 不一样,它是元素界面可见的文本名,

比如上面的元素,暗含的 <font style="color:rgb(45, 132, 172);">Accessible Name</font> 值就是 <font style="color:rgb(45, 132, 172);">白月黑羽教程</font> , 当然也暗含了 <font style="color:rgb(45, 132, 172);">ARIA role</font> 值为 <font style="color:rgb(45, 132, 172);">link</font>

所以,可以这样定位

lc = page.get_by_role('link',name='白月黑羽教程')
print(lc.click())

上面的写法, 只要 Accessible Name 包含 参数name 的字符串内容即可,而且大小写不分, 并不需要完全一致。

所以,这样也可以定位到

lc = page.get_by_role('link',name='白月黑羽')

如果你需要 Accessible Name 和 参数name 的内容完全一致,可以指定 <font style="color:rgb(45, 132, 172);">exact=True</font> ,如下

lc = page.get_by_role('link',name='白月黑羽', exact=True)

name值还可以通过正则表达式,进行较复杂的匹配规则,比如

lc = page.get_by_role('link',name=re.compile("^白月.*羽"))

那么除了html 元素 a 以外, 哪些元素是自带 Accessible Name 属性的呢?

他们的 Accessible Name 值 又是怎么确定的呢?

w3c有个计算规则文档,很复杂,不容易看懂。

我们这里说一些常见的:

<font style="color:rgb(45, 132, 172);"><a> <td> <button></font> Accessible Name 值 就是其内部的文本内容。

<font style="color:rgb(45, 132, 172);"><textarea> <input></font> 这些输入框,它们的 Accessible Name 值 是和他们关联的 的文本。

比如:

<label>
  <input type="checkbox" /> Subscribe
</label>

这个 checkbox 的 Accessible Name 却是 <font style="color:rgb(45, 132, 172);">Subscribe</font> ,应该这样定位

page.get_by_role("checkbox", name="Subscribe")

另外,一些元素 比如 <font style="color:rgb(45, 132, 172);"><img></font> ,它的 Accessible Name 是其html 属性 <font style="color:rgb(45, 132, 172);">alt</font> 的值

比如

<img src="grape.jpg" alt="banana"/>

它的 Accessible Name 值为 <font style="color:rgb(45, 132, 172);">banana</font> ,role 为 <font style="color:rgb(45, 132, 172);">img</font>

其他基于用户视觉的定位

下面的这4种定位,也属于根据用户视觉上的内容定位。

可以通过代码助手产生,其实也完全可以用 css selector 定位替代,了解即可。

根据 元素 placeholder 定位🏷️

<font style="color:rgb(45, 132, 172);">input</font> 元素,通常都有 <font style="color:rgb(45, 132, 172);">placeholder</font> 属性,

可以使用 Page/Locator 对象的 get_by_placeholder 方法,根据 <font style="color:rgb(45, 132, 172);">placeholder</font> 属性值定位。

比如

<input type="text" placeholder="captcha" />

就可以这样定位

page.get_by_placeholder('captcha',exact=True).fill('白月黑羽')

参数 <font style="color:rgb(45, 132, 172);">exact</font> 值为 <font style="color:rgb(45, 132, 172);">True</font> ,表示完全匹配,且区分大小写。如果值为False,就只需包含参数字符串即可,且不区分大小写。

作用类似 get_by_role 里面的 <font style="color:rgb(45, 132, 172);">exact</font> 参数

根据 元素关联的 label 定位🏷️

<font style="color:rgb(45, 132, 172);">input</font> 元素,通常都有关联的 label

可以使用 Page/Locator 对象的 get_by_label 方法,根据 元素关联的 label 定位。

比如

<input aria-label="Username">
  <label for="password-input">Password:</label>
  <input id="password-input">

就可以这样定位

page.get_by_label("Username").fill("john")
page.get_by_label("Password").fill("secret")

<font style="color:rgb(45, 132, 172);">get_by_label</font> 也有 <font style="color:rgb(45, 132, 172);">exact</font> 参数,作用和 <font style="color:rgb(45, 132, 172);">get_by_placeholder</font> 里面的 <font style="color:rgb(45, 132, 172);">exact</font> 参数 一样。

根据 元素的 alt text 定位

有些元素,比如 <font style="color:rgb(45, 132, 172);">img</font> 元素,通常都有 <font style="color:rgb(45, 132, 172);">alt</font> 属性

可以使用 Page/Locator 对象的 get_by_alt_text 方法,根据 元素的 <font style="color:rgb(45, 132, 172);">alt</font> 属性值 定位

比如

<img 
  src="https://doc.qt.io/qtforpython/_images/windows-pushbutton.png" 
  alt="qt-button">

就可以这样定位

href = page.get_by_alt_text("qt-button").get_attribute('src')
print(href)

<font style="color:rgb(45, 132, 172);">get_by_alt_text</font> 也有 <font style="color:rgb(45, 132, 172);">exact</font> 参数,作用和 <font style="color:rgb(45, 132, 172);">get_by_placeholder</font> 里面的 <font style="color:rgb(45, 132, 172);">exact</font> 参数 一样。

根据 元素 title 定位

有些元素,比如 <font style="color:rgb(45, 132, 172);">span</font>, <font style="color:rgb(45, 132, 172);">a</font> 等等,可能有 <font style="color:rgb(45, 132, 172);">title</font> 属性,当鼠标悬浮在该元素上时,可以显示title属性内在一个提示框里面

可以使用 Page/Locator 对象的 get_by_title 方法,根据 元素的 <font style="color:rgb(45, 132, 172);">title</font> 属性值 定位

比如

<a href="https://www.byhy.net" title="byhy首页">白月黑羽教程</a>

就可以这样定位

page.get_by_title("byhy首页").click()

<font style="color:rgb(45, 132, 172);">get_by_title</font> 也有 <font style="color:rgb(45, 132, 172);">exact</font> 参数,作用和 <font style="color:rgb(45, 132, 172);">get_by_placeholder</font> 里面的 <font style="color:rgb(45, 132, 172);">exact</font> 参数 一样。

等待时间

Playwright 中,当我们定位元素(比如 通过locator/get_by_text 等方法)后,对元素进行操作(比如 click, fill),

如果当时根据定位条件,找不到这个元素, Playwright并不会立即抛出错误, 而是缺省等待元素时间为30秒,在30秒内如果元素出现了,就立即操作成功返回。

比如,点击打开这个网页

这个网页,我们输入股票名称关键字,点击搜索后, 搜索结果并不是立即返回的,而是有 一定的延时。

前面我们讲 Selenium时,发现如果在定位 id为1的元素不sleep或者设置缺省等待时间, 会报错。

但是,如下代码并没有sleep之类的等待,也没有设置缺省等待时间,为什么在 打印 id为1的元素文本时,不会报错呢?

from playwright.sync_api import sync_playwright

p = sync_playwright().start()
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://www.byhy.net/cdn2/files/selenium/stock1.html")
page.locator('#kw').fill('通讯\n')
page.locator('#go').click()
element = page.locator("[id='1']")

print(element.inner_text())

因为Playwright不需要我们额外设置,本来元素操作时,如果根据定位规则找不到元素,就会等待最多 30秒。

如果我们修改下面的代码

print(element.inner_text())

改为

print(element.inner_text(timeout=10 ))

表示等待元素出现时长修改为10毫秒,再运行,就会有错误了。

如果,我们想修改 <font style="color:rgb(45, 132, 172);">缺省</font> 等待时间, 可以使用 <font style="color:rgb(45, 132, 172);">BrowserContext</font> 对象, 如下

from playwright.sync_api import sync_playwright
p = sync_playwright().start()

browser = p.chromium.launch(headless=False, slow_mo=50)
context = browser.new_context()
context.set_default_timeout(10) #修改缺省等待时间为10毫秒
page = context.new_page() # 通过context 创建Page对象
page.goto("https://www.byhy.net/cdn2/files/selenium/stock1.html")
page.locator('#kw').fill('通讯\n')
page.locator('#go').click()

element = page.locator("[id='1']")
print(element.inner_text())

可以发现缺省等待时间修改为10毫秒,后续操作等待时间无需单独指定,都是10毫秒了。

元素通用操作

获取文本内容

前面我们学过 通过 Locator对象的 inner_text() 方法 可以获取元素的内部文本,

如果Locator选择到的元素是多个,可以使用 all_inner_texts 获取所有匹配的文本,放到列表中返回。

上面者两个方法返回的都是元素内部的 <font style="color:rgb(45, 132, 172);">可见</font> 文本。

html元素内部还可以包括不可见的文本,比如

<p id="source">
  <span id='text'>看一下<br>这个内容<br>如何变化</span>
  <span style="display:none">隐藏内容</span>
</p>

要获取p元素内部所有内容,包括隐藏内容,可以用 Locator对象的 text_content() 或者 all_text_contents() 方法 获取 <font style="color:rgb(45, 132, 172);">单个</font> 或者 <font style="color:rgb(45, 132, 172);">多个</font> 匹配对象文本

如下所示

lc = page.locator("#source")
print('innerText:',   lc.inner_text())
print('--------------')
print('textContent:', lc.text_content())

大家可以对比一下区别

获取元素属性

获取元素的属性值,可以使用 Locator对象的 get_attribute 方法

获取元素内部HTML

获取元素内部的整个html文本, 可以使用 Locator对象的 inner_html 方法

点击

click点击

dbclick双击

悬停

让光标悬停在某个元素上方,可以使用 Locator对象的 hover 方法

等待元素可见

前面讲过,Playwright通过Locator对元素进行操作时,如果元素当前还没有出现,缺省就会等待30秒。

但是,有时我们的代码并不是要操作这个元素,而是要等待这个元素出现后,进行别的操作。

这时,可以使用 Locator对象的 wait_for 方法

比如

page.locator("#source").wait_for()

该方法有个参数 <font style="color:rgb(45, 132, 172);">state</font> ,缺省值为 <font style="color:rgb(45, 132, 172);">'visible'</font> , 就是等待元素可见。

如果值为 <font style="color:rgb(45, 132, 172);">'hidden'</font> 就是等待该元素消失。

等待时长为全局设定的时长, 缺省为 30秒,如果要修改,可以使用参数 <font style="color:rgb(45, 132, 172);">timeout</font>

超出时长,元素还没有出现在界面上,会抛出错误

判断元素是否可见

有时,我们的自动化代码需要根据当前界面中,<font style="color:rgb(45, 132, 172);">是否存在某些内容</font> ,来决定下一步操作。

这时,可以使用 Locator对象的 is_visible 方法

比如

page.locator("#source").is_visible()

该方法不会等待元素出现,而是立即返回 True 或 False 。

输入框操作

文本框输入

单行文本框 <font style="color:rgb(45, 132, 172);">input</font> 或者 多行文本框 <font style="color:rgb(45, 132, 172);">textarea</font> 都可以使用 Locator对象的 fill 方法进行输入

文本框清空

要清空 单行文本框 <font style="color:rgb(45, 132, 172);">input</font> 或者 多行文本框 <font style="color:rgb(45, 132, 172);">textarea</font> 的内容,都可以使用 Locator对象的 clear 方法

获取输入框里面的文字

如果要获取输入框 <font style="color:rgb(45, 132, 172);"><input></font> <font style="color:rgb(45, 132, 172);"><textarea></font> 对应的用户输入文本内容,不能用 <font style="color:rgb(45, 132, 172);">inner_text()</font> 方法。

而是应该用 input_value 方法

文件输入框

html中 有文件类型的输入框,用于指定本地文件, 通常用于上传文件

<input type="file" multiple="multiple">

要设置选中的文件,可以使用 Locator 对象的 set_input_files 方法。

比如

# 先定位
lc = page.locator('input[type=file]')

# 单选一个文件
lc.set_input_files('d:/1.png')

# 或者 多选 文件
lc.set_input_files(['d:/1.png', 'd:/2.jpg'])

单选多选框选择

radio单选/checkbox多选

请点击打开这个网址

并且按F12,观察HTML的内容

常见的选择框包括: radio框、checkbox框、select框

<font style="color:rgb(45, 132, 172);">radio</font> 是常见的 点选 元素

比如, 下面的 html:

<div id="s_radio">
  <input type="radio" name="teacher" value="小江老师">小江老师<br>
  <input type="radio" name="teacher" value="小雷老师">小雷老师<br>
  <input type="radio" name="teacher" value="小凯老师" checked="checked">小凯老师
</div>

如果要点选 radio框, 可以使用 Locator对象的 check 方法

如果要取消选择 radio框, 可以使用 Locator对象的 uncheck 方法

如果我们要判断 radio框 是否选中,可以使用 Locator对象的 is_checked 方法

假设,我们对上面html中的 radio 输入框

  • 先打印当前选中的老师名字
  • 再选择 小雷老师

对应的代码如下

# 获取当前选中的元素
lcs = page.locator('#s_radio input[name="teacher"]:checked').all()
teachers = [lc.get_attribute('value')  for lc in lcs ]
print('当前选中的是:', ' '.join(teachers))

# 确保点选 小雷老师
page.locator("#s_radio input[value='小雷老师']").check()

<font style="color:rgb(45, 132, 172);">checkbox</font> 是常见的 勾选 元素

比如, 下面的html:

<div id="s_checkbox">
  <input type="checkbox" name="teacher" value="小江老师" checked="checked">小江老师<br>
  <input type="checkbox" name="teacher" value="小雷老师">小雷老师<br>
  <input type="checkbox" name="teacher" value="小凯老师" checked="checked">小凯老师
</div>

和 radio input 一样,

如果要点选 checkbox框, 可以使用 Locator对象的 check 方法

如果要取消选择 checkbox框, 可以使用 Locator对象的 uncheck 方法

如果我们要判断 checkbox 框 是否选中,可以使用 Locator对象的 is_checked 方法

比如, 我们要在前面面的html中

  • 先打印当前选中的老师名字
  • 再选择 小雷老师

对应的代码如下

# 获取当前选中的元素
lcs = page.locator('#s_checkbox input[name="teacher"]:checked').all()
teachers = [lc.get_attribute('value')  for lc in lcs ]
print('当前选中的是:', ' '.join(teachers))

# 点选 小雷老师
page.locator("#s_checkbox input[value='小雷老师']").click()

select元素操作

radio框及checkbox框都是input元素,只是里面的type不同而已。

select框 则是一个新的select标签,大家可以对照浏览器网页内容查看一下

要选择选项,可以使用 <font style="color:rgb(45, 132, 172);">select</font> 元素对应的 Locator对象的 select_option 方法

select单选框

对于 select单选框:

不管原来选的是什么,直接用Select方法选择即可。

例如,选择示例里面的小江老师,示例代码如下

page.locator('#ss_single').select_option('小江老师')

这里 select_option 参数是 选项 <font style="color:rgb(45, 132, 172);">option</font> 元素 的 <font style="color:rgb(45, 132, 172);">value 或者 选项文本</font> , 要完全匹配。

也可以使用关键字参数 <font style="color:rgb(45, 132, 172);">index</font> , <font style="color:rgb(45, 132, 172);">value</font> , <font style="color:rgb(45, 132, 172);">label</font> 指定分别根据 索引,value属性, 选项文本 进行匹配

比如

# 根据 索引 选择, 从0 开始, 但是为0的时候,好像有bug
page.locator('#ss_single').select_option(index=1)

# 根据 value属性 选择
page.locator('#ss_single').select_option(value='小江老师')

# 根据 选项文本 选择
page.locator('#ss_single').select_option(label='小江老师')

# 清空所有选择
page.locator('#ss_single').select_option([])

select多选框

对于select多选框,要选中某几个选项,同样可以使用上面的方法,参数为包含多个值的列表即可

比如

# 根据 value属性 或者 选项文本 多选
page.locator('#ss_multi').select_option(['小江老师', '小雷老师'])

# 根据 value属性 多选
page.locator('#ss_multi').select_option(value=['小江老师', '小雷老师'])

# 根据 选项文本 多选
page.locator('#ss_multi').select_option(label=['小江老师', '小雷老师'])

# 清空所有选择
page.locator('#ss_multi').select_option([])

注意,原来已经选中的选项,没有出现在 参数里面的,自动被清除选择。

获取select选中选项

同样可以通过css selector 表达式 <font style="color:rgb(45, 132, 172);">:checked</font> 伪选择 获取所有选中的 select选项

比如:

page.locator('#ss_multi').select_option(['小江老师','小雷老师'])

lcs = page.locator('#ss_multi option:checked').all_inner_texts()
print(lcs)

网页操作

<font style="color:rgb(45, 132, 172);">打开网址/刷新/前进/后退</font> , 可以分别调用 Page 对象的 <font style="color:rgb(45, 132, 172);">goto/reload/go_back/go_forward</font> 方法

<font style="color:rgb(45, 132, 172);">获取整个网页对应的HTML</font> , 可以调用 Page 对象的 <font style="color:rgb(45, 132, 172);">content</font> 方法

<font style="color:rgb(45, 132, 172);">获取整个网页的标题栏文本</font> , 可以调用 Page 对象的 <font style="color:rgb(45, 132, 172);">title</font> 方法

<font style="color:rgb(45, 132, 172);">设置页面大小</font> , 可以调用 Page 对象的 <font style="color:rgb(45, 132, 172);">set_viewport_size</font> 方法

示例代码

# 导入Playwright同步API(新手首选)
from playwright.sync_api import sync_playwright

def main():
    # 1. 启动Playwright
    with sync_playwright() as p:
        # 启动Chromium浏览器,headless=False=显示浏览器窗口(方便看操作)
        browser = p.chromium.launch(headless=False)
        # 创建新页面
        page = browser.new_page()

        # ====================== set-viewport-size:设置页面大小(像素) ======================
        page.set_viewport_size({"width": 640, "height": 480})
        print("✅ 已设置页面大小:640x480像素")

        # ====================== goto:打开网址 ======================
        page.goto("https://www.baidu.com")
        print("✅ 已打开百度首页")

        # ====================== title:获取网页标题 ======================
        page_title = page.title()
        print(f"📝 网页标题:{page_title}")

        # ====================== content:获取完整网页HTML ======================
        page_html = page.content()
        print(f"🌐 网页HTML(前500字符):{page_html[:500]}...")

        # ====================== reload:刷新页面 ======================
        page.reload()
        print("🔄 已刷新页面")

        # ====================== 测试 前进/后退 功能 ======================
        # 先打开第二个网页(制造历史记录)
        page.goto("https://www.sina.com.cn")
        print("✅ 已打开新浪首页")

        # go_back:后退(回到上一个页面:百度)
        page.go_back()
        print("⬅️ 已后退到百度首页")

        # go_forward:前进(回到下一个页面:新浪)
        page.go_forward()
        print("➡️ 已前进到新浪首页")

        # 等待3秒,观察操作效果
        page.wait_for_timeout(3000)

        # 关闭浏览器
        browser.close()

if __name__ == "__main__":
    main()

界面切换

frame切换

请大家点击这里,打开这个链接

如果我们要 选择 下图方框中 所有的 蔬菜,使用css选择,怎么写表达式?

当然,要先查看到它们的html元素特征

大家可能会照旧写出如下代码:

from playwright.sync_api import sync_playwright
p = sync_playwright().start()

browser = p.chromium.launch(headless=False)
page = browser.new_page()

page.goto("https://www.byhy.net/cdn2/files/selenium/sample2.html")

lcs = page.locator('.plant').all()
for lc in lcs:
    print(lc.inner_text(timeout=1000))

运行一下,你就会发现,运行结果打印内容为空白,说明没有选择到 class 属性值为 plant 的元素。

为什么呢?

因为仔细看,你可以发现, 这些元素是在一个叫 <font style="color:rgb(45, 132, 172);">iframe</font> 的 元素中的。

这个 iframe 元素非常的特殊, 在html语法中, <font style="color:rgb(45, 132, 172);">frame</font> 元素 或者 <font style="color:rgb(45, 132, 172);">iframe</font> 元素的内部 会包含一个 被嵌入的 另一份html文档。

在我们使用 Playwright 打开一个网页时, 操作范围 缺省是当前的 html , 并不包含被嵌入的html文档里面的内容。

如果我们要 定位/操作 被嵌入的 html 文档 中的元素, 就必须 <font style="color:rgb(45, 132, 172);">切换操作范围</font> 到 被嵌入的文档中。

怎么切换呢?

使用 Page 或者 Locator 对象的 <font style="color:rgb(45, 132, 172);">frame_locator</font> 方法定位到你要操作的frame。

这个 方法会产生一个 <font style="color:rgb(45, 132, 172);">FrameLocator</font> 对象,后续的定位,就使用这个对象,在其内部进行定位。

像这样

# 产生一个  FrameLocator 对象
frame = page.frame_locator("iframe[src='sample1.html']")

# 再 在其内部进行定位
lcs = frame.locator('.plant').all()
for lc in lcs:
    print(lc.inner_text(timeout=1000))

其中, <font style="color:rgb(45, 132, 172);">frame_locator</font> 方法的参数是 css 或者 xpath selector

窗口切换

在网页上操作的时候,我们经常遇到,点击一个链接 或者 按钮,就会打开一个 <font style="color:rgb(45, 132, 172);">新窗口</font>

请大家点击这里,打开这个链接

在打开的网页中,点击 链接 <font style="color:rgb(45, 132, 172);">访问bing网站</font> , 就会弹出一个新窗口,访问bing网址。

如果我们用 Playwright 写自动化程序 在新窗口里面 打开一个新网址, 并且去自动化操作新窗口里面的元素,会有什么问题呢?

我们可以运行如下代码验证一下

from playwright.sync_api import sync_playwright
p = sync_playwright().start()

browser = p.chromium.launch(headless=False)
page = browser.new_page()

page.goto("https://www.byhy.net/cdn2/files/selenium/sample3.html")

# 点击链接,打开新窗口
page.locator("a").click()

# 打印网页窗口标题
print(page.title())

运行完程序后,最后一行 打印当前窗口的标题栏 文本, 输出内容是

白月黑羽测试网页3

问题就在于,即使新窗口打开了, 这时候,我们的 page 变量对应的 还是老窗口,自动化操作也还是在老窗口进行,

如果我们要到新的窗口里面操作,该怎么做呢?

这时,需要使用 BrowserContext 对象。如下

from playwright.sync_api import sync_playwright
pw = sync_playwright().start()

browser = pw.chromium.launch(headless=False)

# 创建 BrowserContext 对象
context = browser.new_context()

# 通过context 创建page
page = context.new_page() 

page.goto("https://www.byhy.net/cdn2/files/selenium/sample3.html")

# 点击链接,打开新窗口
page.locator("a").click()

# 等待2秒, 不能用 time.sleep
page.wait_for_timeout(2000)

# pages属性是 所有窗口对应Page对象的列表
newPage = context.pages[1]

# 打印新网页窗口标题
print(newPage.title())

# 打印老网页窗口标题
print(page.title())

<font style="color:rgb(45, 132, 172);">BrowserContext</font> 对象有个 <font style="color:rgb(45, 132, 172);">pages</font> 属性,这是一个列表,里面依次为所有窗口对应Page对象。

我们就可以通过不同的page对象操作对应的不同窗口了。

如果自动化打开了很多链接窗口,不知道目标窗口的次序号,这时可以根据标题栏定位到要操作的page

那么我们就可以通过 类似下面的代码,

for pg in  context.pages:
    # 得到该窗口的标题栏字符串,判断是不是我们要操作的那个窗口
    if '必应' in pg.title():
        break

print(pg.title())

设置当前tab

如果当前界面有很多窗口,要把某个窗口作为当前活动窗口显示出来,可以调用该窗口对应的Page对象的 bring_to_front 方法。

关闭网页窗口

前面我们学过Browser对象有close 方法,那是关闭整个浏览器。

如果只是要关闭某个网页窗口,可以调用该窗口对应的Page对象的 close 方法。

冻结页面操作

有些网站上面的元素, 我们鼠标放在上面,会动态弹出一些内容。

比如,百度首页的导航栏,有个 更多 选项

如果我们把鼠标放在上边,就会弹出 一个更多产品内容的图标页 。

如果我们要用 Playwright 自动化 点击 其中一个产品,比如 <font style="color:rgb(45, 132, 172);">音乐</font> ,就需要 F12 查看这个元素的特征。

但是 当我们的鼠标 从 <font style="color:rgb(45, 132, 172);">音乐</font> 移开, 这个 栏目就整个消失了, 就没法 查看 其对应的 HTML。

怎么办?

可以如下图所示:

在 开发者工具栏 console 里面执行如下js代码

setTimeout(function(){debugger}, 5000)

这句代码什么意思呢?

表示在 5000毫秒后,执行 debugger 命令

执行该命令会 浏览器会进入debug状态。 debug状态有个特性, 界面被冻住, 不管我们怎么点击界面都不会触发事件。

所以,我们可以在输入上面代码并回车 执行后, 立即 鼠标放在界面 右上角 更多产品处。

这时候,就会弹出 下面的 图标。

然后,我们仔细等待 5秒 到了以后, 界面就会因为执行了 debugger 命令而被冻住。

然后,我们就可以点击 开发者工具栏的 查看箭头, 再去 点击 <font style="color:rgb(45, 132, 172);">音乐</font> 图标 ,查看其属性了。

截屏

<font style="color:rgb(45, 132, 172);">整个网页</font> 截屏,使用 Page 对象的 screenshot 方法。

比如

# 截屏当前页面可见内容,保存到当前工作目录下的ss1.png文件中
page.screenshot(path='ss1.png')

# 截屏 完整页面,页面内容长度超过窗口高度时,包括不可见部分。
page.screenshot(path='ss1.png', full_page=True)

也可以只对 <font style="color:rgb(45, 132, 172);">某个元素的显示内容</font> 进行截屏,使用 Locator 对象的 screenshot 方法。

比如

page.locator('input[type=file]').screenshot(path='ss2.png')

拖拽

要实现拖拽功能,可以使用Page对象的 drag_and_drop 方法。

比如,对下面这段 html

<span id="t1">t1</span>
<span id="t2">t2</span>


<form >
  <div id="captcha">
  </div>
  <input type="text" placeholder="captcha" />
  <button type="submit">Submit</button>
</form>

要选中 <font style="color:rgb(45, 132, 172);">span#t1</font> 文本内容,并且拖拽到 输入框 <font style="color:rgb(45, 132, 172);">[placeholder="captcha"]</font> 里面去,可以使用如下代码:

# 选中  `span#t1`  文本内容
page.locator('#t1').select_text()

# 拖拽到 输入框  `[placeholder="captcha"]` 里面去
page.drag_and_drop('#t1', '[placeholder="captcha"]')

drag_and_drop 方法的:

第1个参数是被拖动元素的 css selector 或者 xpath selector , 如果可以匹配页面多个元素,取第一个匹配到的元素

第1个参数是拖动目标元素的 css selector 或者 xpath selector, 如果可以匹配页面多个元素,取第一个匹配到的元素

如果被拖动元素的Locator对象已经产生,可以直接调用其 drag_to 方法 进行拖动

上例中,代码也可以这样写

# 选中  `span#t1`  文本内容
lc = page.locator('#t1')

lc.select_text()

# 拖拽到 输入框  `[placeholder="captcha"]` 里面去
lc.drag_to(page.locator('[placeholder="captcha"]'))

注意, drag_to 的参数是 目标元素的 Locator 对象 , 而不是 selector 表达式字符串

弹出对话框

有的时候,我们经常会在操作界面的时候,出现一些弹出的对话框。

请点击打开这个网址

分别点击界面的3个按钮,你可以发现:

弹出的对话框有三种类型,分别是 <font style="color:rgb(45, 132, 172);">alert(警告信息)</font> <font style="color:rgb(45, 132, 172);">confirm(确认信息)</font> <font style="color:rgb(45, 132, 172);">prompt(提示输入)</font>

Alert

Alert 弹出框,目的就是显示通知信息,只需用户看完信息后,点击 OK(确定) 就可以了。

那么,自动化的时候,代码怎么模拟用户点击 OK 按钮呢?

可以这样

from playwright.sync_api import sync_playwright
pw = sync_playwright().start()
browser = pw.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://www.byhy.net/cdn2/files/selenium/test4.html")

# 处理 弹出对话框 的 回调函数
def handleDlg(dialog):
    # 等待1秒
    page.wait_for_timeout(1000)
    # 点击确定
    dialog.accept()
    # 打印 弹出框 提示信息
    print(dialog.message) 

# 设置弹出对话框事件回调函数
page.on("dialog", handleDlg )

# 点击 alert 按钮
page.locator('#b1').click()

处理函数中被回调时,会传入 Dialog 对象

这个对象的

<font style="color:rgb(45, 132, 172);">accept</font> 方法作用等同于点击确定按钮

<font style="color:rgb(45, 132, 172);">dismiss</font> 方法作用等同于点击取消按钮

<font style="color:rgb(45, 132, 172);">message</font> 属性就是对话框界面的提示信息字符串

注意:

  • 注册的处理函数中一定要调用 <font style="color:rgb(45, 132, 172);">accept</font> 或者 <font style="color:rgb(45, 132, 172);">dismiss</font> 方法,让对话框消失。

否则当对话框弹出时,后续任何代码都不会执行,并且会有超时错误。

比如

from playwright.sync_api import sync_playwright
pw = sync_playwright().start()
browser = pw.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://www.byhy.net/cdn2/files/selenium/test4.html")

# 处理 弹出对话框 的 回调函数
def handleDlg(dialog):
    print('不调用accept')

page.on("dialog", handleDlg )

#点击 alert 按钮
page.locator('#b1').click()

print('后续代码')

会发现,点击alert 按钮之后的后续代码不会执行

  • Playwright 在界面有弹出框时,发现如果没有任何注册的处理函数,会自动点击取消

比如

# 注释掉 事件回调函数
# page.on("dialog", handleDlg )

#点击 alert 按钮
page.locator('#b1').click()
print('点击1次')

page.wait_for_timeout(1000)

#点击 alert 按钮
page.locator('#b1').click()
print('点击2次')

会发现,对话框自动取消

Confirm

Confirm弹出框,主要是让用户确认是否要进行某个操作。

比如:当管理员在网站上选择删除某个账号时,就可能会弹出 Confirm弹出框, 要求确认是否确定要删除。

Confirm弹出框 有两个选择供用户选择,分别是 <font style="color:rgb(45, 132, 172);">OK</font> <font style="color:rgb(45, 132, 172);">Cancel</font> , 分别代表 <font style="color:rgb(45, 132, 172);">确定</font> <font style="color:rgb(45, 132, 172);">取消</font> 操作。

那么,自动化的时候,代码怎么模拟用户点击 <font style="color:rgb(45, 132, 172);">确定</font> 或者 <font style="color:rgb(45, 132, 172);">取消</font> 按钮呢?

前面说过,处理函数中被回调时,会传入 Dialog 对象

这个对象的

<font style="color:rgb(45, 132, 172);">accept</font> 方法作用等同于点击确定按钮

<font style="color:rgb(45, 132, 172);">dismiss</font> 方法作用等同于点击取消按钮

比如,前面的代码改为

from playwright.sync_api import sync_playwright
pw = sync_playwright().start()

browser = pw.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://www.byhy.net/cdn2/files/selenium/test4.html")

def handleDlg(dialog):    
    page.wait_for_timeout(1000)
    dialog.dismiss() # 点击取消

page.on("dialog", handleDlg)

# 点击 confirm 按钮
page.locator('#b2').click()


input('....')

会发现打印出来的是 <font style="color:rgb(45, 132, 172);">取消操作</font>

Prompt

出现 Prompt 弹出框 是需要用户输入一些信息,提交上去。

比如:当管理员在网站上选择给某个账号延期时,就可能会弹出 Prompt 弹出框, 要求输入延期多长时间。

怎么办呢?

Dialog 对象,这个对象的 <font style="color:rgb(45, 132, 172);">accept</font> 方法可以输入参数字符串,作为要输入的信息

比如,前面的代码改为

from playwright.sync_api import sync_playwright
pw = sync_playwright().start()
browser = pw.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://www.byhy.net/cdn2/files/selenium/test4.html")


def handleDlg(dialog):    
    page.wait_for_timeout(1000)
    dialog.accept('你好啊') # 输入信息,并确定

page.on("dialog", handleDlg)

# 点击 confirm 按钮
page.locator('#b3').click()

input('....')
posted @ 2026-06-22 11:31  Falamo寒枝  阅读(56)  评论(0)    收藏  举报