【python自动化】playwright长截图切换标签页JS注入实战

前言

当前教程使用的playwright版本为1.37.0,selenium版本为3.141.0

官方文档:https://playwright.dev/python/docs/screenshots

本教程目录如下

文章目录

    • 前言
    • playwright各类截图源码阅读
      • ElementHandle类下的截图
      • Page类下的截图
      • Locator类下的截图
    • Playwright快速使用截图功能
    • 两大框架标签页切换对比
      • selenium切换标签页
      • playwright切换标签页
      • playwright切换标签页代码封装
    • JS注入
      • selenium使用js注入
      • playwright使用js注入
    • 综合实战
      • **实战需求**
      • **难点**
      • **解决方案**
      • 图像拼接封装
    • 实战完整代码

playwright各类截图源码阅读

通过阅读源码:playwright -> sync_api -> _generated.py可知,可以在以下三种方式下进行screenshots截图。

返回值均为字节bytes

class ElementHandle(JSHandle):(官方不建议或弃用)def screenshotclass Page(SyncContextManager):(官方推荐整个页面截图)def screenshotclass Locator(SyncBase):(官方推荐元素定位截图)def screenshot

ElementHandle类下的截图

官方建议不要用此方式,请使用Locator对象进行操作,后续可能会弃用此方式。

该方法会截取页面的屏幕截图,并根据该特定元素的大小和位置进行裁剪。如果该元素被其他元素覆盖,则在截图上实际上不可见。如果该元素是可滚动容器,则截图上只会显示当前滚动的内容。该方法在进行屏幕截图之前会等待 可操作性 检查,然后将元素滚动到视图中。如果该元素从 DOM 中分离,该方法将抛出错误。

用法

element_handle.screenshot()
element_handle.screenshot(**kwargs)

参数

参数类型含义
timeoutUnion[float, None]最大等待时间,以毫秒为单位。默认为30000(30秒)。传入0以禁用超时。browser_context.set_default_timeout()page.set_default_timeout()方法更改默认值。
typeUnion[“jpeg”, “png”, None]指定截图的类型,默认为png。
pathUnion[pathlib.Path, str, None]图像保存的文件路径。截图类型将根据文件扩展名进行推断。如果path是相对路径,则相对于当前工作目录解析。如果不提供路径,则图像将不会保存到磁盘。
qualityUnion[int, None]图像的质量,介于0到100之间。不适用于png图像。
omit_backgroundUnion[bool, None]隐藏默认的白色背景,允许使用透明度进行截图。不适用于jpeg图像。默认为false
animationsUnion[“allow”, “disabled”, None]设置为"disabled"时,停止CSS动画、CSS过渡和Web动画。动画的处理方式取决于其持续时间:有限动画将快进到完成状态,因此它们会触发transitionend事件。 无限动画将取消到初始状态,然后在截图后重新播放。默认为"allow",即保持动画不变。
caretUnion[“hide”, “initial”, None]设置为"hide"时,截图将隐藏文本插入符。设置为"initial"时,文本插入符的行为不会改变。默认为"hide"
scaleUnion[“css”, “device”, None]设置为"css"时,截图上每个CSS像素将具有一个实际像素。对于高DPI设备,这将使截图保持较小的大小。使用"device"选项将使每个设备像素有一个实际像素,因此高DPI设备的截图将是两倍或更大。默认为"device"
maskUnion[List[Locator], None]指定在截图时应隐藏的定位符。被隐藏的元素将被叠加一个粉色框#FF00FF(由maskColor自定义),完全覆盖其边界框。
mask_colorUnion[str, None]指定被隐藏元素的覆盖框的颜色,以CSS颜色格式表示。默认颜色为粉色#FF00FF

Page类下的截图

用法

page.screenshot()
page.screenshot(**kwargs)

参数

参数类型含义
timeoutUnion[float, None]最大等待时间,以毫秒为单位。默认为30000(30秒)。传入0以禁用超时。browser_context.set_default_timeout()page.set_default_timeout()方法更改默认值。
typeUnion[“jpeg”, “png”, None]指定截图的类型,默认为png。
pathUnion[pathlib.Path, str, None]图像保存的文件路径。截图类型将根据文件扩展名进行推断。如果path是相对路径,则相对于当前工作目录解析。如果不提供路径,则图像将不会保存到磁盘。
qualityUnion[int, None]图像的质量,介于0到100之间。不适用于png图像。
omit_backgroundUnion[bool, None]隐藏默认的白色背景,允许使用透明度进行截图。不适用于jpeg图像。默认为false
full_pageUnion[bool, None]true时,截取完整可滚动页面的屏幕截图,而不是当前可见的视口。默认为false
clipUnion[{x: float, y: float, width: float, height: float}, None]指定结果图像的裁剪区域的对象。
animationsUnion[“allow”, “disabled”, None]设置为"disabled"时,停止CSS动画、CSS过渡和Web动画。动画的处理方式取决于其持续时间:有限动画将快进到完成状态,因此它们会触发transitionend事件。 无限动画将取消到初始状态,然后在截图后重新播放。默认为"allow",即保持动画不变。
caretUnion[“hide”, “initial”, None]设置为"hide"时,截图将隐藏文本插入符。设置为"initial"时,文本插入符的行为不会改变。默认为"hide"
scaleUnion[“css”, “device”, None]设置为"css"时,截图上每个CSS像素将具有一个实际像素。对于高DPI设备,这将使截图保持较小的大小。使用"device"选项将使每个设备像素有一个实际像素,因此高DPI设备的截图将是两倍或更大。默认为"device"
maskUnion[List[Locator], None]指定在截图时应隐藏的定位符。被隐藏的元素将被叠加一个粉色框#FF00FF(由maskColor自定义),完全覆盖其边界框。
mask_colorUnion[str, None]指定被隐藏元素的覆盖框的颜色,以CSS颜色格式表示。默认颜色为粉色#FF00FF

Locator类下的截图

该方法将截取页面的屏幕截图,并根据定位符匹配的特定元素的大小和位置进行裁剪。如果该元素被其他元素覆盖,则在截图上实际上不可见。如果该元素是可滚动容器,则截图上只会显示当前滚动的内容。

该方法会等待可操作性检查,然后将元素滚动到视图中,然后再进行截图。如果该元素已从 DOM 中移除,则该方法会抛出一个错误。

用法

page.get_by_role("link").screenshot()
page.locator("#main-content").screenshot()

参数

参数类型含义
timeoutUnion[float, None]最大等待时间,以毫秒为单位。默认为30000(30秒)。传入0以禁用超时。browser_context.set_default_timeout()page.set_default_timeout()方法更改默认值。
typeUnion[“jpeg”, “png”, None]指定截图的类型,默认为png。
pathUnion[pathlib.Path, str, None]图像保存的文件路径。截图类型将根据文件扩展名进行推断。如果path是相对路径,则相对于当前工作目录解析。如果不提供路径,则图像将不会保存到磁盘。
qualityUnion[int, None]图像的质量,介于0到100之间。不适用于png图像。
omit_backgroundUnion[bool, None]隐藏默认的白色背景,允许使用透明度进行截图。不适用于jpeg图像。默认为false
animationsUnion[“allow”, “disabled”, None]设置为"disabled"时,停止CSS动画、CSS过渡和Web动画。动画的处理方式取决于其持续时间:有限动画将快进到完成状态,因此它们会触发transitionend事件。 无限动画将取消到初始状态,然后在截图后重新播放。默认为"allow",即保持动画不变。
caretUnion[“hide”, “initial”, None]设置为"hide"时,截图将隐藏文本插入符。设置为"initial"时,文本插入符的行为不会改变。默认为"hide"
scaleUnion[“css”, “device”, None]设置为"css"时,截图上每个CSS像素将具有一个实际像素。对于高DPI设备,这将使截图保持较小的大小。使用"device"选项将使每个设备像素有一个实际像素,因此高DPI设备的截图将是两倍或更大。默认为"device"
maskUnion[List[Locator], None]指定在截图时应隐藏的定位符。被隐藏的元素将被叠加一个粉色框#FF00FF(由maskColor自定义),完全覆盖其边界框。
mask_colorUnion[str, None]指定被隐藏元素的覆盖框的颜色,以CSS颜色格式表示。默认颜色为粉色#FF00FF

注意点

locator类下的截图方法,是比page类下的截图方法少了两个可选参数。

full_page:对于元素截图不支持全页面滚动长截图。

clip:对于元素截图不支持裁剪。

Playwright快速使用截图功能

当前页面截图

page.screenshot(path="screenshot.png")

当前页面长截图

page.screenshot(path="screenshot.png", full_page=True)

将截图转为字节存储在缓存中

screenshot_bytes = page.screenshot()
print(base64.b64encode(screenshot_bytes).decode())

根据元素截图

page.locator(".header").screenshot(path="screenshot.png")

两大框架标签页切换对比

在Web UI测试中,我们点击某个带有超链接的元素,可能会在新的标签页打开。

实际上有时候浏览器还是停留在当前页面,并没有自己切到新页面,这时候就需要切换到新的标签页进行元素定位等相关操作。

selenium切换标签页

在selenium是通过handles句柄的方式进行切换。每个页面都有唯一的句柄,最新的页面可通过下标[-1]获取。

driver.switch_to.window(driver.window_handles[-1])

playwright切换标签页

在playwright的page类下有个将页面置于最前面(激活选项卡)方法,可以将目标标签页激活,并且在目标标签页进行元素定位等相关操作。

官方文档:https://playwright.dev/python/docs/api/class-page#page-bring-to-front

# 用法如下
Page.bring_to_front()

如何激活我们所需要激活的页面?

1、通过url

page.url

2、通过title

page.title

playwright切换标签页代码封装

个人感觉切换标签页selenium更方便一点,playwright需要我们自己封装一下。我封装的代码如下:

def switch_to_page(context: BrowserContext, url: str = None, title: str = None) -> Page:"""切换到指定url:param context:传入一个浏览器上下文:param title: 当前标签页的标题:param url: 当前标签页的url:return: label_page:Page对象 返回对应的标签页,如果没找到则返回最新的标签页"""for label_page in context.pages:if url:if url in label_page.url:label_page.bring_to_front()return label_pageelif title:if title in label_page.title():label_page.bring_to_front()return label_pageelse:if title:print(f"没有找到【{title}】标题的标签页")if url:print(f"没有找到【{url}】网址的标签页")return context.pages[-1]

代码注释都写的很清楚了,这里就不单独做解析了。

JS注入

在一些特殊的情况下,我们需要执行原生js,从而达到我们一些框架无法完成的操作。

selenium使用js注入

使用execute_script方法

def execute_script(self, script, *args):"""在当前窗口/框架中同步执行JavaScript。:参数:- script: 可执行的JavaScript脚本.- \*args: 任何适用的JavaScript参数.:使用方法:driver.execute_script('return document.title;')"""

实战示列

我要通过js在浏览器创建一个新标签并打开我博客首页。

from selenium import webdriver
driver = webdriver.Chrome()
driver.execute_script(f'window.open("https://blog.csdn.net/qq_46158060", "_blank");')

playwright使用js注入

官方文档:https://playwright.dev/python/docs/api/class-page#page-evaluate

使用evaluate方法或evaluate_handle方法。

page.evaluate() 和 page.evaluate_handle() 之间的唯一区别是 page.evaluate_handle() 返回 JSHandle。

def evaluate(self, expression: str, arg: typing.Optional[typing.Any] = None) -> typing.Any:

传入的是一个表达式,返回一个序列化值。

官方有挺多个示列。

```py
result = await page.evaluate(\"([x, y]) => Promise.resolve(x * y)\", [7, 8])
print(result) # prints \"56\"
``````py
result = page.evaluate(\"([x, y]) => Promise.resolve(x * y)\", [7, 8])
print(result) # prints \"56\"
```A string can also be passed in instead of a function:```py
print(await page.evaluate(\"1 + 2\")) # prints \"3\"
x = 10
print(await page.evaluate(f\"1 + {x}\")) # prints \"11\"
``````py
print(page.evaluate(\"1 + 2\")) # prints \"3\"
x = 10
print(page.evaluate(f\"1 + {x}\")) # prints \"11\"
```

实战示列(1)

我要通过js在浏览器创建一个新标签并打开我博客首页。

page.evaluate(f'window.open("https://blog.csdn.net/qq_46158060", "_blank");')

实战示列(2)

通过js定位一个id为main-content的元素,并且滚动该元素。

js = 'document.getElementById("main-content").scrollTo(600,800)'
page.evaluate(js)
# 或
# page.evaluate_handle(js)

综合实战

需要操作的页面如下

在这里插入图片描述

实战需求

1、使用playwright连接本地指定端口浏览器

2、通过浏览器在新标签页打开指定项目ID下的相关页面

3、页面分为左右两栏,可以分别滚动,需要滚动右侧栏,并且进行长截图

4、要求一个页面只能有一张图

难点

1、playwright如何连接本地指定端口浏览器进行操作

2、前文中提到,页面截图,默认是第一个滚动条(暂未找到切换滚动条方案),这里需要定位右侧栏,也就是第二个滚动条

3、执行playwright按键操作进行滚动,默认是第一个滚动条,需要结合多种定位和键位,操作复杂

4、前文中提到,如果通过定位右侧栏大框元素进行截图,只能固定截图,无法长截图

解决方案

1、playwright连接本地浏览器详细教程参考我之前写过的文章:playwright连接已有浏览器操作

2、使用js定位右侧框的元素

3、使用js定位右侧框的元素进而执行滚动操作

4、通过js滚动进行多次截图。每滚动一次截图一次,至于滚动的范围需要自己先进行调试,最后将多张图进行拼接成一张图片。

图片拼接技术参考之前文章:web自动化之selenium的特殊用法汇总篇 , 这篇文章的特殊网页无法长截图,使用多图拼接技术章节。

图像拼接封装

下载三方库

pip install pillow==8.4.0

封装代码

from PIL import Image
# 拼接图片的代码
def stitch_images(imgPath_1: str, imgPath_2: str, newImgPath: str) -> str:""":param imgPath_1: 第一张图片的路径,:param imgPath_2: 第二张图片的路径,:param newImgPath: 拼接图片的路径,:return: newImgPath"""# 获取JPG、png图像的路径im_list_1 = [imgPath_1, imgPath_2]im_list = [Image.open(fn) for fn in im_list_1]# 图片转化为相同的尺寸ims = []for i in im_list:new_img = i.resize((1920, 961), Image.BILINEAR)ims.append(new_img)# 单幅图像尺寸width, height = ims[0].size# 创建空白长图result = Image.new(ims[0].mode, (width, height * len(ims)))# 拼接图片for i, im in enumerate(ims):result.paste(im, box=(0, i * height))# 保存图片result.save(newImgPath)return newImgPath

实战完整代码

步骤:

1、使用playwright连接本地浏览器(含用户数据,免登陆,懒加载)

2、使用js在新标签页打开相关网址

3、切换至指定标签页

4、定位右侧栏,结合js滚动进行多图截取

5、使用PIL库进行多图拼接

注:本教程为示列代码,业务代码为方便阅读未进行封装,相关代码都进行了注释。

# -*- coding: utf-8 -*-
"""
@Time : 2023/6/25 14:50
@Email : Lvan826199@163.com
@公众号 : 梦无矶的测试开发之路
@File : PW_001_截图.py
"""
__author__ = "梦无矶小仔"import timefrom playwright.sync_api import sync_playwright, BrowserContext, Page
from PIL import Imagedef switch_to_page(context: BrowserContext, url: str = None, title: str = None) -> Page:"""切换到指定url:param context:传入一个浏览器上下文:param title: 当前标签页的标题:param url: 当前标签页的url:return: label_page:Page对象 返回对应的标签页,如果没找到则返回最新的标签页"""for label_page in context.pages:if url:if url in label_page.url:label_page.bring_to_front()return label_pageelif title:if title in label_page.title():label_page.bring_to_front()return label_pageelse:if title:print(f"没有找到【{title}】标题的标签页")if url:print(f"没有找到【{url}】网址的标签页")return context.pages[-1]# 拼接图片的代码
def images_stitch(imgPath_1: str, imgPath_2: str, newImgPath: str) -> str:""":param imgPath_1: 第一张图片的路径,:param imgPath_2: 第二张图片的路径,:param newImgPath: 拼接图片的路径,:return: newImgPath"""# 获取JPG、png图像的路径im_list_1 = [imgPath_1, imgPath_2]im_list = [Image.open(fn) for fn in im_list_1]# 图片转化为相同的尺寸ims = []for i in im_list:new_img = i.resize((1920, 961), Image.BILINEAR)ims.append(new_img)# 单幅图像尺寸width, height = ims[0].size# 创建空白长图result = Image.new(ims[0].mode, (width, height * len(ims)))# 拼接图片for i, im in enumerate(ims):result.paste(im, box=(0, i * height))# 保存图片result.save(newImgPath)return newImgPath# 主代码playwright = sync_playwright().start()
# 连接已经打开的浏览器,找好端口
browser = playwright.chromium.connect_over_cdp("http://127.0.0.1:9223")
default_context = browser.contexts[0]
print(type(default_context))
page = default_context.pages[0]projectID = 6691166666666666666
gameID = 497566666666666666
timeRange = 7# 官方教程 https://playwright.dev/python/docs/api/class-page#page-evaluateurl123 = f'https://play.google.com/console/u/0/developers/{projectID}/app/{gameID}/vitals/metrics/overview?days={timeRange}'page.evaluate(f'window.open("https://play.google.com/console/u/0/developers/{projectID}/app/{gameID}/vitals/metrics/overview?days={timeRange}", "_blank");'
)  # 执行js代码page.wait_for_timeout(5000)page = switch_to_page(default_context, url123)### 进行截图
# 官方教程 https://playwright.dev/python/docs/screenshots#full-page-screenshots
now_time = time.time_ns()
page.wait_for_timeout(1000)
page.locator("#main-content").click()
page.wait_for_timeout(1000)image_path_1 = f'./img-{now_time}.png'
page.locator("#main-content").screenshot(path=f'img-{now_time}.png')
print("截图成功001")page.wait_for_timeout(1000)
js = 'document.getElementById("main-content").scrollTo(600,800)'
# page.evaluate_handle(js)
page.evaluate(js)
print("执行了js滚动")
page.wait_for_timeout(1000)
now_time = time.time_ns()
image_path_2 = f'./img-{now_time}.png'
page.locator("#main-content").screenshot(path=f'img-{now_time}.png')
print("截图成功002")# 截图拼接
new_image_path = './image_result.png'
result_img_path = images_stitch(image_path_1,image_path_2,new_image_path)
print(f"完整长截图路径:{result_img_path}")

最终长截图效果展示

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/103960.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据孤岛的突破口在哪里?

国务院于2021年12月发布的《“十四五”数字经济发展规划》中提到,我国数字经济发展中数字鸿沟问题未得到有效解决,各行业应充分发挥数据要素作用,加强数据治理和监管工作。“数据孤岛”问题虽早已被提出,但至今仍然存在&#xff0…

谷粒商城----缓存与分布式锁

1、缓存使用 为了系统性能的提升,我们一般都会将部分数据放入缓存中,加速访问。而 db 承担数据落盘工作。 哪些数据适合放入缓存?  即时性、数据一致性要求不高的  访问量大且更新频率不高的数据(读多,写少&…

uniapp制作——交友盲盒

在小程序端可以有很多好玩的小玩意,目前网上比较有趣的就是有一个交友盲盒,能抽出和找出对象的一个有趣的小程序,所以今天给大家带来用uniapp搭建的交友盲盒,大家再根据自己的情况去搭建自己的后端和数据库来完成自己的一个小项目…

揭秘分布式文件系统大规模元数据管理机制——以Alluxio文件系统为例

作者简介: 辭七七,目前大,正在学习C/C,Java,Python等 作者主页: 七七的个人主页 文章收录专栏: 七七的闲谈 欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖&#x1f49…

方差分析的核心概念“方差分解“

方差是统计学中用来衡量数据集合中数值分散或离散程度的一种统计量。它表示了数据点与数据集合均值之间的差异程度,即数据的分散程度。方差越大,表示数据点更分散,而方差越小,表示数据点更集中。 方差的计算公式如下:…

【数据分析入门】【淘宝电商API接入与电商数据分析】初识Web API(一)

今天开始我们将学习如何使用Web应用变成借口(API)自动请求网站到特定信息而不是整个网站,再对这些信息进行可视化。由于这样编写到程序始终使用最新到数据来生成可视化,因此即便数据瞬息万变,它呈现到信息也都是最新的。比如,我们…

【监控系统】Promethus整合Alertmanager监控告警邮件通知

【监控系统】Promethus整合Alertmanager监控告警邮件通知 Alertmanager是一种开源软件,用于管理和报警监视警报。它与Prometheus紧密集成,后者是一种流行的开源监视和警报系统。Alertmanager从多个源接收警报和通知,并根据一组配置规则来决定…

Docker网络功能

基本网络功能 Docker 允许通过外部访问容器或容器互联的方式来提供网络服务。使用docker network子命令来管理Docker网络。 外部访问容器可通过端口映射实现,启动容器时使用-p参数指定映射关系。-p可多次使用来绑定多个端口。使用docker port命令查看当前映射的端…

vue3:16、Pinia的基本语法

选项式APi 组合式API src/store/counter.js import { defineStore } from "pinia"; import { computed, ref } from "vue";export const userCounterStore defineStore("counter",()>{//声明数据 state - countconst count ref(100)//声…

《TCP/IP网络编程》阅读笔记--基于TCP的服务器端/客户端

目录 1--TCP/IP协议栈 2--TCP服务器端默认函数调用顺序 3--TCP客户端的默认函数调用顺序 4--Linux实现迭代回声服务器端/客户端 5--Windows实现迭代回声服务器端/客户端 6--TCP原理 7--Windows实现计算器服务器端/客户端 1--TCP/IP协议栈 TCP/IP协议栈共分 4 层&#xf…

CUDA说明和安装[window]

文章目录 1、查看版本信息查看GPU查看cuda版本其他方法 2区分 了解cudaCUDA ToolkitNVCCcuDNN 3/ 安装过程4/版本的问题CUDA Toolkit和 显卡驱动 的版本对应CUDA / CUDA Toolkit和cuDNN的版本对应 5/关于CUDA和Cudnn**5.1 CUDA的命名规则****5.2 如何查看自己所安装的CUDA的版本…

对可再生能源和微电网集成研究的新控制技术和保护算法进行基线和测试及静态、时域和频率分析研究(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…