Showing Posts From
Framework
Eric Stanley- 02 Aug, 2026
Mastering Objective-C Building Robust Frameworks and Libraries for iOS Development
IntroductionIn the ever-evolving world of iOS development, creating reusable components through frameworks and libraries can significantly enhance your productivity and efficiency. Objective-C, while sometimes overshadowed by Swift, remains a powerful language for building robust applications. In this post, we’ll explore the essential steps to create your own frameworks and libraries in Objective-C, empowering you to write cleaner, more maintainable code. Understanding Frameworks and LibrariesBefore diving into the how-to, let’s clarify the difference between frameworks and libraries. A library is a collection of pre-written code that developers can call upon to perform specific tasks, while a framework provides a structure and a set of conventions for building applications. Why Build a Framework?Reusability: Frameworks allow you to encapsulate functionality that can be reused across multiple projects.Maintainability: A well-structured framework makes it easier to maintain and update code without affecting the entire application.Collaboration: Teams can work on different parts of a project independently by utilizing frameworks.Getting Started with Framework Development in Objective-CStep 1: Setting Up Your ProjectOpen Xcode and create a new project.Select the Framework option under the iOS tab.Name your framework and choose Objective-C as the language.Step 2: Structuring Your FrameworkOrganizing your code is essential. A typical framework structure might include:Classes: Individual components that perform specific functions. Categories: Extensions to existing classes, allowing you to add methods without subclassing. Protocols: Define a blueprint of methods that can be adopted by any class.Step 3: Writing Your CodeHere’s a simple example of creating a utility class in your framework: // MyUtilities.h #import <Foundation/Foundation.h>@interface MyUtilities : NSObject+ (NSString *)reverseString:(NSString *)string;@end// MyUtilities.m #import "MyUtilities.h"@implementation MyUtilities+ (NSString *)reverseString:(NSString *)string { NSUInteger length = [string length]; NSMutableString *reversedString = [NSMutableString stringWithCapacity:length]; for (NSUInteger i = length; i > 0; i--) { [reversedString appendString:[NSString stringWithFormat:@"%C", [string characterAtIndex:i - 1]]]; } return reversedString; }@endStep 4: Exposing Your FrameworkTo make your framework usable in other projects, you need to expose its public interface. Ensure that only necessary classes and methods are accessible by marking them with @interface in the header files. Step 5: Testing Your FrameworkTesting is crucial. Create a simple application to integrate your framework and run unit tests to verify that everything works as expected. You can use XCTest for unit testing in Objective-C. Step 6: DistributionOnce your framework is ready, you can distribute it either as a static library, dynamic framework, or even via CocoaPods or Carthage for easier integration into other projects. Best PracticesDocumentation: Create clear documentation for your framework. Use comments and README files to explain usage. Version Control: Keep track of changes using Git. Semantic versioning can help users understand compatibility. Error Handling: Implement robust error handling to make your framework reliable. Performance: Profile your code to ensure it runs efficiently.ConclusionBuilding frameworks and libraries in Objective-C can greatly improve your development workflow. By encapsulating functionality and promoting code reuse, you can create modular applications that are easier to maintain. Whether you’re developing for personal projects or contributing to larger teams, mastering the art of framework development in Objective-C is a valuable skill that will serve you well in your iOS development journey. Call to ActionHave you built a framework in Objective-C? Share your experiences and tips in the comments below! For more in-depth tutorials and resources, subscribe to our blog for the latest updates in iOS development!
TensorFlow vs. PyTorch vs. Scikit-learn Choosing the Right Framework for Your Machine Learning Projects
IntroductionIn the rapidly evolving world of machine learning and artificial intelligence, choosing the right framework can significantly impact your project's success. With a plethora of options available, three frameworks often rise to the forefront: TensorFlow, PyTorch, and Scikit-learn. Each has its own strengths and weaknesses, making them suitable for different types of projects. In this blog post, we’ll dive deep into a comparison of these three frameworks to help you determine which one is the best fit for your needs. TensorFlow: Power and FlexibilityTensorFlow, developed by Google, is an open-source framework that has become synonymous with deep learning. One of its standout features is its scalability; TensorFlow can handle large datasets and complex models, making it ideal for production-level applications. Strengths:Ecosystem: TensorFlow offers a rich ecosystem with tools like TensorBoard for visualization, TensorFlow Hub for sharing models, and TensorFlow Lite for mobile and edge computing. Deployment: Its production-ready features, including TensorFlow Serving for deploying models and TensorFlow.js for running models in the browser, are unparalleled. Community and Support: With a large community and extensive documentation, finding solutions to problems and learning resources is relatively easy.Weaknesses:Steep Learning Curve: The complexity of TensorFlow can be daunting for beginners, especially with its static computation graph approach, although TensorFlow 2.x has introduced eager execution to ease this.PyTorch: Flexibility and Ease of UsePyTorch, developed by Facebook, has gained significant traction in the research community due to its dynamic computation graph, which allows for greater flexibility and ease of debugging. This makes it an excellent choice for experimentation and rapid prototyping. Strengths:Dynamic Computation Graphs: PyTorch’s ability to modify the graph on-the-fly allows for more intuitive model building and debugging. Simplicity: Its Pythonic nature makes it easier to learn and implement, especially for those familiar with Python programming. Strong Community in Research: PyTorch is often the framework of choice for academic research, which means cutting-edge advancements and models are frequently released in PyTorch.Weaknesses:Less Mature for Production: While improvements are continuously made, PyTorch has historically been less suited for deployment compared to TensorFlow, although tools like TorchServe are bridging this gap.Scikit-learn: The Go-To for Traditional Machine LearningScikit-learn is the go-to library for traditional machine learning algorithms. It is built on top of NumPy, SciPy, and Matplotlib, making it an efficient and easy-to-use framework for data preprocessing, model training, and evaluation. Strengths:User-Friendly: With a consistent API, Scikit-learn is straightforward to use, making it accessible to beginners and experienced practitioners alike. Comprehensive Algorithms: It provides a wide range of algorithms for classification, regression, clustering, and dimensionality reduction. Excellent for Prototyping: The simplicity of Scikit-learn enables quick experimentation with different algorithms and parameter tuning.Weaknesses:Limited Deep Learning Support: Scikit-learn is not designed for deep learning tasks, which means it lacks the capabilities of TensorFlow and PyTorch for handling neural networks and large datasets.Conclusion: Making the Right ChoiceThe choice between TensorFlow, PyTorch, and Scikit-learn ultimately depends on your specific project requirements:Choose TensorFlow if you need a robust framework for deep learning in production environments, especially when scalability and deployment are priorities. Choose PyTorch if you value flexibility and ease of use for research and experimentation in deep learning. Choose Scikit-learn for traditional machine learning tasks where simplicity and a rich library of algorithms are essential.Each framework has its unique strengths and use cases, so consider your project's needs carefully. Happy coding!
Eric Stanley- 01 Apr, 2025
Framework Face-Off A Comparative Analysis of React, Vue.js, and Angular in 2025
In the ever-evolving landscape of web development, choosing the right framework can make all the difference in the success of a project. With so many options available, developers often find themselves weighing the pros and cons of various frameworks. In this post, we’ll conduct a comparative analysis of three of the most popular JavaScript frameworks in 2025: React, Vue.js, and Angular. Each framework has its unique strengths and weaknesses, making it essential to understand their differences before making a selection. React: The Flexible PowerhouseOverview: Developed and maintained by Facebook, React has gained immense popularity due to its component-based architecture and flexibility. It allows developers to build single-page applications with a focus on a seamless user experience. Pros:Large Ecosystem: React's ecosystem boasts a multitude of libraries and tools, making it easy to integrate with other technologies. Virtual DOM: The use of a virtual DOM enhances performance, enabling faster UI updates. Strong Community Support: With a vast community, developers can find plenty of resources, tutorials, and third-party libraries.Cons:Steep Learning Curve: For beginners, the JSX syntax and component lifecycle can be challenging to grasp. Frequent Updates: React's rapid evolution may result in breaking changes that require developers to stay up-to-date.Vue.js: The Progressive FrameworkOverview: Vue.js is often touted as the "progressive framework" because it can be adopted incrementally. It’s particularly favored for building interactive user interfaces and is known for its simplicity and ease of integration. Pros:Ease of Learning: Vue's clear and concise documentation makes it accessible for newcomers. Two-Way Data Binding: This feature simplifies the synchronization between the model and the view. Flexibility: Developers can structure their applications as they see fit, enabling a variety of approaches.Cons:Smaller Ecosystem: While growing, Vue's ecosystem is not as extensive as React's, which may limit options for certain projects. Less Corporate Backing: Although it has a strong community, Vue lacks the corporate backing that frameworks like React and Angular enjoy.Angular: The Complete FrameworkOverview: Angular, developed by Google, is a comprehensive framework that provides a robust solution for enterprise-level applications. Its opinionated structure promotes best practices and ensures consistency across large teams. Pros:Complete Solution: Angular comes with built-in features such as routing, state management, and form handling, reducing the need for additional libraries. TypeScript Support: The use of TypeScript enhances code quality and maintainability, making it a favorite among large development teams. Strong Community and Resources: With Google's backing, Angular benefits from extensive documentation and community support.Cons:Complexity: Angular’s comprehensive features can lead to a steep learning curve for new developers. Performance Overhead: The framework may introduce performance overhead in smaller applications due to its size and structure.Conclusion: Choosing the Right FrameworkWhen it comes to selecting a framework, the choice ultimately depends on the specific needs of your project. If you prioritize flexibility and a rich ecosystem, React may be the way to go. For those looking for simplicity and ease of integration, Vue.js could be the perfect fit. Meanwhile, if you're building a large-scale enterprise application that requires a complete solution, Angular stands out as a strong contender. In 2025, the landscape of web development continues to evolve, and each of these frameworks offers unique advantages. By understanding their strengths and weaknesses, developers can make informed decisions that lead to successful projects. Whether you're a seasoned developer or just starting out, weighing these frameworks against your project requirements will help you stay ahead in the fast-paced world of web development. Feel free to share your thoughts or experiences with these frameworks in the comments below! What factors influenced your choice?

Playwright framework implementation - Part 2
Hoping the you have completed part 1 of the post, let’s move on to part 2!! In this post, let's tryin make use of the files that we haven't used in the part 1. We are gonna create a new test for creating a new user and checking whether the user is landed in the My Account page after sign up First things first. Creating the files that we need for the next test. Run the below command from the project directory cd src/apps/automation-practice && touch data/create-account.data.json && touch data/my-account.data.json && touch locators/create-account.locator.ts && touch locators/my-account.locator.ts && touch pages/create-account.page.ts && touch pages/my-account.page.ts && touch tests/create-account.spec.ts && touch tests/my-account.spec.tsLet’s take a moment to plan on how to tackle the sign-up functionality. We know that we need a unique email and password everytime we need to test this. Considering this, let's use an existing package named faker-js. faker-js is a package that is used to provide dummy but valid data for testing. Fortunately, this package is already part of the package.json file and no action needed for this 😇 Now that we know that we need to create unique test data for every run, we also need to persist the existing data, so that we can use it later. Well, good news! We have that as well covered in the frameworkStep # 1Starting with adding test data to the *.data.json files As we discussed, the test data for new account creation is gonna use the faker-js, all we have to do is add an empty array to src/apps/automation-practice/data/create-account.data.json as below []And for the My Account page validation, we are going to validate whether the user is navigated to the My Account page after sign up. All we are doing here is to verify the title of the page matches with the My Account page title. Add the below to src/apps/automation-practice/data/my-account.data.json file { "title": "My Account Magento Commerce - website to practice selenium | demo website for automation testing | selenium practice sites" }Step # 2Now that we have the test data task out of our way, let's close the creation of page objects task by updating the *.locator.json files. Add the below code to src/apps/automation-practice/locators/create-account.locator.ts export const firstNameEdit = 'input[name="firstname"]'; export const lastNameEdit = 'input[name="lastname"]'; export const emailEdit = 'input[name="email"]'; export const passwordEdit = 'input[name="password"]'; export const passwordConfirm = 'input[name="password_confirmation"]'; export const createAccountButton = 'button:has-text("Create an Account")';Since, we are only validating the title in the My Account page and not any elements within the page, we can ignore the src/apps/automation-practice/locators/my-account.locator.ts file for nowStep # 3Start creating the necessary steps for the test. Add the below code to src/apps/automation-practice/pages/create-account.page.ts import path from "path"; import fs from "fs"; import type { Page, TestInfo } from "@playwright/test"; import { test } from "../fixtures"; import * as locators from "../locators/create-account.locator"; import * as actions from "@utils/base/web/actions"; import { appendFile } from "@home/src/utils/functions/file"; import { faker } from "@faker-js/faker";export default class CreateAccountPage { constructor(public page: Page, public workerInfo: TestInfo) {} async createAccount() { const dataFilePath = path.join( __dirname, "..", "data", "create-account.data.json" ); const firstName = faker.name.firstName(); const lastName = faker.name.lastName(); const email = faker.internet.email(); const password = faker.internet.password(); const account = { firstName, lastName, email, password, confirmPassword: password, }; await appendFile(dataFilePath, account); const data = JSON.parse(fs.readFileSync(dataFilePath, "utf-8")); await test.step( this.workerInfo.project.name + ": Enter first name: " + data[data.length - 1].firstName, async () => await actions.fill( this.page, locators.firstNameEdit, data[data.length - 1].firstName, this.workerInfo ) ); await test.step( this.workerInfo.project.name + ": Enter last name: " + data[data.length - 1].lastName, async () => await actions.fill( this.page, locators.lastNameEdit, data[data.length - 1].lastName, this.workerInfo ) ); await test.step( this.workerInfo.project.name + ": Enter email: " + data[data.length - 1].email, async () => await actions.fill( this.page, locators.emailEdit, data[data.length - 1].email, this.workerInfo ) ); await test.step( this.workerInfo.project.name + ": Enter password: " + data[data.length - 1].password, async () => await actions.fill( this.page, locators.passwordEdit, data[data.length - 1].password, this.workerInfo ) ); await test.step( this.workerInfo.project.name + ": Enter confirm password: " + data[data.length - 1].confirmPassword, async () => await actions.fill( this.page, locators.passwordConfirm, data[data.length - 1].confirmPassword, this.workerInfo ) ); await test.step( this.workerInfo.project.name + ": Click create account button ", async () => await actions.clickElement( this.page, locators.createAccountButton, this.workerInfo ) ); } }Add the below code to src/apps/automation-practice/pages/my-account.page.ts import type { Page, TestInfo } from "@playwright/test"; import * as data from "../data/my-account.data.json"; import * as actions from "@utils/base/web/actions";export default class MyAccountPage { constructor(public page: Page, public workerInfo: TestInfo) {} async verifyPageTitle() { await actions.verifyPageTitle(this.page, data.title, this.workerInfo); } }Step # 4Updating fixtures. Once we have added the steps that needs to be performed for the test, it's time for us to update the fixtures. Your src/apps/automation-practice/fixtures/index.ts file should looks like below import { test as baseTest } from "@playwright/test"; import CommonPage from "@common/pages/common.page"; import HomePage from "../pages/home.page"; import CreateAccountPage from "../pages/create-account.page"; import MyAccountPage from "../pages/my-account.page";type pages = { commonPage: CommonPage; homePage: HomePage; createAccountPage: CreateAccountPage; myAccountPage: MyAccountPage; };const testPages = baseTest.extend<pages>({ commonPage: async ({ page }, use, workerInfo) => await use(new CommonPage(page, workerInfo)), homePage: async ({ page }, use, workerInfo) => await use(new HomePage(page, workerInfo)), createAccountPage: async ({ page }, use, workerInfo) => await use(new CreateAccountPage(page, workerInfo)), myAccountPage: async ({ page }, use, workerInfo) => await use(new MyAccountPage(page, workerInfo)), });export const test = testPages; export const expect = testPages.expect; export const describe = testPages.describe;Step # 5Time to create our test. Before we add the test code to the *.spec.ts files, we need to add the locators in src/apps/automation-practice/locators/home.locator.ts to click on the Create Account link in home page Add the below code to src/apps/automation-practice/locators/home.locator.ts export const createAccountLink = "text=Create an Account";Add the below code to src/apps/automation-practice/tests/create-account.spec.ts import { test, describe } from "../fixtures"; import * as homePageLocators from "../locators/home.locator"; import * as createAccountLocators from "../locators/create-account.locator";test.beforeEach(async ({ homePage }) => { await homePage.navigateToAutomationPractice(); });describe("New Account", () => { test("Create new account", async ({ homePage, commonPage, createAccountPage, myAccountPage, }) => { await homePage.clickLink(homePageLocators.createAccountLink); await commonPage.waitForAnimationEnd( createAccountLocators.createAccountButton ); await createAccountPage.createAccount(); await myAccountPage.verifyPageTitle(); }); });Add the below code to src/apps/automation-practice/tests/my-account.spec.ts import { test, describe } from "../fixtures";describe("My Account", () => { test("Verify account page url and title", async ({ myAccountPage }) => { await myAccountPage.verifyPageTitle(); }); });Time to test 🍹Step # 6Change the testMatch option in src/apps/automation-practice/playwright.config.ts as below testMatch: "tests/create-account.spec.ts",and run yarn test:automation-practice Hoping that you have done everything right until now, you would now be able to see something like belowAnd you should be able to see the newly created users test data in src/apps/automation-practice/data/create-account.data.json That's all for today. Happie coding 📣

Playwright framework implementation - Part 1
Let’s see the implementation of the playwright framework from the github repo which I created as part of my hands-on effort. Now before I begin, I just wanted to give a little heads-up. This could be a long post and you might have to spend some quality time in order to make the framework up and running. That being said, I will try to reduce the words as much as possible and explain things clear. However, just keep an open mind for now 😉 Couple of things before we start. Let’s quickly spend some time in understanding how the framework is structured, so that you can have a mental picture of how things work when you start making changes. Framework structure . ├── 📁 src │ ├── 📁 apps │ │ └── 📁 common │ │ │ ├── 📁 pages │ │ │ │ ├── 📃 common.page.ts # common page shared b/w apps │ │ └── 📁 app 01 │ │ │ ├── 📁 data │ │ │ │ ├── 📃 page_01.data.json # data for app 01, page 01 │ │ │ │ ├── 📃 page_02.data.json # data for app 01, page 02 │ │ │ │ ├── 📃 ... │ │ │ │ ├── 📃 page_n.data.json # data for app 01, page n │ │ │ ├── 📁 fixtures │ │ │ │ └── 📃 index.ts # app 01 specific fixtures │ │ │ ├── 📁 locators │ │ │ │ ├── 📃 page_01.locator.ts # locators for app 01, page 01 │ │ │ │ ├── 📃 page_02.locator.ts # locators for app 01, page 02 │ │ │ │ ├── 📃 ... │ │ │ │ ├── 📃 page_n.locator.ts # locators for app 01, page n │ │ │ ├── 📁 pages │ │ │ │ ├── 📃 page_01.page.ts # functions for app 01, page 01 │ │ │ │ ├── 📃 page_02.page.ts # functions for app 01, page 02 │ │ │ │ ├── 📃 ... │ │ │ │ ├── 📃 page_n.page.ts # functions for app 01, page n │ │ │ └── 📁 tests │ │ │ │ ├── 📃 page_01.spec.ts # tests for app 01, page 01 │ │ │ │ ├── 📃 page_02.spec.ts # tests for app 01, page 02 │ │ │ │ ├── 📃 ... │ │ │ │ ├── 📃 page_n.spec.ts # tests for app 01, page n │ │ │ ├── 📃 config.json # config data for app 01 │ │ │ ├── 📃 package.json # scripts for app 01 │ │ │ ├── 📃 playwright.config.ts # config details for app 01 │ │ └── 📁 app 02 │ │ │ ├── 📁 data │ │ │ │ ├── 📃 page_01.data.json # data for app 02, page 01 │ │ │ │ ├── 📃 page_02.data.json # data for app 02, page 02 │ │ │ │ ├── 📃 ... │ │ │ │ ├── 📃 page_n.data.json # data for app 02, page n │ │ │ ├── 📁 fixtures │ │ │ │ └── 📃 index.ts # app 02 specific fixtures │ │ │ ├── 📁 locators │ │ │ │ ├── 📃 page_01.locator.ts # locators for app 02, page 01 │ │ │ │ ├── 📃 page_02.locator.ts # locators for app 02, page 02 │ │ │ │ ├── 📃 ... │ │ │ │ ├── 📃 page_n.locator.ts # locators for app 02, page n │ │ │ ├── 📁 pages │ │ │ │ ├── 📃 page_01.page.ts # functions for app 02, page 01 │ │ │ │ ├── 📃 page_02.page.ts # functions for app 02, page 02 │ │ │ │ ├── 📃 ... │ │ │ │ ├── 📃 page_n.page.ts # functions for app 02, page n │ │ │ └── 📁 tests │ │ │ │ ├── 📃 page_01.spec.ts # tests for app 02, page 01 │ │ │ │ ├── 📃 page_02.spec.ts # tests for app 02, page 02 │ │ │ │ ├── 📃 ... │ │ │ │ ├── 📃 page_n.spec.ts # tests for app 02, page n │ │ │ ├── 📃 config.json # config data for app 02 │ │ │ ├── 📃 package.json # scripts for app 02 │ │ │ ├── 📃 playwright.config.ts # config details for app 02 │ │ └── 📁 ... │ │ └── 📁 app n │ │ │ ├── 📁 data │ │ │ │ ├── 📃 page_01.data.json # data for app n, page 01 │ │ │ │ ├── 📃 page_02.data.json # data for app n, page 02 │ │ │ │ ├── 📃 ... │ │ │ │ ├── 📃 page_n.data.json # data for app n, page n │ │ │ ├── 📁 fixtures │ │ │ │ └── 📃 index.ts # app n specific fixtures │ │ │ ├── 📁 locators │ │ │ │ ├── 📃 page_01.locator.ts # locators for app n, page 01 │ │ │ │ ├── 📃 page_02.locator.ts # locators for app n, page 02 │ │ │ │ ├── 📃 ... │ │ │ │ ├── 📃 page_n.locator.ts # locators for app n, page n │ │ │ ├── 📁 pages │ │ │ │ ├── 📃 page_01.page.ts # functions for app n, page 01 │ │ │ │ ├── 📃 page_02.page.ts # functions for app n, page 02 │ │ │ │ ├── 📃 ... │ │ │ │ ├── 📃 page_n.page.ts # functions for app n, page n │ │ │ └── 📁 tests │ │ │ │ ├── 📃 page_01.spec.ts # tests for app n, page 01 │ │ │ │ ├── 📃 page_02.spec.ts # tests for app n, page 02 │ │ │ │ ├── 📃 ... │ │ │ │ ├── 📃 page_n.spec.ts # tests for app n, page n │ │ │ ├── 📃 config.json # config data for app n │ │ │ ├── 📃 package.json # scripts for app n │ │ │ ├── 📃 playwright.config.ts # config details for app n │ └── 📁 utils │ │ ├── 📁 base │ │ │ └── 📁 web │ │ │ │ ├── 📃 actions.ts # actions in web applications │ │ │ │ └── 📃 screenshots.ts # screenshot functions │ │ ├── 📁 functions (utility functions) │ │ ├── 📁 packages (other reusable packages) │ │ └── 📁 reports │ │ │ └── 📃 custom-reporter.ts # custom reporter to pretty print in console ├── 📃 config.init.ts # configuration initializer ├── 📃 global-setup.ts ├── 📃 global-teardown.ts ├── 📃 package.json ├── 📃 playwright.config.ts # global configSo, why is the structure important 🤔 Coz' it gives you the view of the things that you need to add for using this framework for a brand new application. The only problem is to know how to do it sequentially. For example, as soon as you look at the framework structure, you might already understand that you need app_name folder within the src/apps folder within which you would need to create all the sub-folders as shown.The question is which one should I create first and how are these folders linked?Moving on.. First things first. Listed below are the necessary pre-requisites for the framework implementation.Node JS - v18.1.0 or above (what I used while developing) IDE of your choice (VS Code recommended)and... that's it! 💁♂️ - If you want to use different versions of node in different projects, use nvmAlright, now that we have completed the pre-requisites, let’s start with the rest of the implementation 🔰Step # 1Clone the git repo using the below command git clone git@github.com:eric-stanley/playwright-framework.gitand cd into the cloned folderStep # 2Let’s assume that the application under test is named automation-practice Start creating the necessary 📁 and 📃 using the below commands. mkdir src/apps/automation-practice && cd src/apps/automation-practice && mkdir data && mkdir fixtures && mkdir locators && mkdir pages && mkdir tests && touch config.json && touch package.json && touch playwright.config.ts && touch data/home.data.json && touch fixtures/index.ts && touch locators/home.locator.ts && touch pages/home.page.ts && touch tests/home.spec.tsCopy the below code and paste it in src/apps/automation-practice/config.json. This file has the environment specific url's for the AUT (Application Under Test) { "env": { "dev": { "url": "https://magento.softwaretestingboard.com/" }, "test": { "url": "https://magento.softwaretestingboard.com/" }, "uat": { "url": "https://magento.softwaretestingboard.com/" } } }🔔 Assuming that we have the same url for all environments 😏 Copy the below code and paste it in src/apps/automation-practice/package.json. Here we define the commands that we use to trigger the necessary run Note: This is a dependent file and will not run standalone. All dependencies for the framework is part of the main package.json which resides in the parent folder { "name": "automation-practice", "version": "1.0.0", "private": true, "author": "Eric Stanley", "license": "MIT", "scripts": { "test": "APP_NAME=automation-practice NODE_ENV=dev playwright test", "test:debug": "APP_NAME=automation-practice NODE_ENV=dev PWDEBUG=1 playwright test", "report": "npx playwright show-report reports/playwright-report", "allure": "npx allure generate reports/allure/allure-result -o reports/allure/allure-report --clean && npx allure open reports/allure/allure-report" } }🔔 Change the author to your name Copy the below code and paste it in src/apps/automation-practice/playwright.config.ts. This file has the app specific playwright config. import { PlaywrightTestConfig, devices } from "@playwright/test";const config: PlaywrightTestConfig = { testDir: "tests", testMatch: "tests/*.spec.ts", timeout: 30 * 1000, retries: 3, workers: 3, globalSetup: require.resolve("@home/global-setup"), globalTeardown: require.resolve("@home/global-teardown"), expect: { timeout: 20000, }, use: { headless: true, actionTimeout: 0, trace: "retain-on-failure", ignoreHTTPSErrors: true, video: "on-first-retry", screenshot: "only-on-failure", acceptDownloads: true, colorScheme: "dark", launchOptions: { slowMo: 500, }, }, reporter: [ ["list"], [ "json", { outputFile: "reports/json-reports/json-report.json", }, ], [ "html", { outputFolder: "reports/playwright-report/", open: "never", }, ], [ "allure-playwright", { outputFolder: "reports/allure/allure-result/", open: "never", }, ], ], projects: [ { name: "chromium", use: { ...devices["Desktop Chrome"] }, }, { name: "firefox", use: { ...devices["Desktop Firefox"] }, }, { name: "webkit", use: { ...devices["Desktop Safari"] }, }, ], };export default config;Now that we have completed the basic setup, let’s move on to step # 3Step # 3Let’s start with writing a basic test for verifying if the home page url contains a specific text and title equals a specific text Starting with the data first. Add the below data in src/apps/automation-practice/data/home.data.json { "urlContains": "softwaretestingboard", "title": "Home Page - Magento eCommerce - website to practice selenium | demo website for automation testing | selenium practice sites | selenium demo sites | best website to practice selenium automation | automation practice sites Magento Commerce - website to practice selenium | demo website for automation testing | selenium practice sites" }Now that we have the data in place, lets start writing the functions for the test. Add the below code to src/apps/automation-practice/pages/home.page.ts import type { Page, TestInfo } from "@playwright/test"; import { test, expect } from "../fixtures"; import * as data from "../data/home.data.json"; import * as actions from "@utils/base/web/actions";export default class HomePage { constructor(public page: Page, public workerInfo: TestInfo) {} async navigateToAutomationPractice() { await actions.navigateTo(this.page, process.env.URL, this.workerInfo); const url = this.page.url(); await test.step( this.workerInfo.project.name + ": Check if URL contains " + data.urlContains, async () => expect(url).toContain(data.urlContains) ); } async verifyPageTitle() { await actions.verifyPageTitle(this.page, data.title, this.workerInfo); } }At this point, you will get an error with the fixtures import line stating that, fixtures/index.ts is not a module. This is fine. We are going to tackle this next 😉 But before that, let's take a moment to look at the code in home.page.ts. Basically, I have added two functions in this class; one for navigating to the home page navigateToAutomationPractice() and the other one to verify the title of the page verifyPageTitle(). The actions is a consolidated list of all actions (or atleast most of the actions) that you could possibly perform in a web page, since we will be using these actions in almost all of our pages, it's only logical to have this as a separate utility! Let’s tackle the fixtures error now. Add the below code in src/apps/automation-practice/fixtures/index.ts import { test as baseTest } from "@playwright/test"; import CommonPage from "@common/pages/common.page"; import HomePage from "../pages/home.page";type pages = { commonPage: CommonPage; homePage: HomePage; };const testPages = baseTest.extend<pages>({ commonPage: async ({ page }, use, workerInfo) => { await use(new CommonPage(page, workerInfo)); }, homePage: async ({ page }, use, workerInfo) => { await use(new HomePage(page, workerInfo)); }, });export const test = testPages; export const expect = testPages.expect; export const describe = testPages.describe;What we do here is basically extending the test object in @playwright/test to add all pages in our application, so that when we start writing our tests, we will be able to easily pull the page objects and its associated functions without creating a new object everytime when we need to access the home.page.ts which might inturn cause a lot of duplication in code. In other words, every time we access the test object from @playwright/test, it automatically injects all the app specific pages in it. Let’s see how it happens next Add the below code in src/apps/automation-practice/tests/home.spec.ts import { test, describe } from "../fixtures";describe("Home", () => { test("Verify home page url and title", async ({ homePage }) => { await homePage.navigateToAutomationPractice(); await homePage.verifyPageTitle(); }); });As you can see the second argument in the test object takes in a function and since we have already extended the homePage object within the test object, we are able to access it with in the function And..... that's it ⏰ Now, before we run the test, there is one last place where we need to update few things which is basically the script object in package.json which is present in the parent directory.Remember, this is not the package.json that we edited in step # 2. This is the one that is present in your parent (root) directoryAdd the below code in the scripts section in package.json file "test:automation-practice": "yarn workspace automation-practice test", "test:debug:automation-practice": "yarn workspace automation-practice test:debug", "report:automation-practice": "yarn workspace automation-practice report", "allure:automation-practice": "yarn workspace automation-practice allure"Once you are done with editing the package.json file in your root directory, you package.json file would look something like below { "name": "playwright-hands-on", "version": "1.0.0", "private": true, "description": "", "workspaces": ["src/apps/*"], "scripts": { "test": "yarn workspace ui-testing-playground test", "test:debug": "yarn workspace ui-testing-playground test:debug", "report": "yarn workspace ui-testing-playground report", "allure": "yarn workspace ui-testing-playground allure", "test:ui-testing-playground": "yarn workspace ui-testing-playground test", "test:debug:ui-testing-playground": "yarn workspace ui-testing-playground test:debug", "report:ui-testing-playground": "yarn workspace ui-testing-playground report", "allure:ui-testing-playground": "yarn workspace ui-testing-playground allure", "test:automation-practice": "yarn workspace automation-practice test", "test:debug:automation-practice": "yarn workspace automation-practice test:debug", "report:automation-practice": "yarn workspace automation-practice report", "allure:automation-practice": "yarn workspace automation-practice allure" }, "author": "Eric Stanley", "license": "MIT", "devDependencies": { "@playwright/test": "^1.25.2", "@types/adm-zip": "^0.5.0", "adm-zip": "^0.5.9", "allure-commandline": "^2.18.1", "allure-playwright": "^2.0.0-beta.19", "colors": "^1.4.0", "playwright": "^1.25.2", "ts-node": "^10.9.1", "typescript": "^4.8.3" } }Step # 4Time to test If you are running the project for the first time run the yarn command from your parent folder to install all dependencies. Once you are done with installing the dependencies, run the below command yarn test:automation-practiceIf you have done everything right, you might probably see something like belowYou might or might not get the Slow test file warning. You can play around with it by changing the timeout value in playwright.config.ts in src/apps/automation-practice folder 🙋 Now, does that mean that we are done?Yep!I told you that this post would be a long one and we have covered a lotta ground until now, but why do we have the home.locator.ts file in the locators folder. And how do we compare screenshots 🤔 Like I said, this could take some time, but at the same time, a lot has been already done. See you in the next post with implementing more features 🥏 Feel free to add your thoughts and comments in the comments section and I will try to address those in time ⏰