পাঠ ২৯ · ৫৭-এর মধ্যে · মডিউল ৭
Home / Courses / Software Testing & Quality Assurance / টেস্ট অটোমেশন

পেজ অবজেক্ট প্যাটার্ন

The page object pattern
১০ মিনিট পড়া মধ্যবর্তী · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • পেজ অবজেক্ট প্যাটার্ন কী এবং এটি কোন সমস্যা সমাধান করে (UI পরিবর্তনে টেস্ট ভেঙে যাওয়া)
  • কেন টেস্ট কোড raw সিলেক্টরের বদলে পেজ অবজেক্টের মেথডের সাথে কথা বলা উচিত
  • একটি সিমুলেটেড dict-ভিত্তিক "DOM" ও পেজ অবজেক্ট ক্লাস বাস্তবে বানিয়ে দেখা
  • একটি সত্যিকারের, চালানো ডেমোতে দেখা — UI গঠন বদলালেও একই টেস্ট কোড অক্ষত থাকে

১ · সমস্যাটি কী

ধরা যাক একটি UI টেস্ট সরাসরি সিলেক্টর ব্যবহার করে লেখা: dom["username_field"], dom["login_button"] ইত্যাদি। এখন যদি ডেভেলপাররা UI রিডিজাইন করার সময় এই আইডিগুলোর নাম পাল্টে দেন (যেমন username_field থেকে user_input), তাহলে যতগুলো টেস্ট সেই সিলেক্টর সরাসরি ব্যবহার করেছিল, প্রতিটি টেস্ট আলাদাভাবে খুঁজে বের করে ঠিক করতে হবে — ৫টি টেস্ট হলে সহনীয়, কিন্তু ৫০০টি টেস্ট হলে এটি একটি বড় রক্ষণাবেক্ষণ দুঃস্বপ্ন হয়ে দাঁড়ায়।

টেস্ট কোড page.login(u, p) পেজ অবজেক্ট সিলেক্টর ম্যাপিং এখানে DOM real আইডি/সিলেক্টর
DOM বদলালে শুধু মাঝের স্তর (পেজ অবজেক্ট) আপডেট হয় — টেস্ট কোড কখনো DOM-এর real সিলেক্টর জানে না, তাই অপরিবর্তিত থাকে।

২ · একটি সিমুলেটেড পেজ অবজেক্ট তৈরি করা

যেহেতু এখানে সত্যিকারের ব্রাউজার (Selenium/Playwright) নেই, নিচে একটি সিমুলেটেড DOM — একটি সাধারণ Python ডিকশনারি — এবং একটি LoginPage ক্লাস তৈরি করা হয়েছে। মূল ধারণাটি একই: টেস্ট কোড শুধু page.login(...) ও page.get_error_message() কল করে, DOM-এর ভেতরের real কী (key) কখনো সরাসরি স্পর্শ করে না। এরপর দুটো ভিন্ন গঠনের DOM সংস্করণ তৈরি করা হয়েছে (যেন UI রিডিজাইন হয়েছে) — দেখা যাক পেজ অবজেক্টের সিলেক্টর-ম্যাপিং একবার সরবরাহ করলেই একই টেস্ট কোড দুটোতেই পাস করে কি না।

Python
import unittest

# ---------- সিমুলেটেড DOM সংস্করণ ১ (আসল UI) ----------
def make_dom_v1():
    return {
        "username_field": {"value": ""},
        "password_field": {"value": ""},
        "error_message":  {"text": ""},
    }

# ---------- সিমুলেটেড DOM সংস্করণ ২ (UI রিডিজাইনের পর — আইডি সম্পূর্ণ বদলে গেছে) ----------
def make_dom_v2():
    return {
        "user_input":   {"value": ""},
        "pass_input":   {"value": ""},
        "error_banner": {"text": ""},
    }

def fake_backend_authenticate(username, password):
    # বাস্তব সার্ভার হলে এখানে একটি নেটওয়ার্ক কল হতো — এখানে সিমুলেটেড
    return username == "admin" and password == "s3cret"

def simulate_click_login(dom, selectors):
    """একটি বাস্তব ব্রাউজারে এটি হতো লগইন বাটনের ক্লিক-হ্যান্ডলার। এখানে সরাসরি ফাংশন কল দিয়ে সিমুলেটেড।"""
    username = dom[selectors["username"]]["value"]
    password = dom[selectors["password"]]["value"]
    if fake_backend_authenticate(username, password):
        dom[selectors["error"]]["text"] = ""
    else:
        dom[selectors["error"]]["text"] = "Invalid username or password"

class LoginPage:
    """পেজ অবজেক্ট: টেস্ট কোড শুধু এই ক্লাসের মেথডের সাথে কথা বলে, DOM-এর real কী-এর সাথে নয়।"""
    def __init__(self, dom, selectors):
        self.dom = dom
        self.selectors = selectors  # UI বদলালে এই একটি ম্যাপিংই আপডেট করতে হয়

    def login(self, username, password):
        self.dom[self.selectors["username"]]["value"] = username
        self.dom[self.selectors["password"]]["value"] = password
        simulate_click_login(self.dom, self.selectors)

    def get_error_message(self):
        return self.dom[self.selectors["error"]]["text"]

# দুটো DOM সংস্করণের জন্য দুটো আলাদা সিলেক্টর-ম্যাপিং — এটিই একমাত্র জিনিস যা বদলায়
SELECTORS_V1 = {"username": "username_field", "password": "password_field", "error": "error_message"}
SELECTORS_V2 = {"username": "user_input",     "password": "pass_input",     "error": "error_banner"}

class LoginPageTestsBase:
    """এই দুটো টেস্ট মেথড একবারই লেখা — নিচে অক্ষত অবস্থায় দুটো DOM ভার্সনের বিপক্ষে চলবে।"""
    dom_factory = None
    selectors = None

    def setUp(self):
        self.page = LoginPage(self.dom_factory(), self.selectors)

    def test_wrong_login_shows_error(self):
        self.page.login("admin", "wrongpass")
        self.assertEqual(self.page.get_error_message(), "Invalid username or password")

    def test_correct_login_clears_previous_error(self):
        self.page.login("admin", "wrongpass")
        self.assertEqual(self.page.get_error_message(), "Invalid username or password")
        self.page.login("admin", "s3cret")
        self.assertEqual(self.page.get_error_message(), "")

class LoginPageTestsOnDomV1(LoginPageTestsBase, unittest.TestCase):
    dom_factory = staticmethod(make_dom_v1)
    selectors = SELECTORS_V1

class LoginPageTestsOnDomV2(LoginPageTestsBase, unittest.TestCase):
    dom_factory = staticmethod(make_dom_v2)
    selectors = SELECTORS_V2

loader = unittest.TestLoader()
suite = unittest.TestSuite()
suite.addTests(loader.loadTestsFromTestCase(LoginPageTestsOnDomV1))
suite.addTests(loader.loadTestsFromTestCase(LoginPageTestsOnDomV2))
unittest.TextTestRunner(verbosity=2).run(suite)

    
চারটি টেস্টই পাস করে — LoginPageTestsOnDomV1-এর দুটো এবং LoginPageTestsOnDomV2-এর দুটো, দুটো সম্পূর্ণ ভিন্ন dict কাঠামোর (আলাদা কী-নাম) বিপক্ষে। লক্ষ্য করুন LoginPageTestsBase-এর test_wrong_login_shows_error ও test_correct_login_clears_previous_error মেথড দুটো একবারই লেখা হয়েছে — কোনো কপি-পেস্ট নেই, দুটো সাবক্লাস শুধু dom_factory ও selectors সরবরাহ করেছে। যদি এই টেস্ট দুটো সরাসরি dom["username_field"]-এর মতো raw কী ব্যবহার করত, V2-এর বিপক্ষে সেগুলো KeyError দিয়ে ব্যর্থ হতো — পেজ অবজেক্টের সিলেক্টর-ম্যাপিং স্তরটিই এই বিচ্ছিন্নতা সম্ভব করেছে।
মূল কথা · Key takeaway

পেজ অবজেক্ট প্যাটার্নের আসল মূল্য রক্ষণাবেক্ষণে — UI-এর গঠন বদলালে একটি নির্দিষ্ট, ছোট জায়গায় (সিলেক্টর ম্যাপিং) পরিবর্তন করলেই চলে, বাকি টেস্ট কোড অক্ষত থাকে। এটি M7/L31-এ আলোচিত DRY নীতিরই একটি বিশেষ প্রয়োগ — সিলেক্টরের জ্ঞান একবারই লেখা, বহুবার পুনরাবৃত্তি নয়।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ উপরের কোড সেলে যদি LoginPageTestsBase-এর টেস্ট মেথডগুলো সরাসরি self.page.dom["username_field"]["value"] = "admin"-এর মতো লিখত (পেজ অবজেক্টের login() মেথড ব্যবহার না করে), তাহলে V2-এর বিপক্ষে কী ঘটত?

KeyError: 'username_field' — কারণ V2-এর dom-এ সেই কী নেই, সেখানে আছে "user_input"। এটাই মূল সমস্যা যা পেজ অবজেক্ট সমাধান করে: raw সিলেক্টর টেস্টে লেখা থাকলে, প্রতিটি টেস্ট UI-এর নির্দিষ্ট গঠনের সাথে সরাসরি বাঁধা পড়ে যায়।

প্র ০২ LoginPageTestsOnDomV1 ও LoginPageTestsOnDomV2 ক্লাস দুটোতে test_wrong_login_shows_error মেথডের কোড ঠিক কতবার লেখা হয়েছে, এবং কেন এটি গুরুত্বপূর্ণ?

মাত্র একবার — LoginPageTestsBase ক্লাসে। দুটো সাবক্লাস শুধু সেই বেস ক্লাস থেকে ইনহেরিট করে এবং শুধু dom_factory/selectors সরবরাহ করে। এটি গুরুত্বপূর্ণ কারণ এটি প্রমাণ করে দুটো DOM সংস্করণের বিপক্ষে আসলেই হুবহু একই টেস্ট-লজিক চলছে, দুটো আলাদা করে লেখা কিন্তু কাকতালীয়ভাবে মিলে যাওয়া কোড নয়।

প্র ০৩ পেজ অবজেক্ট প্যাটার্নে get_error_message()-এর মতো মেথড থাকার সুবিধা কী, যদি টেস্ট নিজেই page.dom[page.selectors["error"]]["text"] লিখে সরাসরি পড়ে নিতে পারত?

get_error_message() টেস্টকে selectors ডিকশনারির গঠন সম্পর্কেও অজ্ঞ রাখে — টেস্ট শুধু জানে "একটি এরর মেসেজ পাওয়া যায়", কীভাবে তা DOM থেকে বের করা হয় তা জানার দরকার নেই। এটি বিচ্ছিন্নতাকে আরও এক ধাপ গভীর করে — ভবিষ্যতে যদি এরর মেসেজ বের করার পদ্ধতিই বদলে যায় (যেমন একাধিক এলিমেন্ট একসাথে চেক করতে হয়), শুধু পেজ অবজেক্টের মেথডের ভেতরের লজিক বদলালেই চলবে।

অনুশীলন

  1. চিন্তা করুন: একটি রেজিস্ট্রেশন ফর্ম পেজের জন্য একটি RegistrationPage পেজ অবজেক্ট ডিজাইন করতে চাইলে, কোন কোন মেথড (যেমন register(name, email, password), get_validation_errors()) থাকা উচিত বলে আপনার মনে হয়?

    সাধারণ প্যাটার্ন: প্রতিটি সম্পূর্ণ ব্যবহারকারী-কাজ (যেমন ফর্ম পূরণ করে সাবমিট করা) একটি একক মেথড হয়, আর প্রতিটি "পেজ থেকে তথ্য পড়া" কাজ (যেমন এরর মেসেজ, সাকসেস মেসেজ, বর্তমান ফিল্ড ভ্যালু) একটি গেটার মেথড হয়। টেস্ট কখনো ফর্মের individual ইনপুট ফিল্ডের সিলেক্টর সরাসরি জানবে না।

  2. পরীক্ষা করুন: উপরের কোড সেলে SELECTORS_V2-এ "error"-এর মান "error_banner"-এর বদলে ভুল করে "error_message" (V1-এর নাম) লিখে Run চেপে দেখুন কী ঘটে।

    LoginPageTestsOnDomV2-এর দুটো টেস্টই KeyError: 'error_message' দিয়ে ERROR (ব্যর্থ) দেখাবে, কারণ V2-এর dom-এ সেই কী নেই। এটি স্পষ্টভাবে দেখায় সিলেক্টর-ম্যাপিং ভুল হলে ঠিক কোথায় সমস্যা ধরা পড়ে — এবং কেন এই ম্যাপিং সঠিকভাবে রক্ষণাবেক্ষণ করা গুরুত্বপূর্ণ।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ টেস্ট ডিজাইন টেকনিক, ইউনিট টেস্টিং, টেস্ট ডাবলস, ইন্টিগ্রেশন টেস্টিং, কভারেজ, অটোমেশন, পারফরম্যান্স ও সিকিউরিটি টেস্টিং, অ্যাডভান্সড টেকনিক ও CI/CD ইন্টিগ্রেশন — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
  • ফুল-স্ট্যাক অ্যাপের জন্য টেস্টিং পিরামিড সহোদর কোর্স ব্রাউজার-ভিত্তিক E2E টেস্টে পেজ অবজেক্ট প্যাটার্ন বাস্তবে কোথায় বসে তার প্রেক্ষাপট।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics, Full-Stack Web Frameworks, Mobile App Development, Ethics in Computing & AI Safety ও Software Testing & Quality Assurance — সব এক জায়গায়।
আগের পাঠ
কী অটোমেট করবেন, কী ম্যানুয়াল রাখবেন