断言
expect(page).to_have_title(re.compile("Playwright"))
断言标题是否有Playwright字段。
get_by_role():
button = page.get_by_role("button", name="Submit")
page.get_by_role("link", name="Get started"):查找链接,用于筛选具有名称为 "Get started" 的链接元素
"button"
: 查找按钮元素。"checkbox"
: 查找复选框元素。"radio"
: 查找单选框元素。"textbox"
: 查找文本框元素。"combobox"
: 查找下拉框(组合框)元素。"listbox"
: 查找列表框元素。"link"
: 查找链接元素。"heading"
: 查找标题(例如<h1>
,<h2>
等)元素。"menu"
: 查找菜单元素。"menuitem"
: 查找菜单项元素。"progressbar"
: 查找进度条元素。"radio"
: 查找单选按钮元素。"tab"
: 查找选项卡元素。
expect(page).to_have_url(re.compile(".*intro")):
断言页面的url中具有intro字段
断言自定义等待时间:
全局等待:
expect.set_options(timeout=10_000)
局部等待:expect(page.get_by_text("Name")).to_be_visible(timeout=10_000)
其他的一些库
@pytest.mark.skip_browser("firefox")
def test_visit_example(page):
page.goto("https://example.com")
不用这个浏览器
用特定的浏览器:
用命令行,运行特定的浏览器:
pytest --browser-channel chrome
设置基本的路径:
命令行:pytest --base-url http://localhost:8080
def test_visit_example(page):
page.goto("/admin")
# -> Will result in http://localhost:8080/admin
更改浏览器的默认参数:
@pytest.fixture(scope="session")
def browser_context_args(browser_context_args):
return{
**browser_context_args,
"ignore_https_errors": True",
"viewport": {
"width": 1920,
"height": 1080,
}
}
配置手机型号;
@pytest.fixture(scope="session")
def browser_context_args(browser_context_args, playwright):
iphone_11 = playwright.devices['iPhone 11 Pro']
return {
**browser_context_args,
**iphone_11,
}
或者用命令行:pytest --device="iphone 13"
page.wait_for_timeout(5000)来替代time.sleep(5),playwright自带的等待时间可以用于debugger
pdb-python debugger
-
continue
(c):继续执行代码,直到下一个断点或程序结束。 示例:c
或continue
-
next
(n):执行下一行代码,不进入函数内部。 示例:n
或next
-
step
(s):执行下一行代码,如果是函数调用则进入函数内部。 示例:s
或step
-
quit
(q):退出pdb交互式环境并终止程序。 示例:q
或quit
-
print
(p):打印变量的值。 示例:p variable_name
-
list
(l):显示当前代码上下文的代码片段。 示例:l
或list
-
break
(b):设置断点。 示例:b line_number
或b function_name
-
up
(u):在调用栈中向上移动一层,进入上一层函数的上下文。 示例:u
或up
-
down
(d):在调用栈中向下移动一层,返回到当前函数的上下文。 示例:d
或down
-
help
(h):获取帮助信息。 示例:h
或help
Deploy to CI
CI:continuous integration
在Playwright中,部署到持续集成环境可能需要配置自动化脚本或流程,以便在每次代码提交时自动运行测试并生成构建。这可以通过使用适当的CI工具(如Jenkins、Travis CI、GitHub Actions等)来实现。
actions
locator.fill()用于input填入内容
# Text input
page.get_by_role("textbox").fill("Peter")
check:
复、单选框选中:locator.check()
判断是否选中 : assert locator.is_checked() is True
# Check the checkbox
page.get_by_label('I agree to the terms above').check()
# Assert the checked state
assert page.get_by_label('Subscribe to newsletter').is_checked() is True
# Select the radio button
page.get_by_label('XL').check()
选中复选框
locator.select_option("xxx")
多个:locator.select_option(["1","2"])
点击操作:
click单击 dblclick双击 click(button="right")右击 hove悬浮
type:模拟用户输入文字
locator.type("hello")
按键操作:press
locator.press("Enter")
"Control+ArrowRight" ("$")
upload files-上传文件
locator.set_input_files("xxx.txt")
多个:locator.set_input_files(["1","2"])
聚焦:focus
locator.focus()
拖拽内容
drag:locator.drag_to(locator)
模拟真实:
locator.hover() locator.mouse.down() locator2.hover locator2.mouse.up
auto-waiting
attached,visible,editable,stable,receive events,enable
Isolation
Tests written with Playwright execute in isolated clean-slate environments called browser contexts. This isolation model improves reproducibility and prevents cascading test failures.
使用Playwright编写的测试在称为浏览器上下文的隔离干净环境中执行。这种隔离模型提高了可重现性并防止级联测试失败
好处:
-
没有失败传递:如果一个测试失败,不会影响其他测试。每个测试用例都在独立的浏览器上下文中运行,因此一个测试的失败不会影响到其他测试用例,确保了隔离性。
-
容易调试错误或不稳定性:你可以随意运行单个测试用例多次,从而更容易调试出现的错误或不稳定性。这有助于定位问题并验证修复效果。
-
不需要考虑顺序问题:在并行运行、分片等情况下,你不必担心测试用例之间的顺序。每个测试用例都在独立的浏览器上下文中运行,因此不会出现顺序相关的问题。
每执行一次用例,都可以执行所设置的上下文环境:
browser = playwright.chromium.launch()
context = browser.new_context()
page = context.new_page()
还可设置多个上下文环境:
# create a chromium browser instance
chromium = playwright.chromium
browser = chromium.launch()
# create two isolated browser contexts
user_context = browser.new_context()
admin_context = browser.new_context()
setup\teardown
用关键字标识、yield之前的是执行测试用例之前执行。yield之后是执行完所有测试用例之后执行的。
例子:
@pytest.fixture(scope="session") def api_request_context( playwright: Playwright, ) -> Generator[APIRequestContext, None, None]: headers = { # We set this header per GitHub guidelines. "Accept": "application/vnd.github.v3+json", # Add authorization token to all requests. # Assuming personal access token available in the environment. "Authorization": f"token {GITHUB_API_TOKEN}", } request_context = playwright.request.new_context( base_url="https://api.github.com", extra_http_headers=headers ) yield request_context request_context.dispose()
这段代码是一个使用 pytest 框架的测试用例的装置(fixture)的定义,名为 `api_request_context`。它的作用范围被设置为 `"session"`,这意味着整个测试会话只会创建一次。
这个装置接受一个参数 `playwright`,它的类型是 `Playwright`。这个参数是用来与浏览器进行交互的对象。
函数的返回类型被标注为 `Generator[APIRequestContext, None, None]`,表明这个装置是一个生成器,会生成一系列的值作为迭代器。生成的值的类型为 `APIRequestContext`,迭代的结束标志为 `None`。
在装置的主体内,首先定义了一个名为 `headers` 的字典,其中设置了请求头信息,包括接受的数据格式和授权令牌。接下来,使用 `playwright.request.new_context()` 创建了一个新的请求上下文 `request_context`,其中设置了基础 URL 和额外的 HTTP 头部信息。
之后使用 `yield` 语句生成了 `request_context`,这表示在测试运行期间会提供这个请求上下文供测试用例使用。最后,在测试会话结束时(即所有测试用例执行完成后),使用 `dispose()` 方法销毁了请求上下文。
总之,这个装置的作用是为每个测试用例提供一个带有特定请求头信息的请求上下文,以便与 GitHub API 进行交互。这可能用于模拟特定的请求条件,以及在测试用例运行结束后清理资源。
@pytest.fixture(scope="session", autouse=True)
def create_test_repository(
api_request_context: APIRequestContext,
) -> Generator[None, None, None]:
# Before all
new_repo = api_request_context.post("/user/repos", data={"name": GITHUB_REPO})
assert new_repo.ok
yield
# After all
deleted_repo = api_request_context.delete(f"/repos/{GITHUB_USER}/{GITHUB_REPO}")
assert deleted_repo.ok
这段代码定义了一个 pytest 装置(fixture)称为 create_test_repository
,作用范围被设置为 "session"
,并且设置 autouse=True
,这意味着它会在整个测试会话期间自动被使用。
这个装置接受一个参数 api_request_context
,类型为 APIRequestContext
,用于与 GitHub API 进行交互。
函数的返回类型被标注为 Generator[None, None, None]
,表明这是一个生成器,不会生成实际的值,只是在开始和结束时执行特定的操作。
在装置的主体内,定义了一个名为 new_repo
的变量,通过调用 api_request_context.post()
向 GitHub API 发送 POST 请求以创建一个新的仓库。断言 new_repo.ok
确保请求成功。
接下来使用 yield
语句表示装置在测试会话的执行过程中会执行到这里,但并不会生成任何值。这一段可以看作是在所有测试用例之前的准备阶段。
在 yield
之后的部分,表示在所有测试用例执行完成后的清理阶段。使用 api_request_context.delete()
发送 DELETE 请求删除之前创建的仓库。同样,断言 deleted_repo.ok
用于确保请求成功。
总之,这个装置的作用是在测试会话的开始时创建一个 GitHub 仓库,在测试会话结束时删除这个仓库。这是为了在测试期间模拟使用真实的 GitHub 仓库,然后在测试结束后进行资源的清理。
快捷键
pycharm全局搜索快捷键:shift+shift
Authentication
import os
# Get session storage and store as env variable
session_storage = page.evaluate("() => JSON.stringify(sessionStorage)")
os.environ["SESSION_STORAGE"] = session_storage
# Set session storage in a new context
session_storage = os.environ["SESSION_STORAGE"]
context.add_init_script("""(storage => {
if (window.location.hostname === 'example.com') {
const entries = JSON.parse(storage)
for (const [key, value] of Object.entries(entries)) {
window.sessionStorage.setItem(key, value)
}
}
})('""" + session_storage + "')")
解析:
这段代码用于获取和设置会话存储(session storage)。会话存储是一种在浏览器中存储数据的机制,仅在当前会话期间有效,关闭浏览器后会话存储数据会被清除。
以下是代码的解释:
-
import os
:导入Python的os
模块,用于处理操作系统相关的功能。 -
获取会话存储:通过
page.evaluate()
方法执行JavaScript代码,获取当前页面的会话存储内容,并将其序列化为字符串。 -
存储为环境变量:将获取的会话存储内容存储为操作系统的环境变量,使用
os.environ
来设置环境变量。 -
设置会话存储:使用新的浏览器上下文(
context
),通过添加初始化脚本来将之前存储的会话存储内容设置到新的上下文中。这确保在新的上下文中,会话存储的内容与之前的页面保持一致。
需要注意的是,这段代码的目的是在不同的浏览器上下文之间共享会话存储的内容。它演示了如何将会话存储数据从一个页面提取出来,存储为环境变量,然后在新的上下文中将会话存储数据重新设置为相同的值,以模拟会话在不同页面之间的持续性。
Browsers
Each version of Playwright needs specific versions of browser binaries to operate:要确保每个Playwright版本与特定版本的浏览器二进制文件兼容。
用命令行 CLI来下载:playwright install
指定数据库命令,pytest s.py --browser firefox
改变装置device:
def run(playwright):
iphone_13 = playwright.devices['iPhone 13']
browser = playwright.webkit.launch(headless=False)
context = browser.new_context(
**iphone_13,
)
with sync_playwright() as playwright:
run(playwright)
viewpoint
context = browser.new_context(
viewport={ 'width': 1280, 'height': 1024 }
)
notifications:接受通知
context = browser.new_context(
permissions=['notifications'],
)
JShandle
length = page.evaluate("a => a.length", my_array_handle)
获得某个元素的长度:
# 获取所有链接元素 links = page.query_selector_all("a") # 使用page.evaluate获取链接元素的数量 links_count = page.evaluate("links => links.length", links)
创建数组,
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
# 创建一个JavaScript数组
my_array_handle = page.evaluate_handle('[]')
# 向数组中添加一个新元素
page.evaluate("(arg) => arg.myArray.push(arg.newElement)", {
'myArray': my_array_handle,
'newElement': 2
})
# 使用evaluate获取数组的长度
array_length = page.evaluate("(arg) => arg.myArray.length", {'myArray': my_array_handle})
print(f"Array length after adding a new element: {array_length}")
# 释放句柄
my_array_handle.dispose()
browser.close()
locator和ElementHandle的区别
在Playwright中,Locator
和ElementHandle
之间的区别在于后者指向特定的元素,而前者捕获了如何检索该元素的逻辑。
-
Locator
是一个对象,它包含了定位元素的方法和逻辑,但它本身并不代表一个具体的元素。可以将Locator
看作是一个“定位器”,它定义了如何找到页面上的某个元素,但在实际使用之前,需要将它转换为ElementHandle
来表示一个具体的元素。 -
ElementHandle
是一个具体的元素句柄,它代表了页面上的一个特定元素。可以对ElementHandle
执行各种操作,如点击、输入等,而且它是直接操作页面上元素的主要方式。 -
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()# 创建一个定位器
locator = page.locator('#myElement')# 通过定位器找到元素,并返回一个ElementHandle
element_handle = locator.first()# 在元素上执行操作
element_handle.click()browser.close()
-
在这个例子中,
locator
是一个定位器,它指定了如何找到ID为myElement
的元素。通过locator.first()
方法,我们获得了一个ElementHandle
,然后可以在该元素上执行操作。
locator
page.get_by_label("User Name").fill("John")
page.get_by_label("Password").fill("secret-password")
page.get_by_role("button", name="Sign in").click()
expect(page.get_by_text("Welcome, John!")).to_be_visible()
举例:
<h3>Sign up</h3>
<label>
<input type="checkbox" /> Subscribe
</label>
<br/>
<button>Submit</button>
expect(page.get_by_role("heading", name="Sign up")).to_be_visible()
page.get_by_role("checkbox", name="Subscribe").check()
page.get_by_role("button", name=re.compile("submit", re.IGNORECASE)).click()
get_by_label:
<label>Password <input type="password" /></label>
输入input:page.get_by_label(“Password")fill("xxx")
get_by_placeholder:
<input type="email" placeholder="name@example.com" />
page.get_by_placeholder("name@example.com").fill("playwright@microsoft.com")
get_by_text:
<span>Welcome, John</span>
expect(page.get_by_text("Welcome, John")).to_be_visible()
expect(page.get_by_text("Welcome, John", exact=True)).to_be_visible() 断言内容是精确的
expect(page.get_by_text(re.compile("welcome, john", re.IGNORECASE))).to_be_visible() 断言忽略大小写
get_by_alt_text:
<img alt="playwright logo" src="/img/playwright-logo.svg" width="100" />
page.get_by_alt_text("playwright logo").click()
get_by_title:
<span title='Issues count'>25 issues</span>
expect(page.get_by_title("Issues count")).to_have_text("25 issues")
get by css or xpath
page.locator("css=button").click()
page.locator("xpath=//button").click()
page.locator("button").click()
page.locator("//button").click()
page.locator(
"#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input"
).click()
page.locator('//*[@id="tsf"]/div[2]/div[1]/div[1]/div/div[2]/input').click()
filter:
例子:<ul>
<li>
<h3>Product 1</h3>
<button>Add to cart</button>
</li>
<li>
<h3>Product 2</h3>
<button>Add to cart</button>
</li>
</ul>
page.get_by_role("listitem").filter(has_text="Product 2").get_by_role("button",name="Add to cart").click()
用正则:page.get_by_role("listitem").filter(has_text=re.compile("Product 2")).get_by_role("button",name="Add to cart").click()
另一种包含:page.get_by_role("listitem").filter(
has=page.get_by_role("heading", name="Product 2")
).get_by_role("button", name="Add to cart").click()
不包含:expect(
page.get_by_role("listitem").filter(
has_not=page.get_by_role("heading", name="Product 2")
)
).to_have_count(1)
has_no_text
# 验证页面上不包含 "Out of stock" 标记的商品数量是否为 5
expect(page.get_by_role("listitem").filter(has_not_text="Out of stock")).to_have_count(5)
解析:找到li元素中内容不是Out of stock的,然后判断数量是否为5
利用两个定位器来匹配:
button = page.get_by_role("button").and_(page.getByTitle("Subscribe"))
利用其中一个来匹配:.or_ expect(new_email.or_(dialog)).to_be_visible()
匹配可见的:
<button style='display: none'>Invisible</button>
<button>Visible</button>
page.locator("button").click()两个都匹配到
page.locator("button").locator("visuble=true").click()
github地址:GitHub - ThompsonJonM/python-playwright: A testing repository using Python 3.9, Pytest, and Playwright.
class API(object): @staticmethod def authenticate() -> Union[dict, None]: 随机返回一个 _data: dict = user _url = f"{base_url}/Account/v1/Login" tries: int = 10 while True: tries -= 1 sleep(random()) response: Response = requests.post(url=_url, data=_data)发起requests请求 if response.status_code == 200: return response.json() 返回json的数据 elif tries == 0: raise HTTPError( f"Requests failed with response: {response.json()}" ) 这段代码用于获得用户登录后的token内容
设置统一的浏览器环境:
@pytest.fixture(scope="session") def browser_context_args(browser_context_args: fixture) -> dict: response: Union[dict, str] = api.authenticate() return { **browser_context_args, "storage_state": { "cookies": [ { "name": "token", "value": response["token"], "path": path, "domain": domain, }, ] }, }
python
import requests url="https://supplierportal-ui.sp.uat.dbs.corp/api/security/ldap-login" data = { "username": "weechong", "password": "" } response = requests.post(url, json=data, verify=False) if response.status_code == 200: print("Request was successful") print("Response data:", response.json()) else: print("Request failed with status code:", response.status_code)
playwright代码:
#两个装饰器、用于分类、方便组织测试和筛选测试 @pytest.mark.authentication @pytest.mark.bookstore class TestLogin: def test_valid_login(self, page: Page) -> None: """Login with valid credentials. :param page: A Playwright browser page. """ page.context.clear_cookies() #清除cooki,保证登录环境的干净 login_page = Login(page) #登录页面 profile_page = Profile(page) #用户信息页面 login_page.navigate() #跳转到登录页面 login_page.fill_form(user) #填充登录信息 login_page.submit_button.click() #点击登录按钮 visible: bool = profile_page.username_value_field.is_visible() #用户信息页面是否显示用户名 #断言profile在页面的url中并且visible为true assert "profile" in page.url and visible
page = await browser.new_page() await page.route(re.compile(r\"(\\.png$)|(\\.jpg$)\"), lambda route: route.abort()) await page.goto(\"https://example.com\") await browser.close()
这段代码使用 Playwright 进行如下操作:
-
使用
browser.new_page()
创建一个新的浏览器页面,并将其赋值给变量page
。 -
使用
page.route()
方法拦截图片文件的请求,通过正则表达式re.compile(r"(\\.png$)|(\\.jpg$)")
来匹配以 ".png" 或 ".jpg" 结尾的 URL,然后使用lambda route: route.abort()
将这些请求中断。 -
使用
page.goto("https://example.com")
导航到指定的网页 "https://example.com"。 -
最后,使用
browser.close()
关闭浏览器实例,释放资源。
综上所述,这段代码的作用是创建一个新的浏览器页面,拦截所有图片文件的请求(通过正则表达式匹配),然后导航到指定网页,最后关闭浏览器实例。这可以用于在测试中控制请求的行为,比如在此示例中中断图片请求。