Test Fixtures | Bondar Academy
Course: Playwright API Testing with TypeScript
Module: Building a Framework
Instructor: Artem Bondar
Lesson Summary
In this lesson, we explore the concept of test fixtures in the Playwright framework . A test fixture is a function that serves as a precondition or teardown for tests, offering more capabilities than traditional test hooks like before and after . Key Concepts Test Fixture: A reusable function that sets up conditions for tests. Before Hooks: Traditional hooks that require restructuring tests when different tokens are needed. Fixture Advantages: Avoids code duplication and allows for cleaner test organization. Creating a Test Fixture To create a test fixture: Create a new file named fixtures.ts in the utils folder. Import the test function from Playwright and rename it to base . Extend the base function to define your fixture. Use the async function syntax to define the fixture's behavior. Fixture Implementation In the fixture: Pass an object with dependencies as the first argument. Use the use function as the second argument to wrap test execution. Code before the use line executes as a precondition, while code after it executes afterward. Example Usage To utilize the fixture: Export the updated test function as export const test = testBase; . Import this test function in your test files instead of the default Playwright test. Call the fixture in your tests to access the configured request handler. By implementing fixtures, we achieve a structured approach to testing, akin to a three-layer architecture : the top layer for tests, the middle for fixtures, and the bottom for request handlers. That's a summary of creating and using test fixtures in Playwright! See you in the next lesson.
Video Transcript
In this lesson, I will show you another important concept about Playwright framework, which is test fixtures. So what is test fixtures in a few words? Test fixture is a function that can work as a precondition or teardown for your test. So it works similarly to before hook or after hook, but has other features and capabilities which traditional test hooks do not have. And let me show you some of the examples what I mean. So here previously in the example spec.ts, we were using before all hook. And this hook created for us a token that later used in the test. But imagine the scenario, for example, that test number 1 and number 3 need token number 1, and test number 2 and number 4 need a completely different token. What would you do in this case? In this case, you need to kind of restructure the order of your test, group them differently by the test suites. So the test that need a particular token would have in one section, and the test that need another token would be in the different section. So you will need to create two different before hooks for that. Or another example, let's say you have multiple spec files and you need to use this token creation process across multiple spec files. What would you do? Copy-paste the before hook across the test? Well, you can, but there is a better way. And the better way is fixture. So fixture is a function that behaves the same way as a before hook, but you create this function once and then call this function inside of the test, triggering the precondition creation. We're going to use fixture to do this. So here, previously, we created a new instance of request handler right inside of the test. And technically, we can code that way down the road. So we can create the test one by one and just in every single test importing this instance. But it would be much better if we would give this responsibility to a test fixture to create the setup for us, create the request handler and pass the instance of the request handler to the test. So we will not care about this instance creation. It's going to work just automatically, okay? So let's implement the test fixture refactoring our current setup. To create a test fixture, I will create a new file on the utils folder. I will call it fixtures.ts. So how to create fixture? First of all, we need to import the test function from Playwright library the same way like we did it in the test, okay? But I import this test function not as just a test function. I import it as base. So what it means that I import this function and rename it to name base to use this name later on inside of the test. I just want to use different name to not interfere with the name test. That's all pretty much the reason why I need that. And now I can call this base function and I can extend its capabilities like this. So call extend the object inside and enter. Now inside of the base extend, I can create my fixture. So let's create a first fixture with the name API and then colon and the value for this API will be a synchronous function. So I type async, then function like this, okay? So let me stop right here just for the second to explain this syntax. So the API colon and async is a value. So this is like a JSON object that here, over here. So limit 10 and offset 0, you see limit colon 10, offset 0. So in the fixtures is the same way. API is a key, async function is a value. Right now it's highlighted in red because we did not complete it yet successfully. And let's complete writing the fixtures. Inside of the fixture, we have to pass two required argument. First argument should be an object with some dependencies that you want to pass into the fixture. It can be some data, some object, something. We don't have anything right now, so we just pass an empty object. And the second argument should be a use function. So use function is a special function in the play, right? That kind of wraps test execution. So don't put too much thought into this. So just remember that the use function have to be used as a second argument of the fixture always. Now, inside of the fixture, let's implement what we want this fixture to do. So what we want, we want to create the instance of request handler inside of this fixture like this. And let me give a different name just to not conflict API and API. So it will be more meaningful for you just down the road. A request handler, something like this. Okay, the request handler is complaining because we need to create the import. Let's add the import. It will be visible like this. Okay, and once we created a new instance of the request handler, we call await use. This is our playwright function and provide as the argument, the request handler like this. And that's it, our function is ready. And let me explain quickly how the fixture works. So all code that you put before the line of code with the use method will be executed as a precondition for the test when the fixture is called. Everything what you put after the use line inside of the fixture will be executed after the test where you call this fixture, just in case you need it. We're not gonna use after step at all. So after the use, we not put anything. We have the precondition step before the test, create the instance of request handler. Now, how to use this fixture? We need to export the updated version of the test function as a new test function. So I type export const test equals to test base. So I reassign the improved test function with a test fixture back to the test object. So we stay consistent with the names of the functions, how we're using the framework, so test function. And now we need to go back to the test and we need to import this test function, but not from the playwright library from here. We need to import it from fixtures. So dot dot, I go to utils and fixture. Okay, now this test fixture is imported from the fixtures. And to call this API fixture, we call it right here, API instead of request fixture that we were using here before. I remove this line from here and I remove this import from here as well. Well, that's almost done. Let's run this and to see if it's executable. Yeah, you see it is executable, working fine. What else? We need to fix one little thing, which is IntelliSense. So currently, if I try to call API, look, API, and you see nothing works. So I put dot and I don't see which methods are available inside of the object. We need to fix this. To fix this, going back to the fixtures, we need to add a custom typing right here. So export type and let's give it a name, for example, test options. It's just an object, API of type request handler. And I add this type, type options, test options right here. All right, going back and now try it one more time. API dot and here we go. Now IntelliSense is working correctly. We see the methods available inside of API fixtures. So that's pretty much it. We created our first fixture. Let's quickly summarize what we did in this lesson. So to create a new fixture, first of all, you need to import the test function and give it a different name, for example, base. Then you need to extend this base function and provide the fixtures that you want. You can put multiple fixtures inside of this function for the base. We provide the name for the fixture, for example, API, and the value for this fixture is a synchronous function. Two arguments are required. First argument is a precondition for the function. Second is the special playwright function for the use. And then before the use block, everything what is before use, executed as a before test. Everything what is put in the function after the use, executed as after test. After that, we move the creation of the instance of our request handler into this fixture. And we export it again, our updated test function as a test function right here. Then we imported this fixture, this test from fixtures instead of the playwright. And now we can call this fixture inside of the test and call that way methods of our request handler. So currently we created kind of a three layer architecture. So think about it as an apple pie. So we have apple pie with three layers. The top layer is our test. The middle layer is our fixture. And the bottom layer is request handler that will be responsible for performing all API request operations. All right, that's it guys. Moving on and see you in the next lesson.