# Series: Playwright Getting Started (13 Parts) — Luca Del Puppo > Complete 13-part technical series "Playwright Getting Started" by Luca Del Puppo (https://delpuppo.net/blog/series/playwright/). Ordered chronologically from Part 1 to Part 13. - **Canonical Series URL**: https://delpuppo.net/blog/series/playwright/ - **Hashnode Series Mirror**: https://blog.delpuppo.net/series/playwright - **Blog Index LLMs.txt**: https://delpuppo.net/blog/llms.txt ## Series Curriculum 1. [Part 1: Playwright - Let's start](https://delpuppo.net/blog/playwright/playwright-lets-start/) (Dec 7, 2022) — In the last period, in the frontend ecosystem, a new e2e tool has come up and it has started to... ([Article LLMs.txt](https://delpuppo.net/blog/playwright/playwright-lets-start/llms.txt)) 2. [Part 2: Playwright - How to write tests](https://delpuppo.net/blog/playwright/playwright-how-to-write-tests/) (Dec 14, 2022) — What are you going to learn in this post? In this post, you will learn how to write tests... ([Article LLMs.txt](https://delpuppo.net/blog/playwright/playwright-how-to-write-tests/llms.txt)) 3. [Part 3: Playwright - debug your tests](https://delpuppo.net/blog/playwright/playwright-debug-your-tests/) (Dec 21, 2022) — Debug your tests One of the most popular things done by developers is to debug the code... ([Article LLMs.txt](https://delpuppo.net/blog/playwright/playwright-debug-your-tests/llms.txt)) 4. [Part 4: Playwright - Generate tests' code](https://delpuppo.net/blog/playwright/playwright-generate-tests-code/) (Dec 29, 2022) — Hi there, today you will learn how to generate the code for your tests. First of all, you have to... ([Article LLMs.txt](https://delpuppo.net/blog/playwright/playwright-generate-tests-code/llms.txt)) 5. [Part 5: Playwright - Fixtures](https://delpuppo.net/blog/playwright/playwright-fixtures/) (Jan 5, 2023) — Hey there, Today I want to speak about Fixtures, a vital friend if you're going to work with... ([Article LLMs.txt](https://delpuppo.net/blog/playwright/playwright-fixtures/llms.txt)) 6. [Part 6: Playwright - Components testing](https://delpuppo.net/blog/playwright/playwright-components-testing/) (Jan 12, 2023) — Hi there, Today I'll speak about a beta feature in Playwright: Components. The team is building a... ([Article LLMs.txt](https://delpuppo.net/blog/playwright/playwright-components-testing/llms.txt)) 7. [Part 7: Playwright - CI](https://delpuppo.net/blog/playwright/playwright-ci/) (Jan 19, 2023) — Hi there, Today I want to speak about integrating Playwright in your GitHub action CI. So don't waste... ([Article LLMs.txt](https://delpuppo.net/blog/playwright/playwright-ci/llms.txt)) 8. [Part 8: Playwright - Mock API](https://delpuppo.net/blog/playwright/playwright-mock-api/) (Jan 26, 2023) — Hi there, Today I want to talk about how to mock API with Playwright. To do that, I add a new... ([Article LLMs.txt](https://delpuppo.net/blog/playwright/playwright-mock-api/llms.txt)) 9. [Part 9: Playwright - Visual Comparisons](https://delpuppo.net/blog/playwright/playwright-visual-comparisons/) (Feb 2, 2023) — Hi there, Today I want to speak about Visual comparisons with Playwright. In some projects is... ([Article LLMs.txt](https://delpuppo.net/blog/playwright/playwright-visual-comparisons/llms.txt)) 10. [Part 10: Playwright - Parallelism and sharding](https://delpuppo.net/blog/playwright/playwright-parallelism-and-sharding/) (Feb 9, 2023) — Hey Folks, Today I'll speak about parallelism and sharding with Playwright, so don't waste time and... ([Article LLMs.txt](https://delpuppo.net/blog/playwright/playwright-parallelism-and-sharding/llms.txt)) 11. [Part 11: Playwright - Not just for Frontend](https://delpuppo.net/blog/playwright/playwright-not-just-for-frontend/) (Feb 16, 2023) — In this path with Playwright we spent a lot of time speaking about e2e tests and component testing in... ([Article LLMs.txt](https://delpuppo.net/blog/playwright/playwright-not-just-for-frontend/llms.txt)) 12. [Part 12: Playwright - Parametrize tests](https://delpuppo.net/blog/playwright/playwright-parametrize-tests/) (Feb 23, 2023) — Hey folks, Today it's time to speak about how to parametrize tests with Playwright. There are cases... ([Article LLMs.txt](https://delpuppo.net/blog/playwright/playwright-parametrize-tests/llms.txt)) 13. [Part 13: Playwright - UI Mode](https://delpuppo.net/blog/playwright/playwright-ui-mode/) (Apr 6, 2023) — Hi folks, In release 1.32, Playwright released a new cool feature for us, the UI Mode! This feature... ([Article LLMs.txt](https://delpuppo.net/blog/playwright/playwright-ui-mode/llms.txt)) --- ## Full Series Content ### Part 1 of 13: Playwright - Let's start - **URL**: https://delpuppo.net/blog/playwright/playwright-lets-start/ - **Published**: Dec 7, 2022 - **Tags**: playwright, e2e In the last period, in the frontend ecosystem, a new e2e tool has come up and it has started to become famous; its name is Playwright. In this short series, I want to leave you with some information on this and tips on how to get started. ## What is it? Let's start with what Playwright is. Playwright is a testing framework to handle e2e testing. With Playwright we can write e2e tests and run them in different browsers, like Chrome, Firefox and Webkit. We can run our tests on many platforms like Windows, Mac, Linux or CI, and we can write tests in Typescript, Javascript, Java, Phyton or .Net. If you open the official [website](https://playwright.dev/), you can find these characteristics: - Any browser Any platform One API - Resilient No flaky tests - No trade-offs No limits - Full isolation Fast execution - Powerful Tooling In my opinion, the tool reflects all of these characteristics. ## TicTacToe application I want to use a TicTacToe application built with vite + react in this series. And you can find it [in the GitHub repository](https://github.com/Puppo/playwright-series/tree/00-the-app). ![Tic Tac Toe App](https://cdn.hashnode.com/res/hashnode/image/upload/v1670327417970/XSyzIlhd2.gif) As you can see, the application is straightforward, but with it, we can start to move to the first steps with Playwright. ## Install Playwright The first step to use Playwright is installing it, that is easy. Open your terminal and type `npm init playwright@latest` (I use npm but if you want to use yarn or pnpm read the [docs](https://playwright.dev/docs/intro#installing-playwright)). Ok, now the installation asks you some questions: 1. Where to put your end-to-end tests? The default option is `tests` but I prefer to use the folder `e2e`. 2. Add a GitHub Actions workflow? The default option is `false`; in this demo, I leave this option to false for now. 3. Install Playwright browsers (can be done manually via 'npx playwright install')? The default option is `true`, and I used this value and now you have to wait until the installation ends. When the installation is completed... congratulation, you are ready to get your hands dirty with Playwright! ## First Look The installation created in your repository 3 new files: - playwright.config.ts - e2e/example.spec.ts - tests-examples/demo-todo-app.spec.ts Let's start with the last one, the easiest 😊 This file contains a list of examples of how you can write tests with Playwright. It uses an application created by the playwright team (a simple to-do app) and illustrates how you can interact with your page. Carrying on with the second one. This file is a quick example of some tests on the playwright website, but it's here only as a placeholder for your tests. And now, the first one, the most important. Playwright uses the `playwright.config.ts` file to understand its configuration. Inside it, you can find configurations about the test folder, the timeout used by the expect function, the configurations for the browsers used by Playwright to run the tests, and more. ## Run your first test It's time to see Playwright in action, so open your terminal again and run the following command `npx playwright test`. The result is something like this ![Playwright Result](https://cdn.hashnode.com/res/hashnode/image/upload/v1670322747775/m88LhTkFj.png) Playwright in this report shows you the result of its execution, and you can check if everything has gone ok. The report shows the result for each browser indicated in the configuration, in this case: Chromium, Firefox and Webkit. ## Write your first test Ok cool, but now it's time for you to write your first test with Playwright. Before doing this, you have to configure Playwright to be able to run your application before starting with the tests. To do that, you have to open the `playwright.config.ts` and add the following configuration ```ts const config: PlaywrightTestConfig = { ... webServer: { command: "npm run dev", port: 5173, }, ... }; ``` _N.B. check your port running the command_ `npm run dev` you have to use the same port exposed by vite. And now it's time for your first test. Open the file `e2e/example.spec.ts` and rename it to `e2e/tit-tac-toe.spec.ts`. Then clean everything in the file and leave only the import line `import { expect, test } from "@playwright/test";`. Now add your first test ```ts test("show tic tac toe page", async ({ page }) => { await page.goto("/"); await expect(page).toHaveTitle("Tic Tac Toe"); }); ``` ok, let's see this test together. First of all, you can indicate to Playwright that it is a test using the `test` function. As in every testing framework, the test function's first param is the test's name. The second param is the function to test our application. Using Playwright, this function is always an async function because your tests in these cases are always asynchronous. The first command in this example is a command to say to Playwright to navigate to the home page of our application. As you can see, already this command is async. Now, if the test can navigate to this page, you want to test if the title of the page is "Tic Tac Toe". Yes, it's elementary, but an excellent example of moving your first step. Now, it's time to check if the test runs in Playwright, but before doing that, you can simplify the interaction with Playwright by adding a new script in the package.json, as shown here ```json .... "scripts": { ... "e2e": "playwright test" }, ``` now in your terminal you can type `npm run e2e` and voila, your test is gone Adding another script in your package.json, you can open the report result in your browser ```json .... "scripts": { ... "e2e": "playwright test", "e2e:report": "playwright show-report" }, ``` And now, if you run the command `npm run e2e:report` in your browser, you can see the result of your first test. ![Playwright Result first test](https://cdn.hashnode.com/res/hashnode/image/upload/v1670326776785/mfjxqkmWg.png) Ok, I think that's all for now. In this post, you learnt: - How to install Playwright - How to run your test - How to write your first test with Playwright In the next post, we will continue our journey in the Playwright world, but for now, that's all! See you soon, folks. Bye Bye _p.s. You can find the result of this post at this_ [in the GitHub repository](https://github.com/Puppo/playwright-series/tree/01-install-and-run) > 🎬 **Watch / View Embedded Resource:** [https://dev.to/puppo](https://dev.to/puppo) --- ### Part 2 of 13: Playwright - How to write tests - **URL**: https://delpuppo.net/blog/playwright/playwright-how-to-write-tests/ - **Published**: Dec 14, 2022 - **Tags**: playwright, e2e ## What are you going to learn in this post? In this post, you will learn how to write tests with Playwright, using assertions, locators and something else. ## Write tests Let's start with an example ```ts test('the first player must be the "X" player', async ({ page }) => { await page.goto("/"); const playerParagraph = await page.getByRole("paragraph"); await expect(playerParagraph).toContainText("X"); }); ``` In this example, I introduced two new stuff, `getByRole` and `toContainText`. `getByRole` is part of the _locators_ and `toContainText` is part of the _assertions_, and now we will dive into these new things. ### Locators Locators are the central piece of Playwright's auto-waiting and retry-ability. Locators represent a way to find element(s) on the page at any moment. Using locators, you can retrieve elements in your dom by these commands: - **page.getByRole(role[, options])** to locate by explicit and implicit accessibility attributes. - **page.getByText(text[, options])** to locate by text content. - **page.getByLabel(text[, options])** to locate a form control by associated label's text. - **page.getByPlaceholder(text[, options])** to locate an input by placeholder. - **page.getByAltText(text[, options])** to locate an element, usually image, by its text alternative. - **page.getByTitle(text[, options])** to locate an element by its title attribute. - **page.getByTestId(testId)** to locate an element based on its `data-testid` attribute (other attributes can be configured). - **page.locator(selector)** to locate an element base on its selected (Css or XPath) As you can see, using these eight commands, you can find all the elements on your pages. Watching the previous example, you can view how using the `getByRole` method you can get the paragraph in your DOM and then check whatever you want. Okay, so a quick recap of what locators are: locators are methods that help you to search the elements in the DOM. ### Assertions Like any test framework that is respected, Playwright has its assertions. Assertions are used to check our results, and as developers, we use them to be sure the application or the code runs as expected. Playwright for this feature uses under the hood the [expect](https://jestjs.io/docs/expect) function built by jest. So if you are familiar with [jest](https://jestjs.io/), you won't have problems with Playwright. The most famous assertions that you can use are: `toEqual`, `toContain`, `toMatch`, `toBe` and many more. The assertions are your best friends to check if your application runs as expected. As you can imagine, the combination of these two things permits you to build many tests to check the behaviours of your application. So if you go back to the previous example and type `npm run e2e` in your terminal, you get the following result. ```bash npm run e2e > react-e2e@0.0.0 e2e > playwright test Running 6 tests using 4 workers 6 passed (5s) ``` Wow, it works. 😀 And now you know what to use to create your tests with Playwright. ## Isolate your tests If you are familiar with testing frameworks, you probably know there is a way to group a test list. To do that with Playwright, you have to use the `describe` method exposed by the `test` object, as in the following example. ```ts import { expect, test } from "@playwright/test"; test.describe("On View", () => { test("show tic tac toe page", async ({ page }) => { await page.goto("/"); await expect(page).toHaveTitle("Tic Tac Toe"); }); test('the first player must be the "X" player', async ({ page }) => { await page.goto("/"); const playerParagraph = await page.getByRole("paragraph"); await expect(playerParagraph).toContainText("X"); }); }); ``` The benefit of grouping is also visible in the report that now appears so. ![Playwright Report](https://cdn.hashnode.com/res/hashnode/image/upload/v1670349578285/yrtmYSHW4.png) As you can see, the label `On View` is present before each test. So now, you can split your test file into different contexts if you have this need. ## Hooks Another critical feature if you love working with test frameworks is hooks. Hooks allow you to run a piece of code before or after the test execution. Typically they are called: `beforeEach` `beforeAll` `afterEach` `afterAll` and you know what they are called in Playwright? Precisely in the same way 🚀 So, to give an example, you can refactor the code in this way to navigate the home page before each test. ```ts import { expect, test } from "@playwright/test"; test.describe("On View", () => { test.beforeEach(async ({ page }) => { await page.goto("/"); }); test("show tic tac toe page", async ({ page }) => { await expect(page).toHaveTitle("Tic Tac Toe"); }); test('the first player must be the "X" player', async ({ page }) => { const playerParagraph = await page.getByRole("paragraph"); await expect(playerParagraph).toContainText("X"); }); }); ``` The result is the same as before, but now you only have the navigation command inside the `beforeEach` hook. These hooks are great if you have to mock API or if you have to do something before or after tests. ## Play with your DOM After all these things, it's time to write the last test of this post. Now you will write a test that simulates the user's behaviours in the applications. The test simulates the click of the player "X" in the top left square and then checks if the next player is the "O". ```ts test.describe("Users behaviours", () => { test.beforeEach(async ({ page }) => { await page.goto("/"); }); test('the first player select the top-left square and then the next player is the "O"', async ({ page, }) => { const squares = await page.getByRole("button"); const topLeftSquare = squares.first(); await topLeftSquare.click(); const topLeftSquareImg = await topLeftSquare.getByRole("img"); await expect(topLeftSquareImg).toHaveAttribute("title", "X"); await expect(topLeftSquareImg).toHaveAttribute("src", /x.png/i); const playerParagraph = await page.getByRole("paragraph"); await expect(playerParagraph).toContainText("O"); }); }); ``` This example starts to become trickier. As you can notice, there are some new things. Let's start with the first one, the `first` method. In this case, the `getByRole` returns many elements, and only the first is required in the test. Using the `first` method, you can get the first element in the list. Then when you have the first square on the board, you can simulate the click of this element by the user. Using the `click` method, you can do that. Easy peasy lemon squeezy. But now things start to become "complex". As you can see, now you can use the `getByRole` from the square element, and the method tries to find an image child of this element. After that, using `toHaveAttribute` assertion, you check if this image has a title with the "X" value and if the source contains the value "x.png". The final step is the same as in the previous post, where you get the paragraph and check if the next player is now the "O" player. Ok, it's not rocket science but a bit more complicated than the previous example. ## Conclusion Ok, I think you have materials to study for now! 😃 So, what you have learned in this post: - Locators - Assertions - How to isolate your test - Hooks I suggest you get your hand dirty with these topics because they will be the fundamentals of your test with Playwright. Ok, that's all folk, see you soon. Bye Bye 👋 _P.s. you can find the result of this post at this_ [in the GitHub repository](https://github.com/Puppo/playwright-series/tree/02-how-to-write-tests) > 🎬 **Watch / View Embedded Resource:** [https://dev.to/puppo](https://dev.to/puppo) --- ### Part 3 of 13: Playwright - debug your tests - **URL**: https://delpuppo.net/blog/playwright/playwright-debug-your-tests/ - **Published**: Dec 21, 2022 - **Tags**: playwright, e2e, vscode, extensions ## Debug your tests One of the most popular things done by developers is to debug the code using `console.log`. But today I want to show you how to debug your Playwright tests. Let's start with the Playwright Inspector. Using the option `--debug` when you run the tests, Playwright opens two new windows in your display, a browser and the Playwright Inspector. To try this, you can type in your terminal this command `npx playwright test --debug`, the result seems like this ![Playwright Inspector](https://cdn.hashnode.com/res/hashnode/image/upload/v1670410960054/mKShQ1M4a.gif) You can use the commands in the inspector to move forward in the test and check the stuff. Cool, but I prefer debugging the tests directly from my editor (VsCode). To do that, you have to install the official extension called [Playwright Test for VSCode](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright). The extension permits you to install Playwright in your projects or add new browsers configuration, but today the goal is the debug feature. Now, in VsCode if you open the Test Explorer, you can see all your tests in the projects and run them or debug them directly from your editor. Here is an example. ![VsCode Test Explorer](https://cdn.hashnode.com/res/hashnode/image/upload/v1670413453990/Es3Xapjj_.png) Ok, now it's time to see how you can run or debug your test directly from VsCode. First of all, using the command in the top right of the Test Explorer panel, you can decide between running or debugging all the tests in your suite. Clicking on the arrow on the left of these buttons, you can also decide in which browser you want to run your tests. Here is a short demo! ![Illustration for Playwright - debug your tests](https://cdn.hashnode.com/res/hashnode/image/upload/v1670414310565/e0RQ7kmhL.gif) In this example, you can see how the runner works, but now it's time to see how it's possible to debug tests. Let's start adding a break-point in the code base. If you are not familiar with this in VsCode, it's elementary, click with the mouse on the left of the line where you want the debugger stops its execution. > 🎬 **Watch / View Embedded Resource:** [https://youtu.be/QIryVOtMXvM](https://youtu.be/QIryVOtMXvM) Now, it's time to debug the test. We will see two methods to debug the tests; the first runs all the tests, and the second runs only the necessary test. **Debug all the tests** Using the icon in the top-right of the test explorer, you can run all your tests in debug mode, and for each test with a break-point, VsCode can stop the execution and permits you to check the test's status. Here is a video example of that. > 🎬 **Watch / View Embedded Resource:** [https://youtu.be/F5L-qpg3UBA](https://youtu.be/F5L-qpg3UBA) That is a good example, but typically, you want to debug one test per time. **Debug only needed test** To do that, the flow is very similar, you have to choose which test you want to debug and insert a break-point where you want the execution will stop. Then in Test Explorer, you have to search for your test; you can type the name of your test in the search bar, or you can scroll through the three and find it using the mouse. To run your test now, you must press the icon relative to the debug execution on the right of the test name. Doing that, VsCode will run your test and will stop the execution when the code arrives at the break-point. Here, you can check your application's status and the problems in your test. Using the command in the top-right of the VsCode's window, you can move on in the execution. > 🎬 **Watch / View Embedded Resource:** [https://youtu.be/68mH0VsjV4M](https://youtu.be/68mH0VsjV4M) As you can see, if you are playing with the Test Explorer, you can run or debug all the tests or a specific one. The same approach exists for groups of tests too, so you can run or debug a bunch of tests in the same `describe`. Ok, I spent an entire article showing you how you can run or debug tests using VsCode because many times I see people that don't know how to debug, and they spend a lot of time finding the problem and fixing the test. Now you have all the tools to debug your tests with Playwright, so you can check and fix tests quickly. That's it for today; you have learnt how to run and debug tests with Playwright; before using the Playwright inspector and then directly from VsCode. I hope you enjoy this content, and if you have any questions, don't hesitate to reach out to me. See you soon, folk Bye bye > 🎬 **Watch / View Embedded Resource:** [https://dev.to/puppo](https://dev.to/puppo) --- ### Part 4 of 13: Playwright - Generate tests' code - **URL**: https://delpuppo.net/blog/playwright/playwright-generate-tests-code/ - **Published**: Dec 29, 2022 - **Tags**: playwright, e2e Hi there, today you will learn how to generate the code for your tests. First of all, you have to run the application, so type in your terminal `npm run dev` , and you will have your application up and running at the address `http://localhost:5173`. Now you have to run the Playwright code generator. To do that, you have to open another tab in your terminal and type `npx playwright codegen`. This command opens in your display a new browser and the Playwright inspector. Now, the browser shows an empty tab, and the Playwright inspector is on a new page, ready to listen to what you will do in the browser. If you go into the browser's address bar and type `http://localhost:5173,` you can notice that something has changed in the Playwright Inspector. The inspector noted the page change and added this step to the test body. Now, you can click on one square and simulate a game between two players, you can notice that the inspector records all these steps and creates the body of the test for you. The result of these actions is this ```ts test('test', async ({ page }) => { await page.goto('http://localhost:5173/'); await page.locator('._Square_pba4r_1').first().click(); await page.locator('button:nth-child(5)').click(); await page.locator('button:nth-child(6)').click(); await page.locator('button:nth-child(7)').click(); await page.locator('button:nth-child(3)').click(); await page.locator('button:nth-child(9)').click(); await page.locator('button:nth-child(2)').click(); }); ``` As you can see, the generated code is good but not perfect, so please use this tool with the head on the shoulders and check the result every time to ensure your test's value. Say that, now it's time to copy and paste the code into our test file and improve its content. Let's start by changing the test name from `test` to `should win the player "X"` and changing the goto value from `http://localhost:5173/` to `/` because our base root corresponds to the home page. Then refactoring the first click probably is a good refactor to improve the test, so you have to change the line from `await page.locator('._Square_pba4r_1').first().click();` to `await page.locator("button:nth-child(1)").click();` and last but not least, add the assertion. In this case, if the test name is `should win the player "X"` the best assertion is something like this ```ts const winnerParagraph = await page.getByText(/winner/i); await expect(winnerParagraph).toContainText("X"); ``` So the code tries to get the paragraph with the winner text and checks if the value contains the `X` value. The final result is this ```ts test('should win the player "X"', async ({ page }) => { await page.goto("/"); await page.locator("button:nth-child(1)").click(); await page.locator("button:nth-child(5)").click(); await page.locator("button:nth-child(6)").click(); await page.locator("button:nth-child(7)").click(); await page.locator("button:nth-child(3)").click(); await page.locator("button:nth-child(9)").click(); await page.locator("button:nth-child(2)").click(); const winnerParagraph = await page.getByText(/winner/i); await expect(winnerParagraph).toContainText("X"); }); ``` Now, you can run the test and check the result which should be green. Ok, I think that's all from the code generation. In this article, you learnt how to record your steps and convert them to code for your tests. Please, as I said before, use this tool responsibly and check the generated code every time. That's it, folk. See you soon 👋 p.s. you can find the result of this article [in the GitHub repository](https://github.com/Puppo/playwright-series/tree/04-code-generator) > 🎬 **Watch / View Embedded Resource:** [https://dev.to/puppo](https://dev.to/puppo) --- ### Part 5 of 13: Playwright - Fixtures - **URL**: https://delpuppo.net/blog/playwright/playwright-fixtures/ - **Published**: Jan 5, 2023 - **Tags**: playwright, e2e Hey there, Today I want to speak about Fixtures, a vital friend if you're going to work with Playwright and you want to share data between your tests or create custom executions. Let's start with a simple example. ```ts test('should win the player "X"', async ({ page }) => { await page.goto("/"); await page.locator("button:nth-child(1)").click(); await page.locator("button:nth-child(5)").click(); await page.locator("button:nth-child(6)").click(); await page.locator("button:nth-child(7)").click(); await page.locator("button:nth-child(3)").click(); await page.locator("button:nth-child(9)").click(); await page.locator("button:nth-child(2)").click(); const winnerParagraph = await page.getByText(/winner/i); await expect(winnerParagraph).toContainText("X"); }); ``` Imagine you want to extract all the indexes of the buttons in an array to replicate this test and reduce all these click commands to have a shorter test. So, the first refactor before introducing the fixture feature could be this. ```ts test('should win the player "X"', async ({ page }) => { const playerXWinMoves = [1, 5, 6, 7, 3, 9, 2]; for (const move of playerXWinMoves) { await page.locator(`button:nth-child(${move})`).click(); } const winnerParagraph = await page.getByText(/winner/i); await expect(winnerParagraph).toContainText("X"); }); ``` As you can notice, all the steps to reproduce the players' moves are contained in the `playerXWinMoves` variables. The test is concise, but most important, all the actions are in a single variable. Now it's time to introduce fixtures. Before carrying on, let me spend some words describing what fixtures are. ## What fixtures are Test fixtures are used to establish an environment for each test, giving the test everything it needs and nothing else. Test fixtures are isolated between tests. With fixtures, you can group tests based on their meaning instead of their standard setup. After this definition, you can think fixtures could be replaced by before/after hooks, but fixtures have several advantages over these hooks: - Fixtures **encapsulate** setup and teardown in the same place, making it easier to write. - Fixtures are **reusable** between test files - you can define them once and use them in all your tests. That's how Playwright's built-in `page` fixture works. - Fixtures are **on-demand** - you can define as many fixtures as you'd like, and Playwright Test will set up only the ones needed by your test and nothing else. - Fixtures are **composable** - they can depend on each other to provide complex behaviours. - Fixtures are **flexible**. Tests can use any combination of fixtures to tailor the precise environment they need without affecting other tests. - Fixtures simplify **grouping**. You no longer need to wrap tests in `describe`s that set up an environment and are free to group your tests by their meaning instead. Perfect, after this boring theoric part, it's time to get your hands dirty with fixtures in Playwright. To start the explanation, I want to begin with the final result. ```ts import { expect, test as base } from "@playwright/test"; type TestFixtures = { playerXWinMoves: [number, number, number, number, number, number, number]; }; const test = base.extend({ playerXWinMoves: async ({}, use) => { await use([1, 5, 6, 7, 3, 9, 2]); }, }); test('should win the player "X"', async ({ page, playerXWinMoves }) => { for (const move of playerXWinMoves) { await page.locator(`button:nth-child(${move})`).click(); } const winnerParagraph = await page.getByText(/winner/i); await expect(winnerParagraph).toContainText("X"); }); ``` As you can see, to build our fixture, you have to extend the test object exposed by Playwright. Inside this function, you can create an object that represents your fixtures. In this case, I added the `playerXWinMoves` property to share the steps to reproduce the Player X win. In addition, just because I love working with Typescript, I added the `TestFixtures` type to describe my fixtures. It's essential to notice that inside the fixture, you must use the `use` method to set the value for your fixtures. Ok, carry on and let's take a look at the test now. You can notice that you can get the `playerXWinMoves` fixture directly from the object passed to your test. And now, if you remove the previous variable and use the new one from the params, you will create your first test with Playwright using fixture. Great, I hope you can understand the incredible power of this feature and how it can help you to make your test more readable or to bring together some data useful in many tests. But before closing, let me leave you another bit. With fixtures, you can also override an object exposed directly by Playwright. For instance, you can override the page object, and instead of creating a `beforeEach` hook to navigate to the home page, you can override the page and add this step in a fixture, so every test, before its execution, runs the steps to navigate to the home page. And how can we do that? Simply in this way. ```ts const test = base.extend({ page: async ({ baseURL, page }, use) => { baseURL && (await page.goto(baseURL)); await use(page); }, }); ``` Doing that, now each test, when it starts its execution, goes to the baseURL and then runs all the code. Incredible no? Now, you can remove the `beforeEach` hook in the test file and rerun your tests to check the result. Ok, that's all folk! Today you have learned how Playwright fixtures work and how to build one. I hope you enjoyed this post, and if you have any doubts, don't hesitate to reach out to me; I will be happy to help you. See you soon Bye Bye 👋 _p.s. you can check out the code of this article_ [in the GitHub repository](https://github.com/Puppo/playwright-series/tree/05-fixture) > 🎬 **Watch / View Embedded Resource:** [https://dev.to/puppo](https://dev.to/puppo) --- ### Part 6 of 13: Playwright - Components testing - **URL**: https://delpuppo.net/blog/playwright/playwright-components-testing/ - **Published**: Jan 12, 2023 - **Tags**: playwright, components, testing Hi there, Today I'll speak about a beta feature in Playwright: Components. The team is building a new feature to write component tests with Playwright, so it's time to see how it works. ## Installation To start to use this feature, you have to install it. To do that, type in your terminal ```sh npm init playwright@latest -- --ct ``` The command asks you two questions: - **Which framework do you use? (experimental)** choose to _react_ to this tutorial - Install Playwright browsers choose to _true_ When the installation ends, there will be a new file in your project called: `playwright-ct.config.ts`. This file contains the configuration for the components' testing. For this tutorial, you have to change only one configuration, `testDir` property from `./` to `./src`. Playwright uses this configuration to understand where the files used for the components' tests are located. In this example, you'll put the test file in the same folder as the component. ## First Test Let's start with the first component test. Create a file called `Icon.spec.tsx` in the folder `src/components/Icon/`, and inside of it, copy this code. ```ts import { expect, test } from '@playwright/experimental-ct-react'; import Icon from './Icon'; test.use({ viewport: { width: 500, height: 500 } }); const props = { src: 'src', title: 'title', } test('should work', async ({ mount }) => { const component = await mount(); await expect(component).toHaveAttribute('src', 'src'); await expect(component).toHaveAttribute('title', props.title); }); ``` As you can see, the structure is similar to other Testing Frameworks. You have to import the methods relative to react components from the path `@playwright/experimental-ct-react` and import your component. Then you can set up the viewport for your test; it's important to understand that the tests run in different browsers, so you can also test your components in different viewports. And at the end, it's time to check your component. The test structure is straightforward; using the `mount` method, you can render the component in the DOM, and then you can check the result using the Playwright APIs. For instance, in this case, the test checks if the `src` and `title` attributes have the correct value. Using these tests, you can check the proper logic or the right representation of your components and guarantee the right render in different browsers. ## Run the test Cool, you set up Playwright, and you wrote the tests. Now it's time to run it. Easy peasy lemon squeezy, type in your terminal the following command ```sh npx playwright test -c playwright-ct.config.ts ``` and then ```sh npx playwright show-report ``` the result is like this. ![Playwright result](https://cdn.hashnode.com/res/hashnode/image/upload/v1672655574436/4792472c-0aec-4b1a-8dc6-a64ce583ef18.png) As you can notice, Playwright runs the test in three different browsers by default. You can decide where to run the tests, adding or removing the browsers from the configuration file. Perfect! I think you have a good introduction to component testing with Playwright. The feature is in experimental mode, so use it carefully, please. You can find out more info in the official [documentation](https://playwright.dev/docs/test-components) if you want to dive into it. I hope you enjoyed this content. See you soon folk. Bye Bye 👋 _You can find the code of this post_ [in the GitHub repository](https://github.com/Puppo/playwright-series/tree/06-components) > 🎬 **Watch / View Embedded Resource:** [https://dev.to/puppo](https://dev.to/puppo) --- ### Part 7 of 13: Playwright - CI - **URL**: https://delpuppo.net/blog/playwright/playwright-ci/ - **Published**: Jan 19, 2023 - **Tags**: playwright, testing, github, githubaction Hi there, Today I want to speak about integrating Playwright in your GitHub action CI. So don't waste time and let's start. To start, you have to create a file called `playwright.yml` in this path `.github/workflows`. > The `.github/workflows`' path is used to save the files for your GitHub Actions, and the name of the file is not important for GitHub because the value for it is inside of your files. Let's start to copy and paste this code into the playwright.yml ```yaml name: Playwright Tests on: [push] ``` These two info are used to give a name to the CI and to define what triggers this CI. These are standard fields that you can find in every GitHub Action in the world. In this case, the CI will trigger every push in the project. Now it's time to add the first job to run your e2e tests in CI. To do that, add this to your file. ```yaml jobs: test-e2e: timeout-minutes: 60 runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 16 - name: Install dependencies run: npm ci - name: Install Playwright Browsers run: npx playwright install --with-deps - name: Run Playwright tests run: npx playwright test - uses: actions/upload-artifact@v3 if: always() with: name: playwright-e2e-report path: playwright-report/ retention-days: 30 ``` Jobs are used in GitHub Action to run different stuff, like build, test, deploy etc. etc. In this example, the `test-e2e`'s job creates a list of steps to run the test in the CI. But what this job does: 1. `runs-on` is used to indicate on which platform the job has to run 2. `actions/checkout@v3` checkouts the code 3. `actions/setup-node@v3` setups the node version for the job 4. `Install dependencies` installs all the dependencies in the `node_modules` 5. `Install Playwright Browsers` installs the browsers for Playwright 6. `Run Playwright tests` runs the e2e tests 7. `actions/upload-artifact@v3` uploads the test's results in the GitHub Artifact Ok, this is the job for the e2e tests; now it's time to add the job for the component tests. To do that, copy and paste the following code ```yaml test-ct: timeout-minutes: 60 runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 16 - name: Install dependencies run: npm ci - name: Install Playwright Browsers run: npx playwright install --with-deps - name: Run Playwright tests run: npx playwright test -c playwright-ct.config.ts - uses: actions/upload-artifact@v3 if: always() with: name: playwright-ct-report path: playwright-report/ retention-days: 30 ``` As you can see, it's similar to the previous job but has different configurations, so I don't dive into it. Now with these two jobs, you have a CI ready to run. To do that, `git add` + `git commit` + `git push`, and go into your online GitHub repository. In the tab Actions, you can find your CI in action, and If you click on it, you can see the two jobs running. (It's important to understand that these two jobs run in parallel, you can find more info about job execution [in the GitHub repository](https://docs.github.com/en/actions/using-jobs/using-jobs-in-a-workflow)) ![CI on action](https://cdn.hashnode.com/res/hashnode/image/upload/v1672660069370/bb711c0d-c1e3-46be-aff9-c91a0748f2e1.png) When the CI is ended, you are sure that your tests are ok as expected. And the result will be like this. ![CI ended](https://cdn.hashnode.com/res/hashnode/image/upload/v1672660231591/f0f1a420-23ec-4d5f-a50e-6ffd23df1582.png) Perfect, now you have your first GitHub action configured to run your tests with Playwright. Good, that's all folk, I think you have all the info to integrate Playwright in your CI without any problem. Thank you and see you soon. Bye Bye 👋 _The code of this post is available_ [in the GitHub repository](https://github.com/Puppo/playwright-series/tree/07-ci)_._ > 🎬 **Watch / View Embedded Resource:** [https://dev.to/puppo](https://dev.to/puppo) --- ### Part 8 of 13: Playwright - Mock API - **URL**: https://delpuppo.net/blog/playwright/playwright-mock-api/ - **Published**: Jan 26, 2023 - **Tags**: playwright, e2e, mocking, api Hi there, Today I want to talk about how to mock API with Playwright. To do that, I add a new feature to the usual example. When one of the players wins, the system sends a POST request to the URL `http://localhost:3001/api/winners` to save the history of the results of the games. The API server is built using [json-server](https://github.com/typicode/json-server). You can find all the steps to reproduce the feature in this [commit](https://github.com/Puppo/playwright-series/commit/55467d588c7017bb1c8b851ba17b38fe9faa070c). ## Mock API Mocking API is a common feature in testing frameworks. In Playwright, to do that, you have to use the `page.route` method. This method permits you to work with API in different ways: - Mocking responses - Modify requests - Modify responses - Abort requests Any request that a page does, including XHRs and fetch requests, can be tracked, modified and mocked in Playwright using `page.route`. ## Mock API in action In this article, I want to show you how to start to use this API. Let's start with the test. The test is straightforward; it reproduces a game where the Player `X` wins and, in the end, checks if the application fetches the POST request to save the winner. The test's base is composed in this way ```ts test("should register the winner calling the winner API[POST]", async ({ page, playerXWinMoves, }) => { /// mock the API for (const move of playerXWinMoves) { await page.locator(`button:nth-child(${move})`).click(); } }); ``` As you can see, the test is similar to the previous one. Now it's time to see how you can mock the API. First, you have to get your API; to do this, you have to use the `page.route` that way. ```ts page.route("http://localhost:3001/api/winners", async route => { }); ``` So, as you can notice, the `page.route` method accepts two parameters: the first is the URL of your API, and the second is a function used to work with your request. Now it's time to get info from your request, for instance, the method and the body of the request. ```ts page.route("http://localhost:3001/api/winners", async route => { const request = route.request(); const method = await request.method(); const postData = await request.postDataJSON(); }); ``` Very simple, as you can see, it's important to remember that all the methods of the request return a Promise, and you have to use the await keyword to get the value. Now, another important feature when you want to mock API is to return custom responses. ```ts page.route("http://localhost:3001/api/winners", async route => { const request = route.request(); const method = await request.method(); const postData = await request.postDataJSON(); await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(Object.assign({ id: 1 }, postData)), }); }); ``` Using `route.fulfill` you can mock the response. You can set the status code, content type, body, etc. In this way, you have all the power to work with the response. Perfect, you have all the tools to mock the request. But before closing, I want to leave you another tip. You can use the `page.route` as the assertion of your test. Let's see an example ```ts page.route("http://localhost:3001/api/winners", async route => { const request = route.request(); const method = await request.method(); const postData = await request.postDataJSON(); expect(await request.method()).toBe("POST"); expect(await request.postDataJSON()).toEqual( expect.objectContaining({ winner: expect.stringMatching("X"), createdAt: expect.any(String), }) ); await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(Object.assign({ id: 1 }, postData)), }); }); ``` As you can notice, you can use the `expect` function also inside of the `page.route`. So you can check if the request is called and in the right way. To close the article, here you can find the entire test. ```ts test("should register the winner calling the winner API[POST]", async ({ page, playerXWinMoves, }) => { page.route("http://localhost:3001/api/winners", async route => { const request = route.request(); const method = await request.method(); const postData = await request.postDataJSON(); await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(Object.assign({ id: 1 }, data)), }); expect(await request.method()).toBe("POST"); expect(await request.postDataJSON()).toEqual( expect.objectContaining({ winner: expect.stringMatching("X"), createdAt: expect.any(String), }) ); }); for (const move of playerXWinMoves) { await page.locator(`button:nth-child(${move})`).click(); } }); ``` Ok, I think that's all from Mocking API. Now you have all the notions to start mocking API with Playwright, and if you have any doubt, you can reach out to me without problems or check the official [documentation](https://playwright.dev/docs/mock). I hope you enjoyed this content. See you soon folk! Bye bye 👋 _You can find the code of this article_ [in the GitHub repository](https://github.com/Puppo/playwright-series/tree/08-mockapi) > 🎬 **Watch / View Embedded Resource:** [https://dev.to/puppo](https://dev.to/puppo) --- ### Part 9 of 13: Playwright - Visual Comparisons - **URL**: https://delpuppo.net/blog/playwright/playwright-visual-comparisons/ - **Published**: Feb 2, 2023 - **Tags**: playwright, e2e, visual Hi there, Today I want to speak about **Visual comparisons** with Playwright. In some projects is crucial to respect the same size for the component on the page, or to guarantee the exact visualization each time, or to ensure the same colour, etc. Playwright exposes this feature out of the box. To do that, Playwright uses snapshots and compares a specific snapshot with the test result to check that nothing has changed. But don't waste time, and let's see how it works. I want to use the Square component for this example, but you have to refactor it before moving to the test. First, you have to move the X and O png images from the public folder to the Square component folder. After that, you have to refactor the component so that it imports these images and uses them to render the icon.The result is something like this: _Folder Structure_ ```sh Square O.png Square.module.scss Square.tsx X.png ``` _Square Component_ ```ts import { TicTacToeValue } from '../../models/TicTacToeValue'; import { Nullable } from '../../utils/Nullable'; import Icon from '../Icon/Icon'; import OIcon from './O.png'; import styles from './Square.module.scss'; import XIcon from './X.png'; interface SquareProps { value: Nullable; onSelect: () => void } export default function Square( { value, onSelect }: SquareProps ) { const icon = value === 'X' ? XIcon : OIcon; return ( ) } ``` Perfect, now you are ready to create your first test to check the visual comparisons. Before moving on to code, you should meet what you have to know to make visual comparisons with Playwright, and the only thing that you have to know is the assertion `toHaveScreenshot`. This assertion is your best friend if you want to implement visual comparisons with Playwright, it has some configurations, but you will see the most commons later. Now, don't waste and get your hands dirty. First of all, you have to create a new file called `src/components/Square/Square.spec.tsx`, that will contain your tests. As first example, you can create a new test that checks if the Square component shows the X icon if the value is `X`. To do that, you have to make a test like this ```ts import { expect, test } from '@playwright/experimental-ct-react'; import Square from './Square'; test.describe('Square', () => { test('should show the X icon without regression', async ({ mount, page }) => { await mount( { }} />); await expect(page).toHaveScreenshot(); }); }) ``` As you can notice, this article shows the visual comparisons in the testing of the components, but you can use it in the e2e tests too.Ok, now it's time to see the result by running the command `npm run test-ct`.The result is not like expected, I suppose. In fact, the result shows an error like this: ```sh Error: A snapshot doesn't exist at playwright-series/snapshots/components/Square/Square.spec.tsx-snapshots/Square-should-show-the-X-icon-without-regression-1-chromium-darwin.png, writing actual. ``` As you can imagine, Playwright tries to run your tests but doesn't find a snapshot to compare with your result, so it raises an error for each platform (Chrome, Firefox and WebKit). However, you can notice that three new files came up in your project. These files are the snapshot of your test. You can find them at the root of your project in the folder ` __snapshots__ ` **.** From here, Playwright can run your test and check if the result is like on these snapshots. To check that, you can rerun the previous command and notice that the result will be better and your tests will be passed. To double-check if the visual comparison works, you can change the icon sizes in the `Square.module.scss` file. For instance, from 100x100 pixels to 101x101 pixels. ```scss .Square { width: 101px; height: 101px; background-color: #fff; border: 1px solid #000; display: flex; justify-content: center; align-items: center; font-size: 2rem; font-weight: bold; cursor: pointer; transition: background-color 0.3s ease; } ``` If you rerun the command `npm run test-ct`, the result shows something like this: ```sh 3 failed [chromium] components/Square/Square.spec.tsx:6:3 Square should show the X icon without regression [firefox] components/Square/Square.spec.tsx:6:3 Square should show the X icon without regression [webkit] components/Square/Square.spec.tsx:6:3 Square should show the X icon without regression ``` And as you can imagine, your test suite failed because the result is not exactly like expected. When we run with visual comparison, as you can imagine, introducing a regression is very easy, a pixel of difference and boom, the suite fails. Playwright knows this problem and permits you to be less strict in these cases. It's important to remember that by default, Playwright is strict and checks every single pixel of difference, but you can configure it with a threshold. The most commons are maxDiffPixelRatio, maxDiffPixels or threshold.Each configuration permits you to be slackers in different cases. For instance, if you choose the maxDiffPixels configuration, you can write the previous test in this way ```ts test('should show the X icon without regression', async ({ mount, page }) => { await mount( { }} />); await expect(page).toHaveScreenshot({ maxDiffPixels: 500 }); }); ``` If you run the command `npm run test-ct` , now the test suite has passed, and as you can imagine, Playwright has tested your new snapshot using the maxDiffPixels configuration. Hence, even if the size is different, the result is ok because it respects the maxDiffPixels configuration. These kinds of configurations could be set as default if you want. For instance, you can decide that all the visual comparison tests must have the maxDiffPixels of 500. To do that, you can configure Playwright to have this configuration by default. In the Playwright configuration file, you can set the expect method with this configuration: ```ts import { defineConfig } from '@playwright/test'; export default defineConfig({ expect: { toHaveScreenshot: { maxDiffPixels: 500 }, }, }); ``` Last but not least, sometimes changing the component structure is required. In these cases, you have to change snapshots so that Playwright has the new comparison images in the future to check the results. To do that, you can run your test with the option `--update-snapshots`. This indicates to Playwright that the snapshots are old and that it has to change the previous results with the new ones. Ok, I suppose you have a good idea of how visual comparison works on Playwright, and you can start to play with it without problems.I think that's all from Visual Comparison, I hope you enjoyed this content and if you have any questions, you are welcome! See you soon folk Bye bye _You can find the source code of this article_ [in the GitHub repository](https://github.com/Puppo/playwright-series/tree/09-visual-comparisons)_._ > 🎬 **Watch / View Embedded Resource:** [https://dev.to/puppo](https://dev.to/puppo) --- ### Part 10 of 13: Playwright - Parallelism and sharding - **URL**: https://delpuppo.net/blog/playwright/playwright-parallelism-and-sharding/ - **Published**: Feb 9, 2023 - **Tags**: playwright, e2e Hey Folks, Today I'll speak about parallelism and sharding with Playwright, so don't waste time and jump into it. By default, Playwright runs your tests in parallel. It uses many worker processes at the same time. But you have different solutions to handle or remove parallelism in Playwright. - Test files are in parallel, and each file is executed in order, from the first test to the last one. This is the default configuration - Using `test.describe.configure` you can configure a file to run its tests in parallel - it's possible to configure the entire project to have all tests in all files that run in parallel using testProject.fullyParallel or testConfig.fullyParallel - it's possible to disable the parallelism limit by setting the number of workers to one. ## Worker processes Each test runs in its own worker processes, and communication between workers is impossible. A process is an Os process that runs independently and is orchestrated by the test runner. Each worker has the same environment and starts its own browser. Playwright Test reuses a single worker as much as possible to make testing faster, so multiple test files are usually run in one worker one after another. Each time a test fails, the worker is shouted down to guarantee a pristine environment for the following tests. ### Limit the worker processes Nonetheless, you can control the maximum number of parallel worker processes via the command line or in the configuration file in this way ```sh npx playwright test --workers 4 ``` ```ts // playwright.config.ts import { defineConfig } from '@playwright/test'; export default defineConfig({ // Limit the number of workers on CI, use default locally workers: process.env.CI ? 2 : undefined, }); ``` ## Disable parallelism However, it's possible to disable the parallelism too. To do that, the solution is to set the workers' configuration to one by command line or configuration file. ```sh npx playwright test --workers 1 ``` ```ts // playwright.config.ts import { defineConfig } from '@playwright/test'; export default defineConfig({ // Limit the number of workers on CI, use default locally workers: 1, }); ``` ## Parallelize tests in a single file Playwright, by default, runs all the tests in a single file in order, but it's possible to run them in parallel too. To do that, you have two possible solutions, by the configuration file or by the `test.describe.configure()` The solution by the configuration file enables parallelism in each file of your test suite instead the `test.describe.configure()` enables it just in a single file. By the way, the setting of this configuration is elementary; if you decide to enable it by the config file, the solution is this: ```ts // playwright.config.ts import { defineConfig } from '@playwright/test'; export default defineConfig({ fullyParallel: true, }); ``` else, if you want to enable parallelism using `test.describe.configure()` the solution is this ```ts import { test } from '@playwright/test'; test.describe.configure({ mode: 'parallel' }); ``` It's essential to spend some words about `beforeAll` and `afterAll` hook. In this case, these hooks will be executed for each process, so they become like `beforeEach` and `afterEach` hooks. ## Serial mode Sometimes, you have to run the tests in the same file in serial. To do that in Playwright, you can configure the serial mode. In this way, Playwright executes all the tests in sequence, but if one of the chain fails, the following tests will not execute, and so they will be skipped. _This kind of configuration exists in Playwright, but it's not recommended._ However, if you want to enable it, you have to add this line of code to your test file ```ts import { test, Page } from '@playwright/test'; // Annotate entire file as serial. test.describe.configure({ mode: 'serial' }); ``` ## Shard tests between multiple machines Another cool feature, if you want to speed up your test suite, is the shard configuration. This configuration permits splitting the execution of the test suite in different machines so that you can parallelise your suite much more. This configuration helps the execution of your CI, so you don't have to waste too much time waiting for the CI. To use this configuration, you have to use the `--shard` option and indicate 2 numbers - the total of shards - which shard Playwright has to run So, for instance, an example could be like this ```sh npx playwright test --shard=1/3 npx playwright test --shard=2/3 npx playwright test --shard=3/3 ``` In this way, you are dividing your test suite into 3 shards, and each command runs one of them. ## Control test order By default, Playwright runs all the tests in parallel, so it doesn't guarantee the order execution. However, if you disable the parallelism, you should need to execute the test in a specific order. To do that, you have two options: - Sort test files alphabetically - Use a "test list" file As you can imagine, for the first option, you have to choose a rule to order your files; for instance, in the official documentation, they say to add a prefix with three numbers: 001, 002, 003, etc. etc., easy peasy lemon squeeze. For the second option instead, the game becomes trickier. You have to put your test in a helper file, and then you have to create a new file that imports all the helpers, and you have to wrap each helper in a `test.describe`. Then, you have to disable the parallelism and indicate to Playwright to use only the file that contains the helpers. In my opinion, yes, it's a solution, but probably, I prefer the first approach. To leave you a concrete idea, here you can find a possible implementation picked directly from the official documentation ```ts // feature-a.spec.ts import { test, expect } from '@playwright/test'; export default function createTests() { test('feature-a example test', async ({ page }) => { // ... test goes here }); } ``` ```ts // feature-b.spec.ts import { test, expect } from '@playwright/test'; export default function createTests() { test.use({ viewport: { width: 500, height: 500 } }); test('feature-b example test', async ({ page }) => { // ... test goes here }); } ``` ```ts // test.list.ts import { test } from '@playwright/test'; import featureBTests from './feature-b.spec.ts'; import featureATests from './feature-a.spec.ts'; test.describe(featureBTests); test.describe(featureATests); ``` ```ts // playwright.config.ts import { defineConfig } from '@playwright/test'; export default defineConfig({ workers: 1, testMatch: 'test.list.ts', }); ``` Another critical thing to remember in this solution is to wrap all the tests in the helpers with a function; in this way, your tests will not depend on the import/export statement, and you will prevent strange mistakes. Ok, that's all! Now, I suppose you have an idea of how parallelism works in Playwright and have all the notions to handle it. I hope you enjoyed this article, and if you have any questions, don't hesitate to reach out to me, you are welcome. See you soon folks Bye Bye 👋 > 🎬 **Watch / View Embedded Resource:** [https://dev.to/puppo](https://dev.to/puppo) --- ### Part 11 of 13: Playwright - Not just for Frontend - **URL**: https://delpuppo.net/blog/playwright/playwright-not-just-for-frontend/ - **Published**: Feb 16, 2023 - **Tags**: playwright, apitesting In this path with Playwright we spent a lot of time speaking about e2e tests and component testing in the frontend world. But now its time to spill the beans and tell you a secret. Playwright can be used on the backend side too. ![Illustration for Playwright - Not just for Frontend](https://cdn.hashnode.com/res/hashnode/image/upload/v1676058978940/d5057d18-bfd2-4122-be9f-a34480bc702e.gif) ## API Testing Playwright can test REST API too. In some scenarios, you don't have a UI, but you have an API integration and have to test your integration in some way. Using API testing with Playwright, you can call your APIs without a user interface and check their correct execution. The key volta for doing that on Playwright is the `request` object. Using this object, you can call your APIs and check their result. But let's take a look at it. First, I didn't create a backend API for this example, but I used a fake API to test. I created it with json-server and json-server-auth. They are two npm packages that use a JSON file as a database and expose the database in an API. You can find more about json-server in its [documentation](https://github.com/typicode/json-server) and about json-server-auth [in this reference](https://www.npmjs.com/package/json-server-auth). It's time to understand how API testing works. A straightforward example could be this ```ts test("should call a winner", async ({ request }) => { const result = await request.post("http://localhost:3001/api/winners", { data: { winner: "O", }, }); expect(result.ok()).toBeTruthy(); expect(await result.json()).toEqual( expect.objectContaining({ winner: "O", id: expect.any(Number), }) ); }); ``` This example is to understand the structure of an API test, then you'll see a more complex example. The `request` object is exposed by Playwright in each test, like the page object in a standard e2e test in the browser. With this object, you can call one or more HTTP requests. Then, when you have the result of the requests, you can check if the result is ok or if the response respects a specific schema. As you can notice, it's elementary - create a request - wait for the result - check the result Typically, a simple API test has this flow, probably before you could have a login request because your APIs are under authentication, but a login request is just another request before. I created a more complex example to give you a better idea of the flow you can create with Playwright and API testing (This approach could be used with e2e tests too). The example is so structured. For each test, the suite: - creates a new json-server database - creates a new json-server process - runs the tests - kill the json-server - delete the temporary database Let's start from the beginning. I created two files for the tests. One file is the database with one row for the winners and one user inside. The other file is the route file to make a sort of authentication in the API. (you can learn more about that following the json-server documentation seen before). So these two files are in the db folder and appear in this way ```json // db_test.json { "users": [ { "email": "test@test.it", "password": "$2a$10$ONSI/ac5n4iyMj7LfcRIsOzYQ8dqPYK33TTH1Qp7YORPjvofkoTgS", "id": 1 } ], "winners": [ { "winner": "X", "createdAt": "2023-01-16T21:06:20.749Z", "id": 1 } ] } ``` ```json // routes_test.json { "/api/*": "/$1", "users": 600, "winners": 664 } ``` Now it's time to create a routine for each test that gets these two files, creates a copy of the database, and runs the json-server in a specific port. In this way, if we have many tests in parallel, each test doesn't depend on the actions done in the other tests. To do that, I created this code ```ts // api.spec.ts import { expect, test } from "@playwright/test"; import { ChildProcess, exec } from "child_process"; import { randomUUID } from "crypto"; import { copyFile, unlink } from "fs/promises"; import { join } from "path"; const dbName = `db_test_${randomUUID()}.json`; const dbInitPath = join(process.cwd(), "db", "db_test.json"); const dbPath = join(process.cwd(), "db", dbName); const portsMapping = { chromium: 3001, firefox: 3002, webkit: 3003, } as const; let port: 3001 | 3002 | 3003; let baseUrl: string; let api_process: ChildProcess; test.beforeAll(async ({ browserName }) => { port = portsMapping[browserName]; baseUrl = `http://localhost:${port}/api`; // Create a file to be uploaded await copyFile(dbInitPath, dbPath); // Start the server await new Promise((resolve, reject) => { api_process = exec( `npx json-server-auth --watch ${dbPath} --port ${port} --routes db/routes_test.json`, err => { if (err) reject(err); } ); api_process.stdout?.on("data", data => { if (data.includes("Watching...")) { setTimeout(() => { resolve(); }, 10); } }); }); }); ``` There are some critical points in this code: ```ts const dbName = `db_test_${randomUUID()}.json`; const dbInitPath = join(process.cwd(), "db", "db_test.json"); const dbPath = join(process.cwd(), "db", dbName); ``` Here I created two new variables, one contains the path of the init database file, and the second one is the path for the database used by the test. Then I mapped each browser in different ports, so if the tests run in parallel, there aren't problems of concurrency in the same database. (If your suite is only for API testing you can run the suite only with one browser, in this case, I kept the suite like in the previous articles) ```ts const portsMapping = { chromium: 3001, firefox: 3002, webkit: 3003, } as const; let port: 3001 | 3002 | 3003; ``` In the beforeAll hook, I created the copy of the database, and then I ran the json-server process to expose the API. ```ts test.beforeAll(async ({ browserName }) => { port = portsMapping[browserName]; baseUrl = `http://localhost:${port}/api`; // Create a file to be uploaded await copyFile(dbInitPath, dbPath); // Start the server await new Promise((resolve, reject) => { api_process = exec( `npx json-server-auth --watch ${dbPath} --port ${port} --routes db/routes_test.json`, err => { if (err) reject(err); } ); api_process.stdout?.on("data", data => { if (data.includes("Watching...")) { setTimeout(() => { resolve(); }, 10); } }); }); }); ``` It seems a bit strange, but as you can see, in this way, I simulated a new database for each test file, so each test has its database and doesn't depend on the other tests. As you can imagine, in this way, you can create a clean environment for each test. Then I added another hook, the afterAll, where I killed the API process and deleted the database file. ```ts test.afterAll(async () => { console.log("Kill server", baseUrl); api_process.kill(); await unlink(dbPath); }); ``` Then I created the first test that checks if the result of the get method returns an array of winners. This is easy peasy ```ts test("should return an array of winner", async ({ request }) => { const result = await request.get(`${baseUrl}/winners`); expect(result.ok()).toBeTruthy(); expect(await result.json()).toEqual( expect.arrayContaining([ expect.objectContaining({ winner: expect.stringMatching(/^O|X$/), id: expect.any(Number), }), ]) ); }); ``` I can call the API and get the result using the request object, and then I can check if the result is ok and if the response is like expected. This example is pretty simple, so I don't want to spend too much time on it; I guess it is comprehensible. But now it's time to move to another example that is a bit more complex. The test has to test the insert in the winners' collection. To do this action, the user must be logged in, so before doing the post, we have to login into the API. The result is something like this. ```ts test("should insert a winner", async ({ request }) => { console.log("Testing the server", baseUrl); const loginResult = await request.post(`${baseUrl}/login`, { data: { email: "test@test.it", password: "test", }, }); expect(loginResult.ok()).toBeTruthy(); const { accessToken } = await loginResult.json(); const result = await request.post(`${baseUrl}/winners`, { data: { winner: "O", }, headers: { Authorization: `Bearer ${accessToken}`, }, }); expect(result.ok()).toBeTruthy(); expect(await result.json()).toEqual( expect.objectContaining({ winner: "O", id: expect.any(Number), }) ); }); ``` As you can see, in this example, I created a flow of authentication, and then the test moves to the post request for the winners if it is ok. Yes, probably if you are familiar with testing; this is not rocket science, but you can see how it's easy to create API testing with Playwright. So far so good; in this article, you discovered API testing with Playwright and a medium example of how you can create a flow of API testing with Playwright. That's all folks! I hope you enjoyed it and see you soon! Bye bye 👋 _N.B. you can find the code of this article_ [in the GitHub repository](https://github.com/Puppo/playwright-series/tree/10-api-testing) > 🎬 **Watch / View Embedded Resource:** [https://dev.to/puppo](https://dev.to/puppo) --- ### Part 12 of 13: Playwright - Parametrize tests - **URL**: https://delpuppo.net/blog/playwright/playwright-parametrize-tests/ - **Published**: Feb 23, 2023 - **Tags**: playwright, e2e Hey folks, Today it's time to speak about how to parametrize tests with Playwright. There are cases where we want to test the same behaviour but with different values, so in these cases, it's common to create a structure for the test and reuse it with different parameters. The easiest way to do that with Playwright is using vanilla javascript. You can create an array with different cases, and using the `for` clause, you can call the test N times with different parameters. Coming back to the Tic Tac Toe application, a real example could be to simulate different rounds and, for each round, indicate the sequence of the moves and who must be the winner. An implementation of this scenario can be done in this way ```ts import { expect, test as base } from "@playwright/test"; const test = base.extend({ page: async ({ baseURL, page }, use) => { baseURL && (await page.goto(baseURL)); await use(page); }, }); type TestCase = { winner: "X" | "O"; moves: number[]; }; const testCases: TestCase[] = [ { winner: "X", moves: [1, 5, 6, 7, 3, 9, 2], }, { winner: "O", moves: [3, 1, 5, 7, 9, 4], }, ]; for (const { winner, moves } of testCases) { test(`should win the player "${winner}" with these ${moves}`, async ({ page, }) => { for (const move of moves) { await page.locator(`button:nth-child(${move})`).click(); } const winnerParagraph = await page.getByText(/winner/i); await expect(winnerParagraph).toContainText(winner); }); } ``` As you can notice, the result is quite simple: 1. Define a TestCase type 2. Create an array with the cases 3. Iterate the cases and create a `test` for each case 4. Run and check the test result Ok, this solution is good and can resolve many of our cases, but you can do more with Playwright! It's possible to load a csv file and use it to map all the test cases. Playwright doesn't fully expose this solution, but the simplicity with which Playwright is built permits a simple solution to do this. Let me show you an example: You can map the previous two cases in a csv file like this ```csv // e2e/csv/winners_cases.csv "test_case","winner","moves" "should win the player X","X","1,5,6,7,3,9,2" "should win the player O","O","3,1,5,7,9,4" ``` Then using an npm package called `csv-parse` we can load the csv in our test in this way ```ts import { parse } from "csv-parse/sync"; import fs from "fs"; import path from "path"; const cases = parse( fs.readFileSync( path.join(process.cwd(), "e2e", "csv", "winners_cases.csv") ), { columns: true, skip_empty_lines: true, } ); ``` Now in the `records` variable, you have all the data that describe your test cases. The last thing to do is to create the tests like in the previous example ```ts for (const testCase of cases) { test(testCase.test_case, async ({ page }) => { const moves = testCase.moves.split(","); for (const move of moves) { await page.locator(`button:nth-child(${move})`).click(); } const winnerParagraph = await page.getByText(/winner/i); await expect(winnerParagraph).toContainText(testCase.winner); }); } ``` And voila, you have a test suite that runs the test from a csv file. In my opinion, both ways are good, but with the csv file you have less control of your code and your tests, and it's easy to face an error that doesn't depend on the test or the code but because someone changed the file in the wrong way. So please pay attention to who touches the files 😊 Ok, I think you have an idea of how you can parameterize your tests. It's time to say goodbye to this series, this is the last article. I'll come back to you in the future if there will be new features to share with you 😊 But for now, I suppose that's all! I hope you enjoyed it and see you soon! test.afterAll(() => { Bye bye 👋 }); _N.b. you can find the code of this article_ [in the GitHub repository](https://github.com/Puppo/playwright-series/tree/11-parameterize-test) > 🎬 **Watch / View Embedded Resource:** [https://dev.to/puppo](https://dev.to/puppo) --- ### Part 13 of 13: Playwright - UI Mode - **URL**: https://delpuppo.net/blog/playwright/playwright-ui-mode/ - **Published**: Apr 6, 2023 - **Tags**: playwright, e2e Hi folks, In release [1.32](https://playwright.dev/docs/release-notes#version-132), Playwright released a new cool feature for us, the UI Mode! This feature is still in beta yet, but you can use it by adding a simple option in the CLI `--ui`. ```bash $ npx playwright test --ui ``` The result of this command is a new window in your display! ![Playwright UI Mode view](https://cdn.hashnode.com/res/hashnode/image/upload/v1680593766315/ee493e5a-d6e6-4f93-b283-456851c50058.png) Using this view, you can run and debug your test. But let's dive into it. The view is split into two parts: the left part contains the list of the tests, and the right part is the crucial view to check and debug the tests. If you are familiar with the Playwright tracer, the right section is similar to the tracer view. But what can you do with this view? First, you can run one or many tests using the run button at the right of each test or group of tests. ![Run test](https://cdn.hashnode.com/res/hashnode/image/upload/v1680594130557/fcf3cece-112d-46da-b716-f058170a0b04.png) Playwright runs the tests by clicking this button, and you can see the result. A green tick means the test is ok, and a red icon with a circle X means there is an error in the test. ![Test feedback](https://cdn.hashnode.com/res/hashnode/image/upload/v1680594313494/1fffab38-1142-4573-aa95-f326fa4163b0.png) Now if you click on one of the tests in the right section, you can see what happened during its execution. In this case, let's dive into the wrong test. In the top section, you can see the timeline of the test; you can go over it with the mouse and see all the browser views during the execution. ![Timeline](https://cdn.hashnode.com/res/hashnode/image/upload/v1680594568820/16595bb7-db59-480b-839d-8f367a870ca6.png) In the action tab, you can find all the actions done by the test, for instance: the page creation, the navigation, each click on the page and so on. You can navigate in the history by clicking on one of these actions, and each time you click the action, the panel on the right shows the view at that specific moment. In this way, you can understand what happened during the test execution. ![Actions](https://cdn.hashnode.com/res/hashnode/image/upload/v1680594923651/e0a0565c-e49f-4aeb-8b2b-e20bec947696.png) In the bottom bar, you can access different views: Source, Console, Network, and Log. I think the last three views are pretty simple, and I want to focus on the Source view. In the Source view, you can see all the code used by Playwright to run the test, and if you click on one of the actions, Playwright highlights the line of code that runs that action. If your test is wrong, Playwright shows you the error in the specific line of code where the error is thrown. In this way, you can immediately see the mistake and try fixing it. ![Source](https://cdn.hashnode.com/res/hashnode/image/upload/v1680595280660/c2500776-e23f-4619-851f-3bbfd6c455b3.png) The last cool feature introduced in this view is the possibility to open the Source code directly from the view! If you click the icon at the right of the Run icon, Playwright opens the file using Visual Studio Code. ![Open code to VsCode](https://cdn.hashnode.com/res/hashnode/image/upload/v1680602335785/1880962f-7e9c-467d-ba33-e9047512cac8.png) Last but not least, Playwright keeps syncing your codebase with the Playwright UI Runner, so in this way, you can change and fix errors and retry the test immediately. If you want to find out more or watch the Playwright UI Runner on working, check out my Youtube video. [https://www.youtube.com/watch?v=ErkRgqmHCmc](https://www.youtube.com/watch?v=ErkRgqmHCmc) I think this solution and the integration of Playwright with VsCode are awesome, and Playwright is changing the Developer Experience in the best possible way! Ok, that's all folks! I hope you enjoyed this content, and now your testing phase is more comfortable and easier! See you later! Bye Bye 👋 > 🎬 **Watch / View Embedded Resource:** [https://dev.to/puppo](https://dev.to/puppo) ---