自动化工具 初试 playwright UI 自动化

北洌鲸涛 · 2023年10月19日 · 最后由 北洌鲸涛 回复于 2023年10月20日 · 3674 次阅读

最近看到了 playwright , 试着用了下,主要试了常用的 storage_state,handle 以及 参数化 cases 几个点
这次更新了下,基本几个常用的都有了。

我的 page object

from utils.handle_logging import log
from playwright.sync_api import Playwright
import time

'''
xxxxx page object
'''

class Treasury:
    def __init__(self, page: Page):
        self.page = page
        self.base_url = 'https://xxxxxxxxx.com'

        # page elements
        self.logo_img = self.page.get_by_role('img', name='logo')
        self.no_data = self.page.get_by_role("cell", name="No data").locator("div").nth(1)

        ## iframe
        self.mint_DREX_confirm_frame = self.page.get_by_text("Mint DREXAmountDREXConfirmCancel")

        ## input
        self.client_id_input = self.page.get_by_placeholder("Client ID")
        self.start_day_input = self.page.get_by_placeholder("Start date")

        ## button
        self.reset_buttton = self.page.get_by_role("button", name="Reset")
        self.filter_buttton = self.page.get_by_role("button", name="Apply filters")

        ## top menu
        self.DREX_menu = self.page.get_by_role("menuitem", name="DREX")
        self.DVt_menu = self.page.get_by_role("menuitem", name="DVt")
        self.clients_menu = self.page.get_by_role("menuitem", name="Clients")

        ## tab menu
        self.wallet_balance_tab  = self.page.get_by_role("tab",name="Wallet balance")
        self.client_transactions_tab = self.page.get_by_role("tab",name="Client transactions")


    def goto(self, endpoint: str, use_base_url=True):
        if use_base_url:
            self.page.goto(self.base_url + endpoint)
        else:
            self.page.goto(endpoint)

    def login(self, username: str, password: str):
        if self.page.url == self.base_url + "/user/login":
            # self.page.goto(self.base_url + "/user/login")
            self.page.get_by_placeholder("Email").fill(username)
            self.page.get_by_placeholder("Password").fill(password)
            self.page.get_by_role("button", name="Login").click()
            time.sleep(2)
            self.context.storage_state(path='./conf/xxxxxxxx.json')
        else:
            pass

    def client_query(self, client_id: str, status: str) -> None:
        log.info("client id : {}, status : {}".format(client_id, status))
        status = status.upper()
        self.client_id_input.fill(client_id)
        self.page.locator("#status").click()
        self.page.get_by_title(status).get_by_text(status).click()
        self.filter_buttton.click()
        self.page.wait_for_load_state()

    def close(self):
        self.page.close()
        self.context.close()
        self.browser.close()



storage_state 保存登录信息以及后续使用

conftest.py

import pytest
from playwright.sync_api import sync_playwright

@pytest.fixture(scope="session", autouse=True)
def treasury_login(base_info):
    with sync_playwright() as pw:
        chromium = pw.chromium
        browser = chromium.launch()
        page = browser.new_page()
        page.goto(url = base_info['treasury_base_url'] + '/#/user/login')
        page.get_by_placeholder("Email").fill(base_info['treasury_user'])
        page.get_by_placeholder("Password").fill(base_info['treasury_pwd'])
        page.get_by_role("button", name="Login").click()
        page.wait_for_url(base_info['treasury_base_url']+ '/#/xxxx/xxxx')
        # storage auth info
        page.context.storage_state(path=AUTH_DIR + '/treasury_state.json')
        page.close()
        browser.close()

@pytest.fixture(scope="function",autouse=True)
def browser_context_args(browser_context_args):
    return {
        **browser_context_args,
        # "storage_state": AUTH_DIR + '/treasury_state.json'

    }

case 里面使用

# 不使用已保存的storage_state 信息
@pytest.mark.browser_context_args()
def test_login_success(page:Page) -> None:
    p = treasury_page_object.Treasury(page)
    p.login(username='xxxxxx', password='xxxxxx')
    p.page.wait_for_url(p.base_url+ '/#/xxxxxx')
    assert p.DREX_menu.is_visible()

# 使用已保存的storage_state 信息
@pytest.mark.browser_context_args(storage_state=AUTH_DIR + '/treasury_state.json')
def test_login_and_logout_success(page:Page) ->None:
    p = treasury_page_object.Treasury(page)
    p.goto_home()
    p.page.wait_for_load_state()
    # click username to logout
    p.page.get_by_text("xxxxxxxxxx").click()
    p.page.get_by_text("Logout").click()
    p.page.wait_for_load_state()
    log.info(p.page.url)
    assert p.page.url  == p.base_url + '/#/user/login'

### 参数化
```code
none_para = ['APPROVED','PENDING','DECLINED']
@pytest.mark.parametrize("status", none_para)
@pytest.mark.browser_context_args(storage_state=AUTH_DIR + '/treasury_state.json')
def test_clients_query_with_status_success(page:Page,status) ->None:
    client_id = '2883'
    p = treasury_page_object.Treasury(page)
    p.goto_home()
    p.clients_menu.click()
    p.client_query_with_status(client_id,status)
    # time.sleep(1)
    p.page.wait_for_load_state()
    if p.no_data.is_visible():
        pass
    else:
        expect(p.page.get_by_role("cell", name=client_id)).to_be_visible()
        expect(p.page.get_by_role("cell", name=status)).to_be_visible()

mock

# handle_mock 
def handle_401(route):
    # 用户没有权限(令牌、用户名、密码错误)
    route.fulfill(
        status=401,
        json={
        "error": "unauthorized",
        "error_description": "Full authentication is required to access this resource"
    })

def handle_500(route):
    # 服务器发生错误,请检查服务器
    route.fulfill(status=500)

def handle_login_under_review(route):
    # mock 用户登录时,unser review
    json = {
            "traceId": "26e8edaf3c45474db05522a04a4408b9.263.16989832389060341",
            "code": "SYSTEM_ERROR",
            "message": "Your account is currently under review.",
            "displayMessage": "Your account is currently under review.",
            "data": None
        }
    log.info(json)
    route.fulfill(status=200,json=json)

# case 里使用
@pytest.mark.browser_context_args(storage_state=AUTH_DIR + '/treasury_state.json')
def test_api_mock_401(page:Page) ->None:
    p = treasury_page_object.Treasury(page)
    p.goto_home()
    # mock 401
    p.page.route(p.base_url + '/api/**',handler=handle_mock.handle_401)
    p.DREX_menu.click()
    p.page.wait_for_url(p.base_url + '/#/user/login')
    expect(p.page.get_by_text("Your session is over, please login again").first).to_be_visible()
    p.page.screenshot(path='./results/BancoABC/Treasury/{}.png'.format(401))

@pytest.mark.browser_context_args(storage_state=AUTH_DIR + '/treasury_state.json')
def test_api_mock_500(page:Page) ->None:
    p = treasury_page_object.Treasury(page)
    p.goto_home()
    # mock 500
    p.page.route(p.base_url + '/api/**',handler=handle_mock.handle_500)
    p.DREX_menu.click()
    p.page.wait_for_load_state()
    expect(p.page.get_by_text("Internal server error")).to_be_visible()
    p.page.screenshot(path='./results/BancoABC/Treasury/{}.png'.format(500))

使用 chrome,firefox,webkit 多浏览器测试

def run_tests_and_generate_html_report():
    # Specify the test testcases to be executed
    test_cases = [

        "testcases/BancoABC/test_treasury.py",
    ]

    # Set up pytest arguments
    args = [
        "-v",
        # "-m smoke",
        # "--template=html1/index.html",
        '--browser', 'webkit','--browser', 'chromium','--browser', 'firefox',
        # '--browser-channel','msedge',
        # '--device',"iPhone 11 Pro",
        "--html=./TestReport/Test_Report.html",
    ]
    # Append test testcases to the arguments
    args.extend(test_cases)

    # Run pytest and capture the exit code
    log.info(args)
    exit_code = pytest.main(args)

    # Return the exit code
    return exit_code

第一次发帖子,写的不好,多包涵,后面继续深入使用了,有其它好用的或者困惑的,继续更新吧

  • 写 page object 的时候,建议打开 playwright codegen , 这样写起来飞快;
  • 自带的报告目前只有 nodejs 版本有,python 版本的目前还没有,请用 pytest-html 或者 allure
共收到 2 条回复 时间 点赞

我们也在用 palyWright 不过只是用在 UI 上了,接口我们用的 httpmock

北洌鲸涛 关闭了讨论 10月20日 17:11
北洌鲸涛 重新开启了讨论 10月20日 17:31
仅楼主可见
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册