Selenium – Page Object คืออะไร?

Page Object เป็น design pattern ในการเขียนโค้ดสำหรับ automation testing. แนวคิดก็คือสร้าง Class ที่ทำหน้าที่เป็น interface ระหว่าง application และ test case.

ก่อนที่เราจะพูดเรื่อง Page Object, ผมขอเกริ่นนำก่อนว่า Page Object นี้เกิดมาเพื่อสิ่งใด? เริ่มต้นด้วยตัวอย่างการเขียนเทสแบบไม่ใช้ Page Object (จากนี้ไปจะเรียก Page Object Model ย่อๆว่า POM).

 

Contents

ปัญหาของการเขียนโค้ดโดยไม่ใช้ POM

บัวบานขอยกตัวอย่างการ Search flight ในหน้า Home ของ AirAsia.com. Test case จะทำตามลำดับดังนี้:

searchFlight-activity

 

โค้ดของ Test case จะเป็นดังนี้:

public void SearchFlight_No_PageObject()
{
	driver = new ChromeDriver();
	// Open airasia.com
	driver.Navigate().GoToUrl(@"http://www.airasia.com/ot/en/home.page");

	// Select Origin
	driver.FindElement(By.CssSelector(".stations-container > .expand-icon")).Click();
	System.Threading.Thread.Sleep(1500);
	driver.FindElement(By.CssSelector("#fromFlyoutBody li[data-value='DMK']")).Click();
	System.Threading.Thread.Sleep(1500);

	// Select Destination
	driver.FindElement(By.CssSelector("#toFlyoutBody li[data-value='SYD']")).Click();
	System.Threading.Thread.Sleep(1500);

	// Select Depart Date
	driver.FindElement(By.Id("search_from_date")).SendKeys("26/09/2016");
	driver.FindElement(By.Id("search_from_date")).SendKeys(Keys.Enter);
	System.Threading.Thread.Sleep(1500);

	// Select Return Date
	driver.FindElement(By.Id("search_to_date")).SendKeys("27/09/2016");
	driver.FindElement(By.Id("search_to_date")).SendKeys(Keys.Enter);
	System.Threading.Thread.Sleep(1500);

	// Click Submit
	driver.FindElement(By.Id("searchButton")).Click();

	// Assert Depart > Cities
	string DepartCities = driver.FindElement(By.CssSelector("#availabilityForm div.avail-header-cities")).Text;
	Assert.That(DepartCities, Is.EqualTo("(Bangkok - Don Mueang Sydney)"));
}

ดูเผินๆโค้ดด้านบนก็สวยงามดีนัก แต่ถ้าเราทำแบบนี้ต่อไปเรื่อยๆ จนมีโค้ดสัก 10,000 บรรทัด, เราจะพบปัญหาดังต่อไปนี้:

  1. มีโค้ดซ้ำซ้อนกันมาก.
    เนื่องจากเทสเคสมักจะมีจุดที่เหมือนๆกัน เช่นต้องเลือก Origin, Destination, ฯลฯ.
    เราแก้ปัญหานี้ได้โดยการเขียนฟังก์ชันเพื่อเรียกใช้ซ้ำได้. แต่ถ้าเรามีไฟล์ test case หลายไฟล์ ก็จะมีปัญหาเรื่อง scope ของฟังก์ชัน, นอกจากนี้จะเกิดการงงมากๆว่าจะเรียกใช้ฟังก์ชันไหนดี เพราะเราอาจจะต้องสร้างฟังก์ชันหลายร้อยฟังก์ชันอยู่ในไฟล์เดียวกัน.
  2. บำรุงรักษายากและเสียเวลามาก.
    ลองนึกภาพว่า airasia เปลี่ยนวิธีเลือก Origin จากที่ให้กดจาก <a> มาเป็นเลือกจาก dropdown, เราต้องเปลี่ยนขั้นตอนในการเลือก Origin ซึ่งมีอยู่ในทุก test case. ถ้าเรามี test case เยอะๆ ก็ต้องมาไล่แก้จนปวดหัว.
  3. อ่านโค้ดไม่รู้เรื่อง.
    ในเวลาที่ automate tests ทำงานผิดปกติ, เราต้องมาไล่ดูว่าผิดที่ขั้นตอนไหนและขั้นตอนนั้นต้องการทำอะไร.
    ลองอ่านโค้ดด้านล่างดูครับ รู้มั้ยว่าขั้นตอนนี้คือการทำอะไร?

    driver.FindElement(By.XPath("//div[@id='ui-datepicker-div']//td[not(contains(@class,'ui-state-disabled'))]/a[text()='26']")).Click();

    ถ้าเราเพิ่งเขียนไม่กี่ชั่วโมง เราจะรู้ครับ. แต่ถ้าผ่านไปหนึ่งอาทิตย์ หนึ่งเดือน หรือหนึ่งปี, เราไม่มีทางรู้เลยว่าบรรทัดนี้ต้องการทำอะไรกันแน่.

  4. หลายๆคนมาช่วยกันทำงานได้ยาก.
    ในกรณีที่เราทำงานกันหลายคนบนโปรเจคเดียวกัน, ถ้าเราไม่ออกแบบ package ให้เหมาะสม จะทำให้เกิดปัญหาเช่น มีฟังก์ชันที่ไม่เกี่ยวข้องกันเลยไปอยู่ในไฟล์เดียวกัน ซึ่งมักเป็นสาเหตุให้คนหลายๆคนต้องไปแก้ไฟล์เดียวกัน. สุดท้ายก็จะมีปัญหาตอน merge.

ปัญหา 4 ข้อด้านบนมักจะไม่เกิดขึ้น ถ้าซอฟต์แวร์คุณเล็กมากๆ และไม่มีการอัพเดทเลย. แต่ในโลกปัจจุบันเราไม่มีซอฟต์แวร์แบบนั้นหรอกนะฮะ ยิ่งทุกวันนี้เน้นออกอัพเดทกันทุกอาทิตย์หรือสองอาทิตย์, แก้โค้ด automate tests กันหลังอานเลยล่ะครับ.
โดยสรุปแล้ว เราควรออกแบบ automate tests ให้รองรับการเปลี่ยนแปลง และต้องแก้ไขปัญหา 4 ข้อด้านบน.

Page Object ช่วยอะไรได้?

เนื่องจาก application เวอชันใหม่นั้นมักจะไม่เปลี่ยนแปลง workflow ของการใช้งาน, test case และ application interface จึงมักจะเหมือนเดิมเสมอ. ส่วนที่จะถูกเปลี่ยนคือ behavior ภายในของแต่ละ interface. ยกตัวอย่างเช่น การเลือกวันเดินทางนั้นอาจมีการเปลี่ยนรูปร่างของปฏิทิน แต่ application interface ยังคงเดิมก็คือต้องใส่วันเดือนปี.

แนวคิดของ POM คือการสร้าง class ขึ่นมาเพื่อใช้เป็น interface ให้กับ application.  ถ้ามีการเปลี่ยนแปลง application ก็ให้มาแก้ที่ class แทนที่จะไปแก้ที่ test case. โค้ดของ Test case ข้างล่างแสดงการเรียกใช้ class Home และ SearchDetails ที่สร้างตามแนวคิด POM. จะเห็นได้ว่าโค้ดอ่านง่ายและเข้าใจง่ายขึ้นกว่าเดิมมาก.

public void SearchFlight_PageObject()
{
	driver = new ChromeDriver();
	// Open airasia.com
	Home homepage = new Home(driver);

	// Select Origin
	homepage.FlightSearch.Origin.OpenMenu().SelecByValue("DMK");

	// Select Destination
	homepage.FlightSearch.Destination.CountriesMenu.SelecByValue("SYD");

	// Select Depart Date
	homepage.FlightSearch.DepartDate.EnterText("26/09/2016");

	// Select Return Date
	homepage.FlightSearch.ReturnDate.ClearText().EnterText("27/09/2016");

	// Click Submit
	SearchDetails searchDetailsPage = homepage.FlightSearch.Submit();

	// Assert Depart &gt; Cities
	Assert.That(searchDetailsPage.Depart.HeaderCities, Is.EqualTo("(Bangkok - Don Mueang Sydney)"));
}

Class diagram ข้างล่างคือการออกแบบ Page Object สำหรับหน้า Home และหน้า Search Details.

Class Diagram

 

การออกแบบ Page Objects

ให้มอง Application แบบ Object Oriented, กล่าวคือให้วิเคราะห์ Application แล้วกำหนดว่าอะไรคือProperty และ Method ของ Application. อาจจะเริ่มด้วยการเขียน use case diagram หรือ class diagram ก็ได้.

โดยทั่วไปเราจะเริ่มจากการสร้าง Class ของหน้า  Home. ภายใน Home จะประกอบไปด้วย Property และ Method ที่มี type เป็น Reference type, Custom reference, หรือ Value Type. รูปข้างล่างแสดงการแบ่งขอบเขตของ Property ต่างๆในหน้า Home.

 

 

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *