ปลดล็อกการทำเทส 3rd Party Login ด้วย cy.origin() API พร้อมตัวอย่างจัดเต็ม

Traitanit Huangsri
Cypress.io Thailand
3 min readMay 1, 2022

ผมเชื่อว่าปัญหาที่หลายๆ คนมักจะเจอเมื่อเริ่มต้นเขียนเทสด้วย Cypress ก็คือเรื่องของการจัดการ Login หรือ ​Authentication Workflows ระหว่าง Cypress Test และ Authentication System ที่เป็น 3rd Party Authentication System ไม่ว่าจะเป็น Google, Facebook หรือ ​LINE Login เป็นต้น

ปัญหาก็คือ ที่ผ่านมา Cypress จะไม่อนุญาตให้เรา navigate url ข้าม Domain กันได้ ยกตัวอย่างเช่น สมมติว่าเรากำลังเทส Web Application ของเราที่โดเมน https://www.example.com แล้วจะต้องมีการกดปุ่ม Login เพื่อทำการเข้าสู่ระบบก่อนจะเริ่มทำการเทส ซึ่งการทำระบบ Login ใน Web Application สมัยใหม่ ก็มักจะอนุญาตให้ User ทำการ Login ผ่าน 3rd Party Authentication System ต่างๆ ได้ เมื่อเราพยายามจะ Redirect ไป URL ที่ Domain ไม่ตรงกับ Web App ของเราก็จะเจอ Error ตอนรันเทสแบบนี้

Cross Origin Error เมื่อมีการ navigate url ข้าม domain

เหตุผลก็เป็นเพราะว่า Cypress นั้นเอา Web Application ของเรามารันอยู่ภายใต้ตัวมันเอง เพื่อที่จะทำให้คุณสามารถเข้าถึงทุกอย่างที่รันอยู่ใน Web Application ของคุณได้อย่าง Native เช่นคุณสามารถ Stub Object, Function ที่ถูกเรียกใช้อยู่ในแอพของคุณผ่านการเรียกใช้ Stub Functions ของ Cypress ได้เป็นต้น สามารถอ่านคำอธิบาย Error นี้แบบเต็มๆ ได้ที่นี่

แต่ Cypress ก็เข้าใจเหตุผลของการที่เราต้องการจะเข้าถึง URL ที่อยู่คนละ Domain กันเพื่อทำอะไรบางอย่างที่จำเป็นต้องใช้ในการทำเทส เช่น การทำ Login ผ่าน 3rd Party Authentication System ต่างๆ อย่างที่ได้กล่าวไปข้างต้น จึงได้ทำการพัฒนา API หรือ Command ใหม่เพื่อรองรับการทำงานข้าม Domain กัน โดย API นี้ใช้ชื่อว่า “cy.origin()” ออกมาให้เริ่มต้นใช้งานได้ใน Cypress 9.6.0 เป็นต้นไป

โดย cy.origin() จะอนุญาตให้เราเขียน Code ใน Callback Function ที่เราต้องการจะทำ Action ภายใต้ Domain อื่นๆ ที่ไม่ใช่ Domain ของ Web App ของเรา เช่นถ้าเราอยากจะทำ Login ผ่าน LINE, Facebook ก็สามารถเอาโค้ดที่ใช้ในการกรอก Username, Password มารันอยู่ภายใน Callback Function ของ cy.origin() ได้เลย

มาดูตัวอย่างกันดีกว่า!

ผมได้ลองสร้าง Web Application อันหนึ่งขึ้นมา โดย Web App นี้จะอนุญาตให้ User กดปุ่ม Login เพื่อทำการ Login ผ่าน LINE Login ได้ และเมื่อ Login ผ่าน LINE ได้สำเร็จแล้วก็จะทำการ Get User Profile ออกมาแสดงผลในหน้าเว็บอีกทีหนึ่ง

หน้าตาตัวอย่างเว็บที่ LINE Login แล้ว

แน่นอนว่าการทำ LINE Login จะต้องมีการ Navigate ข้ามโดเมน (Cross Origin) กันแน่นอนจากโดเมนของ Web Application ของเราไปยัง https://access.line.me ซึ่งเป็น URL ของ LINE Login นั่นเอง

เริ่มต้นเขียนเทส Login แบบง่ายๆ ด้วย Cypress

เราลองมาเขียนเทส Web App ของเราโดยทำการกดปุ่ม Login เพื่อทำ LINE Login โดยใช้ cy.origin() แบบง่ายๆ กัน

โดยการที่เราจะใช้งาน cy.origin() ได้นั้นจะต้องทำการ Configure experimentalSessionAndOrigin: true ใน cypress.json ก่อนด้วยนะครับ

โค้ดก็ดูไม่มีอะไรซับซ้อนครับ ก็คือเปิดเว็บขึ้นมา -> กดปุ่ม Login -> Input Email, Password ใน LINE Login -> กดปุ่ม Login และทำการเช็คว่า Login สำเร็จหรือไม่ แค่นั้นเลยครับ ง่ายมั้ยครับ

ใน Scope ของ cy.origin() ก็คือเราทำการ Pass ค่า email และ password ผ่าน args object เข้าไป (อ่านค่าตัวแปรมาจาก Environment Variables ของ Cypress อีกที) และตามด้วย Callback Function ที่ implement Login step ด้วย Cypress Commands ตามปกติเลย

เมื่อรันเทสจริงแบบใช้ cy.origin()
รันผ่านสวยงาม

Optimize โค้ดด้วยการสร้าง Custom Command สำหรับ Login

ในเมื่อเราจะต้องทำการ Login เข้าสู่ระบบก่อนเริ่มเทสทุกครั้ง ดังนั้นเราสามารถที่จะนำการเขียนโค้ดเพื่อ Login ไปสร้างเป็น Custom Command เพื่อให้สามารถ reuse ใช้งานซ้ำๆ แบบนี้ได้

Add Custom Commands ที่ใช้สำหรับทำ Login
ให้ Test เรียกใช้ custom command ที่สร้างไว้แทน

และเนื่องจากว่า Cypress จะมีการ Clear Session, Cookies, localStorage และ sessionStorageให้เราทุกครั้งก่อนเริ่มรันเทสโดยอัตโนมัติ เพื่อป้องกันการ share state ข้าม test cases กัน ดังนั้นเราจึงควรที่จะรัน Login ทุกครั้งก่อนเริ่มรันเทส ที่ scope beforeEach นั่นเอง

Optimize โค้ดอีก โดยการ Preserve Login session ด้วย cy.session()

ปัญหาของโค้ดข้างบนก็คือ ก่อนเริ่มรันเทสทุกข้อ เราจะต้องมารอให้ทำการ Login ให้เสร็จก่อนถึงจะเริ่มรันเทสของตัวเองจริงๆ ได้ ซึ่งในการ Login ก็จะเสียเวลาในการรันเทสไปค่อนข้างมาก คำถามก็คือ “ทำไมเราจะต้องมา Login​​ ซ้ำๆ ก่อนเริ่มเทสทุกครั้งล่ะ ?” เราใช้วิธี Login แค่ครั้งแรกครั้งเดียวแล้วให้ Cypress จำ Login Session นั้นไว้ตลอดได้ไหม ?

คำตอบก็คือ “ได้ครับ” โดยเราสามารถใช้ cy.session() Command ในการจัดการ Login session ต่างๆ โดย Cypress จะทำการ cache cookies, localStorage และ sessionStorage ไว้ ดังนั้นเมื่อเริ่มรันเทสข้อต่อไป ก็สามารถที่จะ reuse ใช้ Login session เดิมที่เราเคยสร้างไว้ได้เลยโดยไม่ต้องทำการ Login ใหม่อีกแล้ว

ดังนั้นใน Login Custom Command ของเราก็ให้ทำการเรียก cy.session() เพื่อทำการ cache login session ไว้ โดยหลักการใช้งาน cy.session() นั้นเราจะต้องระบุ id ของ session ซึ่งเราอาจจะใช้ email, password เป็น key แทนก็ได้ ซึ่งมันมีความ unique อยู่แล้ว และใน argument ที่ 2 ก็ให้ใส่โค้ดที่ใช้ทำ Login ไว้เป็นส่วนของ setup และ argument ที่ 3 คือ ส่วนของการทำ validation เพื่อเช็คว่า login session ของเรายัง valid อยู่มั้ย ซึ่งในส่วนของ LINE Login ก็อาจจะใช้วิธีการเช็คใน localStorage ว่ายังมี authentication token อยู่หรือไม่ก็ได้ครับ

Login Custom Command แบบ Cache Session

เมื่อเรา refactor code ตรงนี้ให้ทำการ Cache Login Session ไว้จะช่วยให้เราใช้เวลารันเทสน้อยลงไปได้มาก ถ้าเราเปรียบเทียบกันระหว่างให้ทำการ Login ทุกครั้งก่อนเริ่มเทส กับ Login แล้ว Cache Login Session ไว้และทำการรันเทสชุดเดียวกัน จะเห็นความแตกต่างได้แบบนี้ครับ

Login ก่อนเทสทุกข้อ ใช้เวลารันเทส 4 ข้อ รวม 30.51 วินาที
Login แบบ Cache Session ไว้ใช้เวลารันเทส 4 ข้อรวม 19.53 วินาที ลดไป 10 กว่าวินาที

สรุป

ถ้าเราต้องการจะเทส Web Application ที่มีการ require ให้ User Login ก่อนถึงจะเริ่มใช้งานได้ และมีการใช้ 3rd Party Authentication System เพื่อทำ Login ได้ การใช้ cy.origin() ก็จะสามารถเข้ามาช่วยแก้ปัญหาตรงนี้ได้ และเรายังสามารถใช้ cy.session() เพื่อทำการ Cache Login Session ไว้เพื่อช่วยลดเวลาที่ใช้ในการรันเทสไปได้เยอะ (ยิ่งถ้ามีจำนวนเทสเคสเยอะจะยิ่งเห็นผลชัด)

หวังว่าบทความนี้จะช่วยปลดล็อกการเขียนเทสหลายๆ อย่างให้กับทุกท่านและอย่าลืมคอย refactor test ของคุณให้ดีขึ้นเสมอๆนะครับ Happy Testing!

--

--