详解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 (这里建议基于场景设计),如果没有这个需求,这部分只是点做为了解即可。

我的每一篇文章都希望帮助读者解决实际工作中遇到的问题!如果文章帮到了您,劳烦点赞、收藏、转发!您的鼓励是我不断更新文章最大的动力!

 

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
青葱年少的头像青葱年少普通用户
上一篇 2023年9月8日
下一篇 2023年9月8日

相关推荐

此站出售,如需请站内私信或者邮箱!