Skip to content

[Question]: Best practices for using page-objects as pytest fixtures and for implementing async methods #2008

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
avielKogan1 opened this issue Jul 10, 2023 · 8 comments
Labels

Comments

@avielKogan1
Copy link

avielKogan1 commented Jul 10, 2023

Your question

Hi!
I'm practicing playwright/pytest.
I wonder what would be the best practices for:

  1. Implementing page-objects as PyTest fixtures?
  2. Implementing async methods within page objects files.
  3. Using the page object fixures in the test files themselves.

This is the high-level structure of my project:

PROJECT
│ README.md

└───src

├───infra
│ │ init.py
│ │
│ └───page_objects
│ └───login
│ │ login_page.py

└───tests
└───sanity_tests
│ test_login.py

@mxschmitt
Copy link
Member

You can create custom Pytest fixtures with your POMs like that:

@pytest.fixture
def my_page(page: Page):
    return MyPage(page)

See here: https://docs.pytest.org/en/6.2.x/fixture.html#back-to-fixtures

Playwright has no best practises in this regard for Python yet, so it totally depends on your project. If you feel comfortable with your setup, understand it and provides you all the flexibility, then its good enough!

@avielKogan1
Copy link
Author

Yes, but when I want to use methods I've defined in the page object class, in the scope of the test, it doesn't recognize the methods and the attributes.

@mxschmitt
Copy link
Member

You mean typing wise? Are you annotating it with types?

def test_foo(my_page: MyPage) -> None:
    my_page.foo()

@avielKogan1
Copy link
Author

avielKogan1 commented Jul 12, 2023

I want to have a fixture for login_page object, so I won't have to initialize it every test from scratch and duplicate my code.
Do you mean doing it this way?

`@pytest.fixture
async def login_page(browser):
page = await browser.new_page()
login_page = LoginPage(page)
return login_page

@pytest.mark.asyncio
async def test_login_with_valid_credentials(login_page: LoginPage):
logging.info(f"login page url: {await login_page.page_url}")
await login_page.goto()
await login_page.verify_page_loaded()`

I get this error:
AttributeError: 'coroutine' object has no attribute 'page_url'

@mxschmitt
Copy link
Member

Oh I see, you are not using our pytest-playwright plugin since you want to leverage the async version of Playwright.

Yes you could do it like that. You can modify your fixture to this and then it should work:

@pytest.fixture
async def login_page(browser):
    page = await browser.new_page()
    login_page = LoginPage(page)
    yield login_page

see e.g. here.

@avielKogan1
Copy link
Author

@mxschmitt do you mean like this:

import pytest
import logging
from src.infra.page_objects.login.login_page import LoginPage
from src.infra.page_objects.platform.dashboard.dashboard_page import DashboardPage
from src.tests.test_data.test_data import User
from playwright.async_api import async_playwright


def pytest_configure(config):
    logging.basicConfig(level=logging.INFO)
    root = logging.getLogger()
    root.setLevel(logging.INFO)

@pytest.fixture
async def login_page(browser):
    page = await browser.new_page()
    login_page = LoginPage(page)
    yield login_page

@pytest.mark.asyncio
async def test_login_with_valid_credentials(login_page):
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        page = await browser.new_page()
        # Initializing user object
        user = User()
       
        # Go to login page
        await login_page.goto()

        # Verify Login Page loaded
        await login_page.verify_page_loaded()
        

        # Fill in user name
        await login_page.fill_username(user.username)

        # Fill in password
        await login_page.fill_password(user.password)

        # Click Login button
        await login_page.click_login_button()
 

I tried to run it this way and encoutered the following error:

self = <_UnixSelectorEventLoop running=False closed=False debug=False>

def _check_running(self):
    if self.is_running():
        raise RuntimeError('This event loop is already running')
    if events._get_running_loop() is not None:
      raise RuntimeError(
            'Cannot run the event loop while another loop is running')

E RuntimeError: Cannot run the event loop while another loop is running

/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/asyncio/base_events.py:590: RuntimeError
================================================================================= short test summary info =================================================================================
FAILED test_login.py::test_login_with_valid_credentials[chromium] - RuntimeError: Cannot run the event loop while another loop is running
==================================================================================== 1 failed in 0.79s ====================================================================================
Task was destroyed but it is pending!
task: <Task pending name='Task-5' coro=<test_login_with_valid_credentials() running at /Users/user1/Documents/GitHub/OrangeHRM_Automation/src/tests/sanity_tests/test_login.py:20>>
sys:1: RuntimeWarning: coroutine 'test_login_with_valid_credentials' was never awaited
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

@mxschmitt
Copy link
Member

Why are you using async in the first place and not our pytest plugin? It provides a lot of things already out of the box: https://playwright.dev/python/docs/test-runners

You need to manually create a browser fixture, otherwise your login_page fixture doesn't know how to get the browser.

Something like this:

@pytest.mark.asyncio
async def browser():
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        yield browser

and then you can change your test to e.g. this:

@pytest.mark.asyncio
async def test_login_with_valid_credentials(login_page):   
      # Go to login page
      await login_page.goto()

      # Verify Login Page loaded
      await login_page.verify_page_loaded()
      

      # Fill in user name
      await login_page.fill_username(user.username)

      # Fill in password
      await login_page.fill_password(user.password)

      # Click Login button
      await login_page.click_login_button()
 

@mxschmitt
Copy link
Member

Closing as per above.

When creating own async pytest fixtures (not using pytest-playwright) we can't provide any official support. See here for our sync implementation, which should be very similar for async. And see microsoft/playwright-pytest#74

@mxschmitt mxschmitt closed this as not planned Won't fix, can't repro, duplicate, stale Jul 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants