skip to Main Content

Lets say I have a class containing page objects. If I instantiate that class in a test, then I can use the page objects as you normally would with playwright. I would like to get the css value as a string from one of those page objects so I can use it in a custom function.

This should give you an idea of what I mean

    export class PageObjects{
        constructor(page){
            this.obj1 = page.locator('#table tbody tr')
        }
    }

    test('Get Values and header names from a table', async({page})=> {
        // Page objects
        const pageObjects = new PageObjects(page)

        // I want the css from the page object?? in other words  
        // how do I pull out the '#table tbody tr' css value as a string?
        // in other words I want cssString value to be #table tbody tr
        // I have tried _selector but it just returns an empty string
        cssString = pageObjects.obj1.??????


    })

is there a way to get the css value out of a locator?

3

Answers


  1. Chosen as BEST ANSWER

    The answer is that you cannot get the string argument passed to the locator in Playwright. But the point was really to use the css selector string to manipulate the DOM. So if you need to manipulate the HTML elements inside some specific element then you can first set up a page object class and define that element as a Locator. Then you can import the class into your test and instantiate the class. Then you can call the locator as a property on the class. Once you have done this then you can use myLocator.evaluate( el=>{}) to access and manipulate the DOM.

        // In your pageObject file
        export class PageObjs{
            obj1: Locator;
            constructor(page){
                this.obj1 = page.locator('#idOfElement')
            }
        }
    
        // In your test
    
        test('Test Description', async({page})=>{
    
            const pageObjects = new PageObjs(page);
    
            pageObjects.obj1.evaluate( el => {
                let tableDataCells = el.document.querySelectorAll('td')
                // do stuff in the dom
                let list = tableDataCells.map(()=>{})
                return list
            })
    
        })
    

  2. In general ,this is not recommended approach for maintaining locators.

    Test should be least exposed to locators directly to avoid any future maintenance efforts in mutiple test files as when they change.

    Use Page Object Model which is a software design pattern for better code abstraction, reusability and long term maintenance.

    In Page Object Model, all the application functionality is captured as black boxes in the form of page object methods which also encapsulate UI objects(CSS/Xpath) as well so that they can be added or updated from a single place of reference- DRY principle.

    Full Code Example:

    Page Object:

    // playwright-dev-page.js
    const { expect } = require('@playwright/test');
    
    exports.PlaywrightDevPage = class PlaywrightDevPage {
    
      /**
       * @param {import('@playwright/test').Page} page
       */
      constructor(page) {
        this.page = page;
        this.getStartedLink = page.locator('a', { hasText: 'Get started' });
        this.gettingStartedHeader = page.locator('h1', { hasText: 'Installation' });
        this.pomLink = page.locator('li', { hasText: 'Guides' }).locator('a', { hasText: 'Page Object Model' });
        this.tocList = page.locator('article div.markdown ul > li > a');
      }
    
      async goto() {
        await this.page.goto('https://playwright.dev');
      }
    
      async getStarted() {
        await this.getStartedLink.first().click();
        await expect(this.gettingStartedHeader).toBeVisible();
      }
    
      async pageObjectModel() {
        await this.getStarted();
        await this.pomLink.click();
      }
    }
    

    Test:

        // example.spec.js
    const { test, expect } = require('@playwright/test');
    const { PlaywrightDevPage } = require('./playwright-dev-page');
    
    test('getting started should contain table of contents', async ({ page }) => {
      const playwrightDev = new PlaywrightDevPage(page);
      await playwrightDev.goto();
      await playwrightDev.getStarted();
      await expect(playwrightDev.tocList).toHaveText([
        `How to install Playwright`,
        `What's Installed`,
        `How to run the example test`,
        `How to open the HTML test report`,
        `Write tests using web first assertions, page fixtures and locators`,
        `Run single test, multiple tests, headed mode`,
        `Generate tests with Codegen`,
        `See a trace of your tests`
      ]);
    });
    
    test('should show Page Object Model article', async ({ page }) => {
      const playwrightDev = new PlaywrightDevPage(page);
      await playwrightDev.goto();
      await playwrightDev.pageObjectModel();
      await expect(page.locator('article')).toContainText('Page Object Model is a common pattern');
    });
    
    Login or Signup to reply.
  3. I’m not sure I follow the motivation for this pattern (Playwright already has a Page abstraction–why do you want to wrap it with another–unless you’re going for a POM as described in this answer?) and it smells like a potential xy problem, but I don’t see why you can’t set that argument as a varible on your instance:

    export class PageObject {
      constructor(page) {
        this.selector = '#table tbody tr';
        this.locator = page.locator(this.selector);
      }
    }
    
    // ...
    
    const pageObject = new PageObject();
    console.log(pageObject.selector);
    

    I’ve taken the liberty to rename a couple of variables: plural usually refers to collections like arrays and objects, so PageObjects would be typed PageObject[], and obj1 is an unclear name.

    It’s a bit strange to hardcode that selector. This effectively makes the class single purpose only, so you might consider making that a parameter, depending on your use case.

    Even if you are able to find a property on Playwright locators holding the argument, I wouldn’t rely on it, since there is no documented public property in the API.

    If you want the argument in one place, it’s easy enough to hardcode it as part of a global selector config, or bind it in a wrapper function if some aspects of the selector may change.

    I suggest taking a step back and re-approaching your design from the top down. Start from your goals and inquire whether this really the right abstraction for achieving them. Often, if things seem hard to do and you find you’re battling your design or the language, it’s probably not the right approach.

    Generally, the best approach is to not write abstractions immediately. Then, once the cut points emerge, you can write accurate, non-premature and robust abstractions that solve clear problems.


    If I misunderstood the question and you want to get the CSS style of the elements, you can use getComputedStyle:

    const style = await page
      .locator("#table tbody tr")
      .evaluateAll(els =>
        els.map(e =>
          Object.fromEntries(
            [...getComputedStyle(e)].map(k => [
              k,
              getComputedStyle(e)[k],
            ])
          )
        )
      );
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search