Selenium异步浏览器操作实现原理深度解析
一、AsyncBrowser类核心结构
1.1 类定义与启动方法
python
class AsyncBrowser(Command):
@classmethod
async def start(cls, remote_driver_server: str,
capabilities: dict,
http_session: ClientSession,
reconnect_server_session: [None, str] = None):
# 初始化实例
self = cls()
# 存储参数
self._http_session = http_session
self._remote_driver_server = remote_driver_server
self._desired_capabilities = capabilities
# 创建新会话或重用现有会话
if reconnect_server_session is None:
# 创建新会话
async with self._http_session.post(...) as resp:
r = await resp.json()
self._browser_session_id = r['value'].get('sessionId')
else:
# 重用现有会话
self._browser_session_id = reconnect_server_session
# 构建会话URL
self.url = f'{self._remote_driver_server}/session/{self._browser_session_id}'
return self
启动参数说明
参数 | 类型 | 作用描述 |
---|---|---|
remote_driver_server | str | WebDriver服务器地址 |
capabilities | dict | 浏览器能力配置 |
http_session | ClientSession | aiohttp会话对象 |
reconnect_server_session | str/None | 要重用的现有会话ID |
二、核心功能实现
2.1 浏览器基础操作
python
async def get(self, url: str):
body = {'url': url}
return await self.command('POST', endpoint='/url', json=body)
async def get_title(self):
return await self.command('GET', endpoint='/title')
async def current_url(self):
return await self.command('GET', endpoint='/url')
async def screenshot(self):
return await self.command('GET', endpoint='/screenshot')
async def quit(self):
return await self.command('DELETE', endpoint='')
2.2 元素定位与封装
python
async def find_element(self, strategy: str, value: str, endpoint: str = '/element'):
# 转换传统定位器为CSS选择器
if strategy == 'id':
strategy = 'css selector'
value = f'[id="{value}"]'
elif strategy == 'name':
strategy = 'css selector'
value = f'[name="{value}"]'
elif strategy == 'class':
strategy = 'css selector'
value = f'.{value}'
# 发送定位请求
body = {'using': strategy, 'value': value}
element_data = await self.command('POST', endpoint=endpoint, json=body)
# 返回Element对象
return Element(element_data, self._url, self._session)
2.3 元素操作快捷方法
python
async def click(self, *location):
element = await self.find_element(*location)
return await element.click()
async def clear(self, *location):
element = await self.find_element(*location)
return await element.clear()
async def send_keys(self, *location, text: str):
element = await self.find_element(*location)
return await element.send_keys(text)
async def text(self, *location):
element = await self.find_element(*location)
return await element.text()
三、异步上下文管理器
python
async def __aenter__(self):
return self
async def __aexit__(self, *args):
await self.quit()
- 自动资源管理 :使用
async with
语法自动关闭浏览器 - 异常安全:确保任何情况下都会执行退出操作
- 简化代码:避免忘记手动调用quit()
四、实战使用示例
4.1 基础使用流程
python
import asyncio
from aiohttp import ClientSession
async def main():
async with ClientSession() as session:
# 启动浏览器
browser = await AsyncBrowser.start(
remote_driver_server='http://localhost:9515',
capabilities={
'browserName': 'chrome',
'goog:chromeOptions': {'args': ['--headless']}
},
http_session=session
)
# 访问网页
await browser.get('https://example.com')
# 获取页面标题
title = await browser.get_title()
print(f'页面标题: {title}')
# 定位搜索框并输入
await browser.send_keys('name', 'q', text='异步测试')
# 定位按钮并点击
await browser.click('css selector', 'input[type="submit"]')
# 获取当前URL
current_url = await browser.current_url()
print(f'当前URL: {current_url}')
# 自动关闭浏览器(通过上下文管理器)
# 运行示例
asyncio.run(main())
4.2 执行输出结果
text
页面标题: Example Domain
当前URL: https://example.com/search?q=异步测试
五、设计原理分析
5.1 定位策略转换逻辑
原始策略 | 转换后策略 | 转换结果示例 |
---|---|---|
id | css selector | [id="search"] |
name | css selector | [name="username"] |
class | css selector | .submit-btn |
其他策略 | 保持不变 | xpath, tag name等 |
5.2 会话管理机制
-
新会话创建 :
httpPOST /session { "capabilities": {...} }
-
会话重用:直接使用现有sessionId
-
会话销毁 :
httpDELETE /session/:sessionId
5.3 协议端点映射
操作 | HTTP方法 | 端点路径 | 参数 |
---|---|---|---|
导航 | POST | /session/:id/url | {"url": "..."} |
获取标题 | GET | /session/:id/title | 无 |
获取当前URL | GET | /session/:id/url | 无 |
截图 | GET | /session/:id/screenshot | 无 |
六、技术优势总结
-
完全异步设计:
- 基于aiohttp实现非阻塞IO
- 支持高并发浏览器操作
-
资源自动管理:
- 上下文管理器确保资源释放
- 避免浏览器进程泄漏
-
定位策略优化:
- 自动转换传统定位器为CSS选择器
- 提高元素定位效率
-
协议标准化:
- 严格遵循W3C WebDriver协议
- 兼容所有标准浏览器实现
-
操作链式封装:
- 提供元素操作快捷方法
- 简化常见操作流程
这种设计模式展示了如何基于WebDriver协议构建现代异步浏览器自动化框架,为高效、可靠的自动化测试提供了坚实基础。
七、完整代码
python
from selenium.webdriver.common.devtools.v133.runtime import await_promise
from chap9.async_http_client import Command
from aiohttp import ClientSession
from chap9.async_element import Element
class AsyncBrowser(Command):
@classmethod
async def start(cls, remote_driver_server: str,
capabilities: dict,
http_session: ClientSession,
reconnect_server_session: [None, str] = None):
self = cls()
self._http_session = http_session
self._remote_driver_server = remote_driver_server
self._desired_capabilities = capabilities
if reconnect_server_session is None:
async with self._http_session.post(f'{self._remote_driver_server}/session',
json=self._desired_capabilities) as resp:
r = await resp.json()
self._browser_session_id = r['value'].get('sessionId', None) or r.get('sessionId', None)
else:
self._browser_session_id = reconnect_server_session
self.url = f'{self._remote_driver_server}/session/{self._browser_session_id}'
return self
@property
def _session_id(self):
return getattr(self, '_browser_session_id')
@property
def _url(self):
return getattr(self, 'url')
@property
def _session(self):
return getattr(self, '_http_session')
async def command(self, method: str, endpoint: str, **kwargs):
await super(AsyncBrowser, self).command(method, self._url + endpoint, self._session, **kwargs)
async def get(self, url: str):
body = {
'url': url
}
return await self.command('POST', endpoint='/url', json=body)
async def find_element(self, strategy: str, value: str, endpoint: str = '/element'):
if strategy == 'id':
strategy = 'css selector'
value = '[id="%s"]' % value
elif strategy == 'name':
strategy = 'css selector'
value = '[name="%s"]' % value
elif strategy == 'class':
strategy = 'css selector'
value = '.%s' % value
body = {
'using': strategy,
'value': value
}
element = await self.command('POST', endpoint=endpoint, json=body)
return Element(element, self._url, self._session)
async def click(self, *location):
element = await self.find_element(*location)
return await element.click()
async def clear(self, *location):
element = await self.find_element(*location)
return await element.clear()
async def send_keys(self, *location, text: str):
element = await self.find_element(*location)
return await element.send_keys(text)
async def text(self, *location):
element = await self.find_element(*location)
return await element.text()
async def get_title(self):
return await self.command('GET', endpoint='/title')
async def current_url(self):
return await self.command('GET', endpoint='url')
async def screenshot(self):
return await self.command('GET', endpoint='/screenshot')
async def quit(self):
return await self.command('DELETE', endpoint='')
async def __aenter__(self):
return self
async def __aexit__(self, *args):
await self.quit()
「小贴士」 :点击头像→【关注】按钮,获取更多软件测试的晋升认知不迷路! 🚀