Transcripted Summary

In this chapter, we'll learn about Dynamic page objects and Fixtures, ways to speed up our implementation, and to create flows to be reused in our application.

We'll see how the hooks file works and how to reuse it for the Page Object Model creation, how to build the URLs dynamically so you can speed up your application tests, and lastly, how to work with Fixtures and create reusable flows.

I hope you like it.

For this chapter, in our playwright.config.ts, let's make sure we have the globalSetup and the storageSetup reverted because we are going to use the reused sign in state, as we just learned.

# Hooks file for reusable pom creation

Let's open the profile-with-dynamic-pom.spec.ts, which is under tests > ui > specs.

This file is a regular spec file with its imports, and since we are testing the profile page, we created a new variable profilePage here with the type of the page object ProfilePage.

Usually, we would have a goto and a new page object creation inside the beforeEach method.

Instead, we are doing it via hooks.beforeEach.

We are going to take a look at that in a minute.

Inside our describe test method, we are performing a checkLoggedIn that comes from the profilePage.

And of course, you could have as many scenarios or as many steps as you would like inside the test.

Just because the focus here is the dynamic page object creation, we are not really concerned about the scenarios right now.

Let's understand what hooks.beforeEach does.

You can see here that it's passing three parameters.

If we open the hooks.ts file, which is under tests > utils, you will see the imports in a function called beforeEach.

This function can be named as anything, and it has the first parameter page, which is the playwright driver.

The second parameter is the PageObjectParam that can be of type LoginPage, BookPage, or ProfilePage, which are the page objects that we have in this application. You can have as many as you want.

The third parameter targetPage is a string, and lastly, extra params, which is not required but comes in a Record<any, any>.

Inside this method, then we will finally do the page.goto and a new thing that is the buildUrl that we will understand.

We are finally creating the pageObject in this dynamic way.

So it doesn't matter necessarily what exactly you are creating, as long as you pass the right PageObjectParam, then you'll be able to create that.

And we return the pageObject to be consumed in the test file.

The benefit of using this is that if you need to create a new page object, you can replace these two lines with one single line, and it will create and go to the page automatically for you for each test.

Again, less lines of code, and of course, faster development of the tests.

Once we are back to the hooks file, we see that inside this beforeEach method we can have any function or command that is similar to more than one page object.

So, whenever you go to a page, the first step is to open it usually.

You can do that here , and right after that, you create the pageObject.

If you have more steps or more functions to be executed, we can combine them inside this method or even create other methods for different page objects.

The idea here is just to simplify our coding experience and make it faster.

# URL Builder for dynamic url creation

Speaking of buildUrl, let's see what it actually does.

Because the idea here is to create things dynamically, you need to understand the pattern of your application to implement this method.

For our demo QA, usually it's just the path of the page - for example, '/profile' - or sometimes at the book page, we do have a parameter which is similar to this one here.

So what this method does is it receives two parameters - one is a page name as a string, and the second is the params as a Record.

These will be used here as the search url parameters.

The page name will be used to build the path.

The uiPath will receive the page, which is basically the URL of the page, and if it does have any parameters, we will create it using the URLSearchParams.

Here you can see this ternary where it concats the query string parameters if it exists.

Otherwise, it will just use the uiPath.

You can see here an example of each of the variables for the books page.

In case it's profile or any other, it will just have the uiPath with the parameters empty.

In this case, the URL would be something like this - '/books`.

Then it is returning the URL to our hooks file and returning the pageObject model to our test file.

This way, we can continue with the test and call the functions whenever we need them.

Another example is the books-with-dynamic-pom.specs.ts file that can be found under the specs folder, and it does the same thing.

We create a variable bookPage with the BookPage object type.

We create the bookPage dynamically using hooks.beforeEach, and you can see here that we pass pages.bookStorePage, which is exactly the page that will be identified inside the path later on, and the test with the methods from the page object.

So, it's pretty simple to create a new test file because you already have the steps defined inside the hooks.beforeEach.

# Fixture file for reusable flows

Let's learn now about fixtures.

I consider a fixture an evolution of a hook file.

With a fixture, you can group your test based on their meanings instead of their setup.

So, in this case here, we have the file book-with-fixture-and-api.spec.ts, which is under specs.

It's pretty similar to any other tests.

The first difference we note is the import of test - instead of coming from @playwright/test, it comes from fixtures/books-fixture. We'll see that file in a minute.

We do have the first definitions here, we have a "beforeAll" that we won't look at right now. We'll see it in a minute.

Inside of the test.describe, we do have bookPage passed as a parameter.

This will mean that this test will use a fixture instead of just playwright.

We do have a method here to cleanBooks and then go to a new books page and then do something else.

We also see here a test.use with isDupe: false.

Let's take a look inside the books fixture.

Here, we can import the test from '@playwright/test' because we will need to use it.

We need to define a type that can be called MyFixture passing the bookPage page object. You could also use something else here.

Another thing is the type Duplicate that is used to store the isDupe parameter that will be passed to this fixture.

Then, you finally create a new test variable extending the base, passing the MyFixtures and Duplicate types that we just created.

This will allow this file to communicate with the test file.

I'm setting here isDupe as false, in case it's not defined by the test.

We create a function bookPage with page and with the variable that we want to pass.

Here, we finally create a new page object bookPage using our hooks file that we already learned, and we see this - await use(bookPage).

After that, we see that we have a command bookPage.addToYourCollection, and passing isDupe.

Finally, we export this expect, and then we can use it in our test, as we saw previously.

Back to the test file, the trick here is that the first thing the test will do when it sees a fixture is to call it.

So, instead of executing cleanBooks right away, it will go to the fixture and execute what is inside it, and it will execute this until it sees the method use.

As soon as it sees the method use, it will go back to the test, execute the commands, and as soon as it finishes, it goes back to the fixture and continues the flow.

So it's very important that you use it with moderation and awareness because it can get really confusing, but it's an amazing feature when you have to combine flows or scenarios within the same context.

# Review of how to use the fixture file

To help with our understanding, I created a list of steps here so we can go over them one more time.

  1. Import the fixture file instead of @playwright/tests. We can see that here on line 1.
  2. As soon as you use bookPage as a param of the test, the fixture will be called. So, right here in our test, we have bookPage, which causes the fixture to be called.
  3. In the fixture file, we'll create the Page Object Model. If we go to the fixture, the first line is to create the Page Object Model using the hooks.beforeEach.
  4. The next step in the fixture is the function use, so it goes back to the test file. Inside the fixture, we can see that right after creating the object model, we can see that use is called. That means that it will go back to the test and execute the first step.
  5. In the test file, it will execute all the commands - right after cleanBooks, it will do await bookPage.goto, as mentioned here.
  6. As the test ends, because there are no commands here, it goes back to the fixture and executes the first instruction right after the use. So, it goes back to the fixture and the next command is bookPage.addToYourCollection. We can see here isDupe is being used, and it's passing that because we were able to define it here, here, and here. Back to our test, we can see that it is also being defined here before the test itself.
  7. In the fixture file, execute bookPage.addToYourCollection, passing the param defined in the describe.

To exemplify how important this feature is, here is another example.

This is the file book-with-fixture-and-api-isolated-auth.spec.ts, which is also under specs.

We can see that we also import the fixture here.

For this test, we are not reusing the signed in state, so we are doing the sign in manually, intentionally.

Inside our test file, we define our isDupe as true, as opposed to false in the other test.

Of course, we could have this test inside the other file too, but I wanted to give you a few more examples.

In our test file, we are calling the bookPage fixture.

If we remember, the first thing it will do is create the page object and it will go back right to the test.

In this case, we are adding books and going to a page with a different parameter.

Compared to this one, we were cleaning books and going to instead of books.duplicate.

As soon as it finishes here, it goes back to the fixture and then calls addToYourCollection passing isDupe.

Finally, if we go to addToYourCollection inside book-page.ts, we'll see that isDupe is being used.

If it's true, it has a series of commands that will execute only in that case.

With that, we complete Chapter 2. Congratulations.

Good luck with the quiz below, and a friendly reminder to take a look at the links in the Resources section. I'll see you in Chapter 3. Happy testing.


© 2024 Applitools. All rights reserved. Terms and Conditions Privacy Policy GDPR