skip to Main Content

I want a custom Playwright command for page.goto called page.gotoDOM that automatically passes the waitUntil: 'domcontentloaded' parameter so I don’t have to code it in every test.

I have followed the instructions for fixtures and tried various combinations without success.

My code so far:

// @ts-check
const base = require('@playwright/test');
const { test, expect } = require('@playwright/test');
const describe = test.describe;

const testDOM = base.test.extend({
    pageDOM: async ({ host, page }, use) => {
    await page.goto(host, { waitUntil: 'domcontentloaded'});
    await use(page);
describe('Google', () => {
  test('Search box', async ({ page }) => {
    await page.goto('', { waitUntil: 'domcontentloaded'});
    await expect(page.getByRole('combobox', { name: 'Search' })).toBeVisible();
  testDOM('Search box using testDOM', async ({ page }) => {
    await testDOM(''); //  <-- Now I don't need to pass the waitUntil parameter
    await expect(page.getByRole('combobox', { name: 'Search' })).toBeVisible();

Right now I want to get it working in one file, ultimately available for all files once that is done.

Currently the code above is complaining that testDOM requires more arguments but so far my attempts to correct that have failed.



  1. Chosen as BEST ANSWER

    Here is a solution for just using a plain function:

    helper file

    const gotoDOM = async ({page, url, opts={}}) => {
      await page.goto(url, { waitUntil: 'domcontentloaded', opts });
    exports.gotoDOM = gotoDOM;

    test file

      const { gotoDOM } = require('../utilities');
      test('Search box using gotoDOM', async ({ page }) => {
        await gotoDOM({ page, url: '' })
          .getByRole('combobox', { name: 'Search' }))

    but I'd rather use a solution that can be used in multiple file without separate imports so as part of the test infrastructure would be nice

  2. There are many ways to do helper functions in Playwright. See Playwright: how to create a static utility method? which is highly relevant here.

    For this case, I’d keep it simple and just inline page.goto(url, {waitUntil: "domcontentloaded"}) everywhere that uses that option. This really isn’t much code and it seems premature to abstract it away–it’s clear to anyone familiar with Playwright what it does on sight, isn’t that verbose, and likely won’t require much maintenance.

    But if "domcontentloaded" is too long to look at over and over, you could make the config object a global:

    // could be in external 'constants'/config file, or at top of a single test script
    const domLoaded = {waitUntil: "domcontentloaded"};
    // use throughout your test scripts:
    await page.goto(url, domLoaded);
    await page.goto(url, {timeout: 20_000, ...domLoaded});

    Note that Playwright has an even faster predicate that’s similar to "domcontentloaded" called "commit", which is shorter to type and can be inlined easily.

    Also note that goto generally shouldn’t be needed much–I usually use one per test file in a test.beforeEach block. If you’re using goto over and over at the beginning of each test, then you can avoid the repetition easily with beforeEach. Most navigations in test cases should be initiated by a user action like a click, not goto.

    Assuming none of the above work, another option is to write a plain, boring JS function that accepts page as the first argument. This is a straightforward and unclever approach and seems like a reasonable solution for this case.


    const gotoDOM = (page, url, opts={}) =>
      page.goto(url, {...opts, waitUntil: "domcontentloaded"});
    export {gotoDOM};


    import {expect, test} from "@playwright/test"; // ^1.42.1
    import {gotoDOM} from "./util";
    test("gotoDOM works", async ({page}) => {
      await gotoDOM(page, "");
      await expect(page.getByText("Example Domain")).toBeVisible();

    This lets you pass in an optional options argument like {timeout: 20_000} as the last argument, just as in normal Playwright. When writing abstractions, I like to try to stick to the design of the library I’m using so it’s less surprising, so I would not want a signature like await gotoDOM({ page, url: '' }) as shown in this other answer, especially if I need to pass an options object through my wrapper into Puppeteer’s API.

    If you don’t like the page argument, you can use a closure pattern, discussed at length in the linked answer:


    const helpersFor = page => {
      return {
        gotoDOM(url, opts={}) {
          return page.goto(url, {...opts, waitUntil: "domcontentloaded"});
        // ... more helpers ...
    export default helpersFor;


    import {expect, test} from "@playwright/test";
    import helpersFor from "./util";
    let gotoDOM;
    test.beforeEach(({page}) => {
      ({gotoDOM} = helpersFor(page));
    test("gotoDOM works", async ({page}) => {
      await gotoDOM("");
      await expect(page.getByText("Example Domain")).toBeVisible();

    If you only have one helper, this isn’t that exciting, but it can be useful once you have a handful of them since you generally only use one page per test.

    By the way, the base.test.extend pattern in OP’s attempt seems best for POMs that you want to inject into each test block, but is overkill for a simple helper function like this.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top