详解async 与 await,带您理解Playwright使用
大家在使用python做playwright自动化测试的过程中,一定会发现下面这种异步用法
async def func():
await api
await api
很多同学可能只是按照这种写法来编写项目的自动化测试代码,对于具体细节可能并不了解,今天我就来讲一下playwright异步用法的相关技术细节。建议大家拷贝文档中的脚本实际运行一下,学习的效果会更好!
同步和异步的概念
同步:发送一个请求,等待返回,然后再发送下一个请求
异步:发送一个请求,不等待返回,随时可以再发送下一个请求
async 与 await
python在3.5以后引入async和await来强化自身的异步编程,提升效率。async 是异步的简写,而 await 可以认为是 async wait 的简写。async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。异步函数的特点是能在函数执行过程中挂起,去执行其他异步函数,等到挂起条件结束后再回来继续执行。await的作用是挂起函数,等待函数操作完成,这时候回去执行其他的异步函数,而不是傻等,等挂起的执行完成以后将会从其他异步函数处返回,执行挂起结束的函数。await只可以对异步函数使用,普通函数使用会报错。await的本质是通过yield from 实现的,关于yield生成器相关知识点这里就不详细介绍了。
例如:两个异步程序async a、async b:
a中一步有await,当程序碰到关键字await后,异步程序a挂起,去执行异步b程序(就相当于从一个函数内部跳出去执行其他函数);当挂起条件结束时候,不管b是否执行完,要马上从b程序中跳出来,回到原程序a执行原来的操作;如果await后面跟的b函数不是异步函数,那么操作就只能等b执行完再返回,无法在b执行的过程中返回,这样就相当于直接调用b函数,没必要使用await关键字了。因此,需要await后面跟的是异步函数。
举个例子
import time
import asyncio
async def wait1():
print('wait1 start')
await asyncio.sleep(1)
print('wait1 end')
async def wait3():
print('wait3 start')
await asyncio.sleep(3)
print('wait3 end')
async def wait5():
print('wait5 start')
await asyncio.sleep(5)
print('wait5 end')
# 2. 将异步函数加入事件队列
tasks = [
wait1(),
wait3(),
wait5(),
]
if __name__ == '__main__':
# 创建一个事件循环
loop = asyncio.get_event_loop()
startTime = time.time()
# 执行队列实践,直到最晚的一个事件被处理完毕后结束
loop.run_until_complete(asyncio.wait(tasks))
# 如果不在使用loop,建议使用关闭,类似操作文件的close()函数
loop.close()
endTime = time.time()
print("sum time: ",endTime-startTime)
运行结果
wait5 start
wait3 start
wait1 start
wait1 end
wait3 end
wait5 end
sum time: 5.000609874725342
上面这段代码大家可以多执行几次,我们会发现:不管wait1 wait3,wait5 哪个函数先执行,但是最后end的顺序一定是 wait1>wait3>wait5。一共运行的时间 在5s左右,充分地证明了三个函数是并行执行的!
接下来,我们可以对代码进行如下修改:
async def wait3():
print('wait3 start')
time.sleep(3)
print('wait3 end')
然后再次运行代码,结果如下:
wait5 start
wait3 start
wait3 end
wait1 start
wait1 end
wait5 end
sum time: 5.002418518066406
大家会发现,只有wait3 end 发生后,才会出现wait1 end 和wait5 end(),很好的证明了上面的话:如果await后面跟的b函数不是异步函数,那么操作就只能等b执行完再返回,无法在b执行的过程中返回,这样就相当于直接调用b函数,没必要使用await关键字了。我们可以任意调整task的执行顺序,例如:
tasks = [
wait1(),
wait5(),
wait3(),
]
执行最慢的情况就是,wait3 第一个start,等待wait3 end后,才能执行wait1 或者wait5
wait3 start
wait3 end
wait5 start
wait1 start
wait1 end
wait5 end
sum time: 8.000799894332886
一个易犯的错误
当我们在同步方法中加入await,执行代码的时候会报错,也就是说像下面这样编写playwright脚步是不对的,因为sync_playwright() 是同步方法!
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(channel="chrome")
page = browser.new_page()
await page.goto("http://www.baidu.com")
print(page.title())
browser.close()
Playwright使用异步方法的正确姿势
如下代码会正常运行,通过await可以保证脚本的运行顺序
async def playwright_async_demo():
async with async_playwright() as p:
browser = await p.chromium.launch(channel="chrome")
page = await browser.new_page()
await page.goto("http://www.baidu.com")
asyncio.run(playwright_async_demo())
如果我们把上面代码中 browser = await p.chromium.launch(channel="chrome")
的await关键字去掉就会报错
page = await browser.new_page()
AttributeError: 'coroutine' object has no attribute 'new_page'
sys:1: RuntimeWarning: coroutine 'BrowserType.launch' was never awaited
原因就是代码行 browser = p.chromium.launch(channel="chrome")还没执行完就执行了下一行 page = await browser.new_page()
最后的总结,如果大家需要并行执行用例,那么需要考虑async (这里建议基于场景设计),如果没有这个需求,这部分只是点做为了解即可。
我的每一篇文章都希望帮助读者解决实际工作中遇到的问题!如果文章帮到了您,劳烦点赞、收藏、转发!您的鼓励是我不断更新文章最大的动力!