vitalets / playwright-bdd

BDD testing with Playwright runner
https://vitalets.github.io/playwright-bdd/
MIT License
287 stars 33 forks source link

Question: Continuation porting from Cucumber World to a Fixture #120

Closed RichardCariven closed 6 months ago

RichardCariven commented 6 months ago

In a previous thread I was trying to port across from a Cucumber project to a Playwright BDD project and you gave some pointers on switching from Cucumber World to BDD World.

I am still trying to convert this all into Fixtures to avoid needing to use World at all.

import { ElementKey } from '../../env/global';
import {getElementLocator} from "../../support/web-element-helper";
import { waitFor } from '../../support/wait-for-behavior';
import { createBdd } from 'playwright-bdd';
import { test } from "../setup/fixtures"
const { Then } = createBdd(test);
Then(
    "the {string} should contain the text {string}",
    async ({page }, elementKey: ElementKey, expectedElementText: string) => {

        const elementIdentifier = getElementLocator(page, elementKey, globalVariables, globalConfig)

        await waitFor(async () => {
            const elementText = await page.textContent(elementIdentifier)
            return elementText?.includes(expectedElementText);
        });
})

This is the step I am trying to get working

I have converted it from

import { Then } from '@cucumber/cucumber'
import { ElementKey } from '../../env/global';
import {getElementLocator} from "../../support/web-element-helper";
import { waitFor } from '../../support/wait-for-behavior';

Then(
    /^the "([^"]*)" should contain the text "(.*)"$/,
    async function(elementKey: ElementKey, expectedElementText: string) {
        const {
            screen: { page },
            globalVariables,
            globalConfig,
        } = this;

        const elementIdentifier = getElementLocator(page, elementKey, globalVariables, globalConfig)

        await waitFor(async () => {
            const elementText = await page.textContent(elementIdentifier)
            return elementText?.includes(expectedElementText);
        });

    }
)

I have previously converted the Index into a Fixture

import dotenv from 'dotenv'
import { env, getJsonFromFile } from '../../env/parseEnv'
import {
    GlobalConfig,
    HostsConfig,
    PagesConfig,
    PageElementMappings,
} from '../../env/global';
import fs from "fs";
import {test as baseTest } from 'playwright-bdd'

type PageMapType = {
    worldParameters: GlobalConfig
}
const pageMapfixture = baseTest.extend<PageMapType>({
    worldParameters: async ({}, use) => {
        dotenv.config({path: env('COMMON_CONFIG_FILE')})
        const hostsConfig: HostsConfig = getJsonFromFile(env('HOSTS_URLS_PATH'));
        const pagesConfig: PagesConfig = getJsonFromFile(env('PAGE_URLS_PATH'));
        const mappingFiles = fs.readdirSync(`${process.cwd()}${env('PAGE_ELEMENTS_PATH')}`);
        const pageElementMappings: PageElementMappings = mappingFiles.reduce(
            (pageElementConfigAcc, file) => {
                const key = file.replace('.json', '');
                const elementMappings = getJsonFromFile(`${env('PAGE_ELEMENTS_PATH')}${file}`);
                return { ...pageElementConfigAcc, [key]: elementMappings };
            },
            {}
        );
        await use({hostsConfig, pagesConfig, pageElementMappings})
     },
})

export const test = pageMapfixture

But I am now trying to convert the World.TS into a fixture, so that global variables and globalConfig can be passed through to the test without using cucumber world, any suggestions?

import { World, IWorldOptions, setWorldConstructor} from "@cucumber/cucumber";
import { GlobalConfig, GlobalVariables } from '../../env/global';

export class ScenarioWorld extends World {
    constructor(options: IWorldOptions) {
        super(options)

        this.globalConfig = options.parameters as GlobalConfig;
        this.globalVariables = { currentScreen: ""};
    }
    globalConfig: GlobalConfig;
    globalVariables: GlobalVariables;
}
setWorldConstructor(ScenarioWorld)
vitalets commented 6 months ago

Yes, you are almost done! I see that your worldParameters fixture has type GlobalConfig, so I assume it contains global config data. In that case you can just use this fixture inside a test, e.g.:

Then(
    "the {string} should contain the text {string}",
    async ({ page, worldParameters }, elementKey: ElementKey, expectedElementText: string) => {

        const elementIdentifier = getElementLocator(page, elementKey, globalVariables, worldParameters)

        await waitFor(async () => {
            const elementText = await page.textContent(elementIdentifier)
            return elementText?.includes(expectedElementText);
        });
})

The same is for globalVariables:

  1. define globalVariables fixutre:
    type PageMapType = {
    worldParameters: GlobalConfig,
    globalVariables: GlobalVariables
    }
    const pageMapfixture = baseTest.extend<PageMapType>({
    worldParameters: async ({}, use) => { ... }),
    globalVariables: async ({}, use) => {
       await use({ currentScreen: ""});
    }),
    });
  2. use it in test:

    Then(
    "the {string} should contain the text {string}",
    async ({ page, worldParameters, globalVariables}, elementKey: ElementKey, expectedElementText: string) => {
    
        const elementIdentifier = getElementLocator(page, elementKey, globalVariables, worldParameters)
    
        await waitFor(async () => {
            const elementText = await page.textContent(elementIdentifier)
            return elementText?.includes(expectedElementText);
        });
    })

One optimization I can suggest for the future: Currently worldParameters fixture runs before every test, that means it reads the same json files many times. I think you can move these operations out of the fixture:

import dotenv from 'dotenv'
import { env, getJsonFromFile } from '../../env/parseEnv'
import {
    GlobalConfig,
    HostsConfig,
    PagesConfig,
    PageElementMappings,
} from '../../env/global';
import fs from "fs";
import {test as baseTest } from 'playwright-bdd'

dotenv.config({path: env('COMMON_CONFIG_FILE')});
const hostsConfig: HostsConfig = getJsonFromFile(env('HOSTS_URLS_PATH'));
const pagesConfig: PagesConfig = getJsonFromFile(env('PAGE_URLS_PATH'));
const mappingFiles = fs.readdirSync(`${process.cwd()}${env('PAGE_ELEMENTS_PATH')}`);
const pageElementMappings: PageElementMappings = mappingFiles.reduce(
    (pageElementConfigAcc, file) => {
        const key = file.replace('.json', '');
        const elementMappings = getJsonFromFile(`${env('PAGE_ELEMENTS_PATH')}${file}`);
        return { ...pageElementConfigAcc, [key]: elementMappings };
    },
    {}
);

const pageMapfixture = baseTest.extend<PageMapType>({
    worldParameters: async ({}, use) => {
        await use({ hostsConfig, pagesConfig, pageElementMappings })
     },
})
RichardCariven commented 6 months ago

this seems to have worked, although hitting a different issue will raise a new question Thankyou