{"id":31339,"date":"2025-06-06T05:00:00","date_gmt":"2025-06-06T03:00:00","guid":{"rendered":"https:\/\/sii.pl\/blog\/?p=31339"},"modified":"2025-06-06T14:53:45","modified_gmt":"2025-06-06T12:53:45","slug":"playwright-in-practice-integration-api-with-ui-architecture-of-testing-framework-part-ii","status":"publish","type":"post","link":"https:\/\/sii.pl\/blog\/en\/playwright-in-practice-integration-api-with-ui-architecture-of-testing-framework-part-ii\/","title":{"rendered":"Playwright in practice: integration API with UI architecture of testing framework. Part II"},"content":{"rendered":"\n<p>In this article, we continue building a test automation framework using Playwright and TypeScript. <a href=\"https:\/\/sii.pl\/blog\/en\/playwright-in-practice-5-steps-to-an-effective-ui-web-test-automation-framework-part-i\/\" target=\"_blank\" rel=\"noopener\" title=\"\">In the previous post<\/a>, we introduced a few key concepts essential for the upcoming development work.<\/p>\n\n\n\n<p>Today, we focus on a code fragment as the solution&#8217;s foundation. We will walk through how to combine API and UI tests. Moreover, we will explore proven practices such as design patterns, factory patterns, page object patterns, and dedicated assertion classes to set up scalable and easy-to-maintain automated tests.<\/p>\n\n\n\n<p>As I mentioned <a href=\"https:\/\/sii.pl\/blog\/en\/playwright-in-practice-5-steps-to-an-effective-ui-web-test-automation-framework-part-i\/\" target=\"_blank\" rel=\"noopener\" title=\"\">in the first part<\/a>, we use the Trello app to demonstrate how the framework we&#8217;ve built works in practice. Trello is a project management tool based on a Kanban board. It offers a powerful API, making it a great choice for learning how to automate tests. I encourage you to use more advanced applications when learning test automation \u2013 ones that reflect real processes and scenarios. This better prepares you for working on commercial projects.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong><strong>What do our tests do?<\/strong><\/strong><\/h2>\n\n\n\n<p>Our test does:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Create a board on Trello using the API.<\/li>\n\n\n\n<li>Create a list (column) for the board.<\/li>\n\n\n\n<li>Create cards for a specific list (as a list is a column in Trello terminology).<\/li>\n\n\n\n<li>Add another list (a new column).<\/li>\n\n\n\n<li>Move cards from the original column to the new one.<\/li>\n\n\n\n<li>Verify that cards are available in the new column.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Framework architecture overview: Playwright + TypeScript<\/strong><\/h2>\n\n\n\n<figure class=\"wp-block-image aligncenter size-large\"><img decoding=\"async\" width=\"1024\" height=\"575\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image2-6-1024x575.png\" alt=\"The diagram of our solution \" class=\"wp-image-31302\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image2-6-1024x575.png 1024w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image2-6-300x168.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image2-6-768x431.png 768w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image2-6-1536x863.png 1536w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image2-6-555x312.png 555w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image2-6.png 1907w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Fig. 1 The diagram of our solution<\/figcaption><\/figure>\n\n\n\n<p>The diagram demonstrates the architecture of a test automation framework built using Playwright and TypeScript, emphasizing the integration of two layers: <strong>API and UI<\/strong>.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Test Runner (<\/strong><code>Playwright<\/code><strong>)<\/strong>\n<ul class=\"wp-block-list\">\n<li>Responsible for executing test specifications and managing fixtures and the test lifecycle.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Fixtures (<\/strong><code>boardFactory<\/code><strong>, <\/strong><code>cardFactory<\/code><strong>, <\/strong><code>trelloApi<\/code><strong>)<\/strong>\n<ul class=\"wp-block-list\">\n<li>Inject dependencies into tests \u2013 for example, creating and deleting test data.<\/li>\n\n\n\n<li>Serve as the integration point between API calls and test execution.<\/li>\n\n\n\n<li>Help reduce duplicated code by centralizing common setup and teardown logic.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Factories\u00a0 (<\/strong><code>BoardFactory<\/code><strong>, <\/strong><code>CardFactory<\/code><strong>)<\/strong>\n<ul class=\"wp-block-list\">\n<li>Encapsulate API calls in a structured, reusable, and testable format.<\/li>\n\n\n\n<li>An example of applying the <strong>Factory Pattern<\/strong> in test automation.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>TrelloAPI<\/strong>\n<ul class=\"wp-block-list\">\n<li>A class responsible for communicating with the Trello REST API via Playwright&#8217;s <code>APIRequestContext<\/code>.<\/li>\n\n\n\n<li>Contains methods such as <code>createBoard<\/code>, <code>createCard<\/code>, <code>getLists<\/code>, and <code>deleteBoard<\/code>.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>APIRequestContext \u2192 Trello REST API<\/strong>\n<ul class=\"wp-block-list\">\n<li>Direct layer for executing HTTP calls to Trello.<\/li>\n\n\n\n<li>Authenticates using an API key and token.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Page Objects (<\/strong><code>LoginPage<\/code><strong>, <\/strong><code>BoardPage<\/code><strong>) + <\/strong>Assertion Class\n<ul class=\"wp-block-list\">\n<li>Contain methods for interacting with Trello&#8217;s UI.<\/li>\n\n\n\n<li>Decouple business logic from UI interaction code.<\/li>\n\n\n\n<li><code>TrelloBoardAssertions<\/code> is a dedicated class for verifying UI state (e.g., the order of cards in a column).<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Configuration (<\/strong><code>TrelloSettings<\/code><strong>) + Environment Variables<\/strong>\n<ul class=\"wp-block-list\">\n<li>Central place for storing URLs, API keys, and login credentials.<\/li>\n\n\n\n<li>Can be overridden using <code>process.env<\/code> in CI\/CD environments.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<p><strong><strong>Benefits of this architecture<\/strong><\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Provides a consistent structure for creating E2E tests using API-generated test data.<\/li>\n\n\n\n<li>Enables easy maintenance, testing, and extension of the framework.<\/li>\n\n\n\n<li>Promotes reusability and clean separation of concerns through modular layers.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong><strong>What&#8217;s worth remembering?<\/strong><\/strong><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Each test should create fresh test data<\/strong> to ensure independence.<\/li>\n\n\n\n<li><strong>Test data should be cleaned up after each test<\/strong> to avoid polluting the environment.<\/li>\n\n\n\n<li>In Trello&#8217;s case, this is especially important \u2013 the <strong>free account allows only up to 10 boards<\/strong>. If you forget to delete test data, your tests may fail due to exceeding account limits.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong><strong>TrelloBoardPage \u2013 <ins>i<\/ins>nteracting with a Trello Board<\/strong><\/strong><\/h2>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1024\" height=\"396\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image3-4-1024x396.png\" alt=\"kod\" class=\"wp-image-31305\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image3-4-1024x396.png 1024w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image3-4-300x116.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image3-4-768x297.png 768w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image3-4.png 1460w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>The <code>TrelloBoardPage<\/code> class contains methods responsible for interacting with a Trello board using Playwright. It handles user interactions on the board, such as navigating between pages, moving cards between columns, and verifying the presence of cards in specific columns.<\/p>\n\n\n\n<p>Since Trello is an SPA (Single-Page Application<strong>)<\/strong>, the number of possible actions within a single page (like the board view) can be extensive. In this case, it&#8217;s recommended to split the page into <strong>smaller Page Object classes<\/strong>, each following the <strong>Single Responsibility Principle<\/strong>. This makes the codebase more readable and easier to maintain.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong><strong>Characteristics of SPA (Single Page Applications)<\/strong><\/strong><\/h2>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li><strong>Dynamic content updates<\/strong> \u2013 UI elements (e.g., columns and cards) update without a full page reload.<\/li>\n\n\n\n<li><strong>Heavy use of JavaScript<\/strong> \u2013 Trello, for example, uses Backbone.js. JavaScript handles dynamic interactions, animations, and events (such as drag-and-drop).<\/li>\n\n\n\n<li><strong>Asynchronous data loading<\/strong> \u2013 technologies like Ajax and WebSockets allow data to load without refreshing the page, providing a smoother user experience.<\/li>\n\n\n\n<li><strong>Client-side state management<\/strong> \u2013 the current board, filters, or opened cards are stored locally and synchronized with the server in the background.<\/li>\n\n\n\n<li><strong>URL changes without reloads<\/strong> \u2013 the browser&#8217;s URL updates to reflect the active board or card, but the page itself is not reloaded.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\"><code><strong>navigateToBoard<\/strong><\/code> methdod<\/h2>\n\n\n\n<p>One of the most important methods in the <code>TrelloBoardPage<\/code> class is <code>navigateToBoard(url)<\/code>. This method navigates to a specific board page and waits until the element representing the board tile appears. This ensures the UI is fully loaded and ready for the next test actions.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-large is-resized\"><img decoding=\"async\" width=\"1024\" height=\"615\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image4-4-1024x615.png\" alt=\"kod\" class=\"wp-image-31307\" style=\"width:840px;height:auto\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image4-4-1024x615.png 1024w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image4-4-300x180.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image4-4-768x461.png 768w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image4-4-1536x922.png 1536w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image4-4.png 1686w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>In this class, we pass the <code>page<\/code> object (provided by Playwright) as a constructor parameter. This allows us to perform actions within a specific browser context.<\/p>\n\n\n\n<p>At the beginning of the class, we define private fields (strings) that store selectors for elements on the login page:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>usernameField<\/code> \u2013 selector for the username input field,<\/li>\n\n\n\n<li><code>passwordField<\/code> \u2013 selector for the password input field,<\/li>\n\n\n\n<li><code>loginSubmitButton<\/code> \u2013 selector for the login button,<\/li>\n\n\n\n<li><code>allBoardContent<\/code> \u2013 selector for the element that represents Trello boards.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><code><strong>go(url: string)<\/strong><\/code> method<\/h3>\n\n\n\n<p>The <code>go<\/code> method navigates to a specified URL. Currently, we pass the URL as a parameter, but this can easily be extended \u2013 for example, by retrieving the value from an environment variable. This is especially useful when running tests across different environments such as dev, staging, or production.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code><strong>getAllBoardsVisibleStatus()<\/strong><\/code> methdod<\/h3>\n\n\n\n<p>This method checks whether the element representing the board content (<code>allBoardContent<\/code>) is visible. If it is, it confirms that the user has successfully logged into the system.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code><strong>login(username: string, password: string)<\/strong><\/code> method<\/h3>\n\n\n\n<p>This method performs the full login flow:<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li>Click the <strong>&#8220;Log in&#8221;<\/strong> button on the header menu.<\/li>\n\n\n\n<li>Enter the username in the login field.<\/li>\n\n\n\n<li>Click the <strong>&#8220;Continue&#8221;<\/strong> button.<\/li>\n\n\n\n<li>Enter the password in the password field.<\/li>\n\n\n\n<li>Click the <strong>&#8220;Log in&#8221;<\/strong> button.<\/li>\n\n\n\n<li>Wait until the board content (<code>allBoardContent<\/code>) is fully loaded.<\/li>\n<\/ol>\n\n\n\n<p>You may also consider adding another method for navigating directly to the login page \u2013 either by passing the URL as a parameter or configuring separate environment-specific settings.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>trelloFixtures<\/strong><\/h2>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img decoding=\"async\" width=\"796\" height=\"773\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image5-4.png\" alt=\"kod\" class=\"wp-image-31309\" style=\"object-fit:cover\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image5-4.png 796w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image5-4-300x291.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image5-4-768x746.png 768w\" sizes=\"(max-width: 796px) 100vw, 796px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img decoding=\"async\" width=\"824\" height=\"557\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image6-1.png\" alt=\"kod\" class=\"wp-image-31311\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image6-1.png 824w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image6-1-300x203.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image6-1-768x519.png 768w\" sizes=\"(max-width: 824px) 100vw, 824px\" \/><\/figure>\n\n\n\n<p>In this part of the code, we define the <code>TrelloFixtures<\/code> type, which contains all fixtures used throughout the tests. It&#8217;s the central place where dependencies, such as page objects, factories, APIs, assertion classes, and configuration settings like TrelloSettings, are initialized.<\/p>\n\n\n\n<p><strong>Key points:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Fixtures are registered using <code>test.extend&lt;TrelloFixtures>()<\/code>.<\/li>\n\n\n\n<li>Each dependency is <strong>lazy-loaded<\/strong>, which means it is only created when a test requires a particular fixture.<\/li>\n\n\n\n<li>This approach helps avoid duplicate code and makes it easy to <strong>share or isolate test state<\/strong>, depending on the needs of a given test case.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong><strong>Tip for designing a scalable test framework<\/strong><\/strong><\/h3>\n\n\n\n<p>In this example, all initializations for page objects, supporting classes (factories, assertions), and configuration are grouped into a single <code>TrelloFixtures<\/code> class. This is convenient and easy to manage for educational purposes or small projects.<\/p>\n\n\n\n<p>However, in <strong>production environments<\/strong>, a more modular approach is recommended:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Split fixtures into smaller groups<\/strong> (e.g., <code>apiFixtures<\/code>, <code>uiFixtures<\/code>, <code>authFixtures<\/code>) to reflect architectural boundaries.<\/li>\n\n\n\n<li>This improves maintainability, simplifies testing, and makes refactoring easier.<\/li>\n\n\n\n<li>Smaller, focused files are easier to debug and review, especially in large-scale projects.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong><strong>Interfaces \u2013 why do we use them?<\/strong><\/strong><\/h2>\n\n\n\n<p>We can define TypeScript interfaces such as Board, List, and Card based on Trello API responses. These types ensure type safety and better code predictability.<\/p>\n\n\n\n<p>Thanks to TypeScript, if the Trello API returns data that matches our interface structure, we can safely and confidently use that data throughout the codebase. Depending on the project&#8217;s needs, these interfaces can also be extended and customized.<\/p>\n\n\n\n<p>Here is an example of the Board interface:<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img decoding=\"async\" width=\"484\" height=\"212\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image7-1.png\" alt=\"kod\" class=\"wp-image-31313\" style=\"object-fit:cover\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image7-1.png 484w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image7-1-300x131.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image7-1-370x162.png 370w\" sizes=\"(max-width: 484px) 100vw, 484px\" \/><\/figure>\n\n\n\n<p>It looks similar to List and Card.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img decoding=\"async\" width=\"516\" height=\"206\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image8.png\" alt=\"kod\" class=\"wp-image-31315\" style=\"object-fit:cover\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image8.png 516w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image8-300x120.png 300w\" sizes=\"(max-width: 516px) 100vw, 516px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img decoding=\"async\" width=\"424\" height=\"204\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image9.png\" alt=\"kod\" class=\"wp-image-31317\" style=\"object-fit:cover\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image9.png 424w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image9-300x144.png 300w\" sizes=\"(max-width: 424px) 100vw, 424px\" \/><\/figure>\n\n\n\n<p>These interfaces allow us to type-check the data returned from the API. They give us better control over the test code and factory pattern implementation. We use these interfaces in methods such as:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1024\" height=\"380\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image10-1024x380.png\" alt=\"kod\" class=\"wp-image-31319\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image10-1024x380.png 1024w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image10-300x111.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image10-768x285.png 768w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image10.png 1032w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Methods, such as createBoard, return a response that matches the fields defined in the Board interface. If we define the fields in the interface to match the structure of the Trello API, TypeScript allows us to use this data seamlessly.<\/p>\n\n\n\n<p>Thanks to that, we can easily access properties such as id, name, or URL without additional type casting.<\/p>\n\n\n\n<p>Interfaces like Board, List, or Card can be easily extended to include optional fields, depending on our needs. This flexibility helps us maintain and scale the test framework effectively.<\/p>\n\n\n\n<p>Framework settings can be stored in a Settings.ts file or class.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-large\"><img decoding=\"async\" width=\"1024\" height=\"718\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image11-1024x718.png\" alt=\"kod\" class=\"wp-image-31321\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image11-1024x718.png 1024w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image11-300x210.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image11-768x539.png 768w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image11.png 1434w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong><strong>Managing configuration in test automation frameworks<\/strong><\/strong><\/h2>\n\n\n\n<p>Every more complex test automation framework often has unexpected needs for managing configuration, such as handling API keys, environment URLs, login credentials, and so on.<br>There are a few common ways to organize this kind of configuration. The most popular approaches are:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Storing settings in <code>.json<\/code>, <code>.yml<\/code>, or <code>.env<\/code> files<\/li>\n\n\n\n<li>Injecting and accessing variables using <code>process.env<\/code><\/li>\n\n\n\n<li>Combining both approaches<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\"><strong><strong>Approach: using a JSON file<\/strong><\/strong><\/h3>\n\n\n\n<p>In the example shown above, the configuration is stored in a file called <code>TrelloSettings.json<\/code>. This approach has several advantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>It allows us to change configuration data without rebuilding the project.<\/li>\n\n\n\n<li>We can maintain separate configuration files for each environment (e.g., <code>settings.dev.json<\/code>, <code>settings.staging.json<\/code>, etc.).<\/li>\n\n\n\n<li>Sensitive data (such as API tokens) can be overridden by environment variables in CI\/CD pipelines, instead of being stored in the Git repository.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><code><strong>TrelloSettings<\/strong><\/code> class<\/h3>\n\n\n\n<p>In the code example, we use a <code>TrelloSettings<\/code> class that:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Loads configuration from a JSON file, whose path is passed via the constructor.<\/li>\n\n\n\n<li>Checks whether the file exists and throws an error if it doesn&#8217;t.<\/li>\n\n\n\n<li>Parses the JSON file and assigns the values to fields like <code>apiKey<\/code>, <code>token<\/code>, <code>baseURL<\/code>, etc.<\/li>\n<\/ol>\n\n\n\n<p>This is a simple way to encapsulate and manage configuration in a single class.<\/p>\n\n\n\n<p><strong>Protip<\/strong>: It&#8217;s worth adding logic that allows fallback to <code>process.env<\/code> variables if certain values are set there. This enables you to use secure, static config files locally, while dynamically loading sensitive values (like tokens) in the CI\/CD pipeline.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">BoardFactory \u2013 creating and deleting a board<\/h3>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img decoding=\"async\" width=\"854\" height=\"490\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image12.png\" alt=\"kod\" class=\"wp-image-31325\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image12.png 854w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image12-300x172.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image12-768x441.png 768w\" sizes=\"(max-width: 854px) 100vw, 854px\" \/><\/figure>\n\n\n\n<p>The <code>BoardFactory<\/code> class is responsible for creating and deleting boards in the Trello app. It utilizes methods available in the Trello API.<\/p>\n\n\n\n<p>Using the <strong>Factory Pattern<\/strong> in this case is an efficient solution, as it helps separate test data creation logic from the test logic itself. As a result, tests become cleaner and easier to manage and maintain.<\/p>\n\n\n\n<p><strong><strong>What do the methods do?<\/strong><\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code><strong>createBoard(name: string): Promise&lt;Board><\/strong><\/code> creates a new board with the specified name. Returns a response typed as <code>Board<\/code>.<\/li>\n\n\n\n<li><code><strong>deleteBoard(boardId: string): Promise&lt;void><\/strong><\/code> deletes a board based on its <code>boardId<\/code>. It&#8217;s recommended to use this method in <code>afterEach<\/code> or <code>finally<\/code> blocks to ensure proper cleanup after test execution.<\/li>\n<\/ul>\n\n\n\n<p><strong><strong>Why is this a good approach?<\/strong><\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Separation of Concerns<\/strong> \u2013 tests focus solely on verifying behavior, while the factory handles test data creation.<\/li>\n\n\n\n<li><strong>Code Reusability<\/strong> \u2013 you can reuse the same factory logic across many tests.<\/li>\n\n\n\n<li><strong>Easier Extensibility<\/strong> \u2013 extending the factory to support additional features (e.g., default board lists, team assignment, metadata) is simple.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong><code>CardFactory<\/code><strong> Class \u2013 creating cards for a board<\/strong><\/strong><\/h3>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1024\" height=\"466\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image13-1024x466.png\" alt=\"kod\" class=\"wp-image-31327\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image13-1024x466.png 1024w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image13-300x136.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image13-768x349.png 768w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image13.png 1262w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>The <code>CardFactory<\/code> class is responsible for creating Trello cards within a specified list or board.<\/p>\n\n\n\n<p>In our example, we use two classes responsible for creating test data: <code>BoardFactory<\/code> and <code>CardFactory<\/code>.<br>Let&#8217;s first focus on the <code>CardFactory<\/code> class.<\/p>\n\n\n\n<p>This class contains the method <code><strong>createCards<\/strong><\/code>, which takes two parameters:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code><strong>titles<\/strong><\/code> \u2013 an array of strings representing the titles of the cards to be created,<\/li>\n\n\n\n<li><code><strong>boardId<\/strong><\/code> \u2013 the ID of the board to which the cards will be assigned.<\/li>\n<\/ul>\n\n\n\n<p><strong>Steps Performed in <code>createCards<\/code>:<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Retrieve board columns (lists)<\/strong><br>Calls the Trello API to get the lists (columns) associated with the specified board.<\/li>\n\n\n\n<li><strong>Create an empty array to store results<\/strong><br>Initializes an empty array called <code>cards<\/code>, where newly created card objects will be stored.<\/li>\n\n\n\n<li><strong>Iterate over titles and create cards<\/strong><br>For each title in the array:\n<ul class=\"wp-block-list\">\n<li><span style=\"color: initial;\">The method is called createCard, and it passes the first list&#8217;s title and ID.<\/span><\/li>\n\n\n\n<li>(Assumption: All cards are created in the first column by default.)<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<p><strong>Why is this useful?<\/strong><\/p>\n\n\n\n<p>This approach allows you to quickly generate a modular test data set that can be reused across multiple test scenarios.<br>The method can also be easily extended to:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Assign cards to specific or random columns.<\/li>\n\n\n\n<li>Add metadata such as labels or descriptions.<\/li>\n\n\n\n<li>Control card order or simulate edge cases.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><code>TrelloBoardAssertions<\/code><strong> Class \u2013 verifying card placement<\/strong><\/h3>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1024\" height=\"454\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image14-1024x454.png\" alt=\"kod\" class=\"wp-image-31329\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image14-1024x454.png 1024w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image14-300x133.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image14-768x341.png 768w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image14-1536x682.png 1536w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image14.png 1726w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><code><strong>TrelloBoardAssertions<\/strong><\/code> is responsible for the assertion layer in tests. It verifies whether the expected state is present in the UI.<\/p>\n\n\n\n<p>At the beginning, we pass an instance of <code>TrelloBoardPage<\/code> through the constructor. This allows us to use UI methods defined in that class throughout the rest of the assertion class \u2013 in this case, we use the <code>getAllCardsInColumn<\/code> method, which returns all cards present in a specified column.<\/p>\n\n\n\n<p>The <code>verifyCardsInColumn<\/code> method accepts two parameters:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>titles<\/strong> \u2013 an array containing the titles of the cards that are expected to be created.<\/li>\n\n\n\n<li><strong>columnName<\/strong> \u2013 the column name in which these cards should appear.<\/li>\n<\/ul>\n\n\n\n<p><strong>How does it work?<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>It retrieves all card titles from the specified column using the <code>getAllCardsInColumn<\/code> method.<\/li>\n\n\n\n<li>It iterates through the expected titles and checks:<br>a. Whether each expected card title is visible in the column (<code>toBeVisible()<\/code>).<br>b. Whether the text of the card matches the expected title.<\/li>\n\n\n\n<li>Finally, it performs two assertions:<br>a. Checks if the actual cards in the column match the expected titles using <code>toEqual(expect.arrayContaining(...))<\/code>.<br>b. Verifies that the number of cards matches the expected count using <code>toBe(cardTitles.length)<\/code>.<\/li>\n<\/ol>\n\n\n\n<p>This approach ensures that both the content and the number of cards are consistent with expectations.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>trelloBoardTests.spec.ts<\/strong> \u2013 <strong>which contains the fully test<\/strong><\/h2>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" width=\"1024\" height=\"662\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image15-1024x662.png\" alt=\"kod\" class=\"wp-image-31331\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image15-1024x662.png 1024w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image15-300x194.png 300w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image15-768x496.png 768w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image15-1536x993.png 1536w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/image15.png 1810w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>As you can see, we have an end-to-end test that creates a board in Trello, adds cards, moves them to another column, and finally removes all test data. The test uses predefined fixtures such as <code>boardFactory<\/code>, <code>cardFactory<\/code>, and <code>TrelloAPI<\/code>, which are injected as dependencies.<\/p>\n\n\n\n<p>The first step is to generate a unique board name using <code>Date.now()<\/code>, which minimizes the risk of name conflicts between parallel or subsequent tests. In the next step, the board is created via API.<\/p>\n\n\n\n<p>We then define an array called <code>cardTitles<\/code>, which contains the titles of the cards to be added to the board. These cards are created using the <code>cardFactory.createCards<\/code> method and assign it to the board using its ID.<\/p>\n\n\n\n<p>Next, we create a new column name (<code>newColumnName<\/code>) to which the cards will be moved.<\/p>\n\n\n\n<p>After the API calls, we proceed to UI interactions. The <code>trelloLoginPage.go<\/code> method navigates to the login form. The login is performed using credentials loaded from a configuration file (<code>settings<\/code>). Once logged in, the test opens the created board using its URL.<\/p>\n\n\n\n<p>In the UI, the cards are moved between columns using a drag-and-drop mechanism. We then verify that the cards are visible in the expected column, which confirms that they have been successfully moved.<\/p>\n\n\n\n<p>At the end of the test, the <code>deleteBoard<\/code> method is executed to remove the created board. It&#8217;s worth noting that if the test fails earlier, the board may not be deleted and could remain visible on the dashboard. To avoid this, it would be a good idea to implement an <code>afterAll<\/code> or <code>finally<\/code> block that ensures the deletion logic always runs, regardless of the test result. I encourage you to implement this yourself \u2013 it&#8217;s a good learning exercise.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><a href=\"https:\/\/sii.pl\/en\/job-ads\/\" target=\"_blank\" rel=\"noreferrer noopener\"><img decoding=\"async\" width=\"737\" height=\"170\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/praca-EN-k-6.jpg\" alt=\"job offert\" class=\"wp-image-31341\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/praca-EN-k-6.jpg 737w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/praca-EN-k-6-300x69.jpg 300w\" sizes=\"(max-width: 737px) 100vw, 737px\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Summary<\/strong><\/h2>\n\n\n\n<p>In this article, I demonstrated how to build a modern testing framework using Playwright. I showed how to integrate two layers: using API calls to create test data and leveraging that data in UI tests.<\/p>\n\n\n\n<p>The example code demonstrates several useful practices and design patterns in Playwright with TypeScript:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Factory Pattern<\/strong> \u2013 for generating test data (e.g., <code>boardFactory<\/code>, <code>cardFactory<\/code>).<\/li>\n\n\n\n<li><strong>Page Object Pattern<\/strong> \u2013 for interacting with the UI.<\/li>\n\n\n\n<li><strong>Assertion Class<\/strong> \u2013 for separating complex assertion logic from test flow.<\/li>\n\n\n\n<li><strong>Fixtures<\/strong> \u2013 to simplify dependency sharing and configuration between tests.<\/li>\n<\/ul>\n\n\n\n<p>This approach provides a solid foundation for developing UI and integration test architecture. I encourage you to experiment and extend this library \u2013 for example, by introducing a global cleanup mechanism (<code>afterAll<\/code>, <code>finally<\/code>) to ensure better reliability.<\/p>\n\n\n\n<p>***<\/p>\n\n\n\n<p>The first part of the article can be found here: <a href=\"https:\/\/sii.pl\/blog\/en\/playwright-in-practice-5-steps-to-an-effective-ui-web-test-automation-framework-part-i\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Playwright in practice: 5 steps to an effective UI &amp; Web test automation framework. Part I <\/a><\/p>\n\n\n\n<p><br><\/p>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;31339&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;2&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;5&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;11&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;5\\\/5 ( votes: 2)&quot;,&quot;size&quot;:&quot;18&quot;,&quot;title&quot;:&quot;Playwright in practice: integration API with UI architecture of testing framework. Part II&quot;,&quot;width&quot;:&quot;139.5&quot;,&quot;_legend&quot;:&quot;{score}\\\/{best} ( {votes}: {count})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 139.5px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 14.4px;\">\n            5\/5 ( votes: 2)    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>In this article, we continue building a test automation framework using Playwright and TypeScript. In the previous post, we introduced &hellip; <a class=\"continued-btn\" href=\"https:\/\/sii.pl\/blog\/en\/playwright-in-practice-integration-api-with-ui-architecture-of-testing-framework-part-ii\/\">Continued<\/a><\/p>\n","protected":false},"author":215,"featured_media":31334,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_editorskit_title_hidden":false,"_editorskit_reading_time":0,"_editorskit_is_block_options_detached":false,"_editorskit_block_options_position":"{}","inline_featured_image":false,"footnotes":""},"categories":[1321],"tags":[2786,2787,1706,1590,1526,1474],"class_list":["post-31339","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-testing","tag-framework-en","tag-testng-en","tag-playwright-en","tag-tools","tag-guidebook","tag-typescript-en"],"acf":[],"aioseo_notices":[],"republish_history":[],"featured_media_url":"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/05\/Coding_2.jpg","category_names":["Testing"],"_links":{"self":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/31339"}],"collection":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/users\/215"}],"replies":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/comments?post=31339"}],"version-history":[{"count":3,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/31339\/revisions"}],"predecessor-version":[{"id":31407,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/posts\/31339\/revisions\/31407"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/media\/31334"}],"wp:attachment":[{"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/media?parent=31339"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/categories?post=31339"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sii.pl\/blog\/en\/wp-json\/wp\/v2\/tags?post=31339"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}