Home > Back-end >  Is there any programmatic workaround to perform chain of actions on selectors in playwright?
Is there any programmatic workaround to perform chain of actions on selectors in playwright?

Time:12-01

According to the official documentation, playwright doesn't support chaining the actions you perform on a single selector, like alternatively Cypress allows for. In result, your code file grows with repetitive expressions:

await page.getByRole('textbox').click();
await page.getByRole('textbox').fill('test value in the text box');
await page.getByRole('textbox').press('Enter');

What I'm trying to achieve is that after you perform a click, you can subsequently, perform a fill and maybe also a press, if it's programmaticaly possible, in a single expression. Without repeating await page.getByRole('textbox')

I'm fairly new to javascript and typescript. I'm reading documentation, but feeling overwhelmed. Would be super grateful for any guidance concerning if e.g. promises would solve this issue, with an example on the above code provided.

Using "@playwright/test": "^1.28.0"

CodePudding user response:

You can simplify your code by moving the Locator to a variable and reuse that. So something like this:

const myTextbox = page.getByRole('textbox');
await myTextbox.click();
await myTextbox.fill('some text');
await myTextbox.press('Enter');

Not sure what your Use Case is but by using fill you can usually already skip the click beforehand. Not even sure if you need the enter there for it to work.

I do not think it is possible to chain all those actions without making it overly complicated or hard to read.

CodePudding user response:

This might work (using Proxy). I didn't try, I don't use this library and I probably made too simplistic assumptions about the types but it should be a good start.

type ChainLocator = {
    [K in keyof Locator]: Locator[K] extends ((...args: any[]) => Promise<void>)
        ? (...args: Parameters<Locator[K]>) => ChainLocator : Locator[K]
} & { run: () => Promise<void> }


const chain = (selector: Locator): ChainLocator => {
    const queue: ((...args: any[]) => Promise<void>)[] = [];
    const proxy = new Proxy(selector, {
        get: (target, key: keyof Locator | 'run') => 
            key === 'run' ? async () => {
                for (const f of queue) await f(selector)
            }
            
            : (typeof target[key] === 'function') ? (...args: any[]) => {
                queue.push(target[key].bind(target, args));
                return proxy;
            }

            : target[key]
        
    });
    return proxy;
}


chain(page.getByRole('textbox'))
    .click().fill('test value in the text box').press('Enter').run();

Now yet another way to do it is to chain function calls

const chain = (selector: Locator) =>
    async (...fs: ((a: Locator) => Promise<void>)[]) => {
        for (const f of fs) await f(selector);
    }

chain(page.getByRole('textbox'))(
    s => s.click(),
    s => s.fill('test value in the text box'),
    s => s.press('Enter')
)

It's a little verbose because the API is not functional. Ideally we would like something like this

const { click, fill, press } = curryMethods(theLocatorClass);
 
chain(page.getByRole('textbox'))(
    click(),
    fill('test value in the text box'),
    press('Enter')
)

That's possible. Hard or even impossible to type for the general case, but if we make the same assumptions as for the Proxy version (no need for generics, no need to care about the return type), it's approachable.

  • Related