Sponsored Link
Looking for a unified way to  viewmonitor, and  debug Playwright Test Automation runs?
Try the Playwright Dashboard by today. Use the coupon code PWSOL10 for a 12 month 10% discount.

How Do You Append Query Parameters To page.goto() Using Playwright Test?

There haven't been many times where I wanted to add and validate query parameters on certain requests, but this guide be really useful if you are testing UTM or other types of tracking links that utilize URL parameters.

For this article I'll be utilizing the repo that has some automated checks for https://practicesoftwaretesting.com, big thanks to Roy de Kleijn for providing this excellent resource to test against!

GitHub - playwrightsolutions/playwright-practicesoftwaretesting.com: Example using Playwright against site https://practicesoftwaretesting.com
Example using Playwright against site https://practicesoftwaretesting.com - GitHub - playwrightsolutions/playwright-practicesoftwaretesting.com: Example using Playwright against site https://practi...

We'll start by writing this spec below which visits a contact page and fills out a form and submits, to keep things simple. The end goal I may want to track is a certain action happens when tracking via a UTM code such as a checkout process or making a payment.

// tests/contact/contact.nofixtured.spec.ts

import { test, expect } from "@playwright/test";

test("Home Page Tests", async ({ page }) => {
  await page.goto("");

  await page.getByTestId("nav-contact").click();
  await page.getByTestId("first-name").fill("Test");
  await page.getByTestId("last-name").fill("Mctester");
  await page.getByTestId("email").fill("[email protected]");
  await page.getByTestId("subject").selectOption("payments");
  await page.getByTestId("message").fill("test".repeat(40));
  await page.getByTestId("contact-submit").click();
  await expect(page.locator(".alert-success")).toHaveText(
    "Thanks for your message! We will contact you shortly."
  );
});

NOTE: The above test doesn't include any URL parameters and isn't checking for them. One thing that is interesting in exploring the site is if I go to

https://practicesoftwaretesting.com

I get redirected to

https://practicesoftwaretesting.com/#/

This is something we will need to account for when appending the URL parameters, as it will attempt to add them to the site we go to, but if re-directed the URL parameters are not preserved.

Now if you just had 1 test case to worry about you could just pass in the url parameter into the page.goto("/#/?UTM_SOURCE=playwright") like so. But for our example let's assume we want to be able to quickly add this to any number of tests for any of the pages. For this we will implement a fixture that extends the base page.

// lib/fixtures/modifiedGoto.ts

import { test as base } from "@playwright/test";

export const test = base.extend({
  page: async ({ page }, use) => {
    const goto = page.goto.bind(page);
    function modifiedGoto(url, options) {
      url += "?UTM_SOURCE=playwright";
      return goto(url, options);
    }
    page.goto = modifiedGoto;
    await use(page);
    page.goto = goto;
  },
});

I won't walk through all the details of how to create a fixture, but will walk through the logic within the fixture.

First up we create a new variable goto which is set to page.goto.bind(page). This uses the bind() function which is used to create a new function that has the this value set to the page object.

The next section of code adds a new function named modifiedGoto() which is what we will use in place of goto(). Within this fuction we take the provided URL and append  ?UTM_SOURCE=playwright to the URL string and return it.

function modifiedGoto(url, options) {
      url += "?UTM_SOURCE=playwright";
      return goto(url, options);
    }

The next line page.goto = modifiedGoto; replaces the original page.goto method with the modifiedGoto method.

We then call await use(page); which is a callback function that will "Use the fixture value in the running test".

We then call page.goto = goto; which restores the original page.goto method after the use function has been called.

All in all we now have a functional fixture that can be imported to any test and will add the URL parameters we specified within the fixture automatically. Let's test it out by creating a new contact.spec.ts which will utilize the @fixtures/modifiedGoto. Note we are making an assertion after we visit the page to validate the UTM_SOURCE is present in the url!

// tests/contact/contact.spec.ts

import { test } from "@fixtures/modifiedGoto";
import { expect } from "@playwright/test";

test("Home Page Tests", async ({ page }) => {
  await page.goto("/#/");
  expect(page.url()).toContain("?UTM_SOURCE=playwright");

  await page.getByTestId("nav-contact").click();
  await page.getByTestId("first-name").fill("Test");
  await page.getByTestId("last-name").fill("Mctester");
  await page.getByTestId("email").fill("[email protected]");
  await page.getByTestId("subject").selectOption("payments");
  await page.getByTestId("message").fill("test".repeat(40));
  await page.getByTestId("contact-submit").click();
  await expect(page.locator(".alert-success")).toHaveText(
    "Thanks for your message! We will contact you shortly."
  );
});

Inspiration from this post came from Tomaj on the Playwright Discord.

He provided his solution and I learned a "Whole New World" of how the bind method can be utilized!


Thanks for reading! If you found this helpful, reach out and let me know on LinkedIn or consider buying me a cup of coffee. If you want more content delivered to you in your inbox subscribe below, and be sure to leave a ❤️ to show some love.