microsoft / playwright

Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
https://playwright.dev
Apache License 2.0
67.23k stars 3.7k forks source link

[Question] Frame locators with fixtures #11473

Closed pkumanowski closed 2 years ago

pkumanowski commented 2 years ago

Hi,

I'm trying to implements fixtures that uses frameLocator. It's build like this:

import { expect } from '@playwright/test';
import type { Page } from 'playwright';

export default class CommonActions {
    readonly page: Page;

    constructor(page: Page) {
        this.page = page;
    }

    async waitForIframe(locator: string) {
        const iframe = this.page.frameLocator(locator);
        await expect(iframe);
    }

Which then is used in here:

import type { Page } from 'playwright';
import CommonActions from '../../lib/CommonActions';
import RtmPageObjects from '../pageObjects/rtmPageObjects';

let commonActions: CommonActions;
export default class RtmPage {
    readonly page: Page;

    constructor(page: Page) {
        this.page = page;
    }

    rtmPageObjects = new RtmPageObjects();

    async getIframe() {
        await commonActions.waitForIframe(this.rtmPageObjects.IFRAME);
    }

My fixtures are initilized here:

import { test as baseTest } from '@playwright/test';
import LoginPage from '../pages/LoginPage';
import RtmPage from '../pages/RTM/RtmPage';

const test = baseTest.extend<{
    loginPage: LoginPage;
    rtmPage: RtmPage;
}>({
    loginPage: async ({ page }, use) => {
        await use(new LoginPage(page));
    },
    rtmPage: async ({ page }, use) => {
        await use(new RtmPage(page));
    },
});

export default test;

Test:

test('test 1', async ({ rtmPage }) => {
    await test.step('Navigate to Application', async () => {
        await rtmPage.goToRTM();
        await rtmPage.getIframe();
    });

When I run the test, i always get: TypeError: Cannot read properties of undefined (reading 'waitForIframe') Any idea how to solve this, or this is some kind of limitation?

When I put: await expect(this.page.frameLocator(this.rtmPageObjects.IFRAME)); in getIframe frunction

  async getIframe() {
        await expect(this.page.frameLocator(this.rtmPageObjects.IFRAME));
    }

then test passes

mxschmitt commented 2 years ago

let commonActions: CommonActions; this just defines the variable but never assignes a value to it. You can e.g. put it inside your class instead, this would look like this:

export default class RtmPage {
    readonly page: Page;
    readonly commonActions: CommonActions;

    constructor(page: Page) {
        this.page = page;
        this.commonActions = new CommonActions(page);
    }

    rtmPageObjects = new RtmPageObjects();

    async getIframe() {
        await this.commonActions.waitForIframe(this.rtmPageObjects.IFRAME);
    }

alternatively you can put your CommonActions inside an own fixture and pass it inside the RtmPage constructor, would work as well.

Unrelated to your question, but I saw this code:

    async waitForIframe(locator: string) {
        const iframe = this.page.frameLocator(locator);
        await expect(iframe);
    }

this will never wait/work as you expect it to be, since framelocators and normal locators are only queried when performing page interactions (calling methods like click on them or using the web-first assertions). What you want is something like this instead:

    async waitForIframe(locator: string) {
        const iframe = this.page.frameLocator(locator);
        await expect(iframe).toBeVisible();
    }
pkumanowski commented 2 years ago

Hi @mxschmitt ,
Thank you for quick answer. I missed the unassigned variable. But now i have another problem:

    async waitForIframe(locator: string) {
        const iframe = this.page.frameLocator(locator);
        await expect(iframe).toBeVisible();
    }

When I run my test I get: Error: toBeVisible can be only used with Locator object It looks like frameLocator does not have access to toBeVisible method.

I had to change function waitForIframe to:

  async waitForIframe(frame: string, locator: string) {
        const iframe = this.page.frameLocator(frame).locator(locator);
        await expect(iframe).toBeVisible();
    }

and getIframe function to:

 async getIframe() {
        await this.commonActions
            .waitForIframe(this.rtmPageObjects.IFRAME, this.rtmPageObjects.SKIP_BUTTON);
    }

It looks like I can't asset the visibility of iframe alone.

mxschmitt commented 2 years ago

If you want to check the visibility of the iframe alone, you need to do this:

const iframe = this.page.locator(locator);
        await expect(iframe).toBeVisible();
pkumanowski commented 2 years ago

Thank you, it works now. I thought .locator and .frameLocator would share the same methods

For me it was counterintuitive to use locator for visibility when i was looking for framelocator.

Btw Playwright is awesome :)

mxschmitt commented 2 years ago

They unfortunately don't, FrameLocator is more a gateway with which you can access embedded frames. You can call .locator("foo") on it to get a normal specific locator of an element inside this frame.

Happy to hear!

Closing since all the questions seems answered.