การทดสอบ application ในบางครั้งก็จะมี dependencies อื่น ๆ ที่เกี่ยวข้องมากมาย โดยหนึ่งในนี้คือ การเรียก API data ที่ไม่สามารถทราบได้เลยว่าการที่ API ตอบกลับมานั้นจะช้าหรือเร็วแค่ใหน การเทสบางครั้งอาจราบลื่นเป็นปกติ เพราะไม่มีการถูกขัดจังหวะด้วย response time ของ API data ที่เร็ว แต่เมื่อ run test อีกครั้งกับได้ผลรับไม่เหมือนเดิม เกิด failures เพราะมี error message แจ้งกลับมาว่า timeout waiting 5000ms บ่งบอกได้ถึงการรอ network request API ที่นานจนโปรแกรมรอไม่ไหว แบบนี้จะแก้ได้อย่างไร
การ Mock API แก้ pain point นี้ได้
การ Mock API ช่วยแก้ pain point นี้ได้ โดยมีหลักการคือการ จำลอง response data ที่สร้างขึ้นมาเพื่อที่จะไม่ต้องเรียก API request จริง วิธีการนี้จะช่วย ควบคุม response time ได้ทำให้ test ของเราทำงานได้ราบลื่นไม่ติดขัด
เมื่อลองทดสอบ run test การทำงานของโปรแกรมจะเปลี่ยนไป ระบบจะแสดงแค่ข้อมูลของประเทศไทย โดยที่ไม่ได้มีการแก้ไขโปรแกรมที่ทดสอบแต่อย่างไร เพียงแต่ทำการ mockup API ที่แสดงข้อมูลด้วย Cypress Route เท่านั้น
เพียงเท่านี้เราก็สามารถควบคุม data ที่มาจากการ call API จากที่ต่าง ๆ ได้ แล้วโดยไม่ต้องรอการ response กลับจาก API นั้น ๆ
describe('TDD Assertions', () => {
it('use assert.* ', () => {
assert.isOk('everything', 'everything is ok')
assert.isNotOk(false, 'this will pass')
assert.equal(3, 3, 'vals equal')
assert.notEqual(3, 4, 'vals not equal')
assert.strictEqual(true, true, 'bools strict eq')
assert.notStrictEqual(5, '5', 'not strict eq')
assert.deepEqual({ id: '1' }, { id: '1' })
assert.notDeepEqual({ id: '1' }, { id: '2' })
assert.isAbove(6, 1, '6 greater than 1')
assert.isAtLeast(5, 2, '5 gt or eq to 2')
assert.isBelow(3, 6, '3 strict lt 6')
assert.isAtMost(4, 4, '4 lt or eq to 4')
assert.isTrue(true, 'this val is true')
assert.isNotTrue('tests are no fun', 'val not true')
assert.isFalse(false, 'val is false')
assert.isNotFalse('tests are fun', 'val not false')
//assert.isNull(err, 'there was no error')
assert.isNotNull('hello', 'is not null')
assert.isNaN(NaN, 'NaN is NaN')
assert.isNotNaN(5, '5 is not NaN')
assert.exists(5, '5 is not null or undefined')
assert.notExists(null, 'val is null or undefined')
assert.isUndefined(undefined, 'val is undefined')
assert.isDefined('hello', 'val has been defined')
assert.isFunction(x => x * x, 'val is func')
assert.isNotFunction(5, 'val not funct')
assert.isObject({ num: 5 }, 'val is object')
assert.isNotObject(3, 'val not object')
assert.isArray(['unit', 'e2e'], 'val is array')
assert.isNotArray('e2e', 'val not array')
assert.isString('e2e', 'val is string')
assert.isNotString(2, 'val not string')
assert.isNumber(2, 'val is number')
assert.isNotNumber('e2e', 'val not number')
//assert.isFinite('e2e', 'val is finite')
assert.isBoolean(true, 'val is bool')
assert.isNotBoolean('true', 'val not bool')
assert.typeOf('e2e', 'string', 'val is string')
assert.notTypeOf('e2e', 'number', 'val not number')
})
})
before (ก่อนหน้า) เป็น lifecycle ของ testscript จะทำเมื่่อเริ่มรัน describe เพียงครั้งแรกเท่านั้น beforeEach (ก่อนหน้าแต่ละ) คล้ายกับ before แต่จะถูกทำทุกการรันแท็ก it จะทำเมื่อเริ่มแรก ของ it describe (อธิบาย) เมื่อเริ่มเขียน testscript ทำหน้าที่อธิบายการทำงานของกลุ่ม testcase นี้ มักจะใช้งานร่วมกับแท็ก it ซึ่งจะเป็นซับ testcase ของกลุ่ม test นี้ context (บริบท) หน้าที่คล้ายกับ describe เลือกใช้งานตามความถนัด it (มัน ,เคส) แท็กเขียนโค๊ด test ที่นี่จะเริ่มเขียน API commands ต่าง ๆ ที่นี่ afterEach (หลังจากนั้นแต่ละ) ทำหลังจากเรียก it ทุกครั้ง จะทำ test บางอย่างหลังจาก it (เคส) ทำงานครบแล้ว after (หลังจาก) ทำท้ายสุดหลังจาก test มาถึงท้าย กลุ่มเทส (describe)
visit ใช้สำหรับเข้า website test ถือว่าเป็น command ที่ต้องรู้ลำดับแรก ๆ ตัวอย่างการใช้งาน cy.visit(‘https://google.com‘) มี function callback ที่น่าสนใจ คือ onBeforeLoad , onLoad สามารถใส่ behavior function การทำงานในส่วนนี้ได้
get ระบุ selector element ของหน้าจอ เพื่อจะเป็น target ในการ action command ถัดไปกรณี ต้องการให้ระบุ element เพื่อทำงานอะไรบ้างอย่าง เช่น set value ในช่อง text ,set text บน label เป็นต้น ตัวอย่างการเรียกใช้งาน cy.get(‘{selector}’) โดย selector คือ input ,.class ,#id ,[name=””] เป็นต้น
type ใช้สำหรับ set value ให้กับ input tag ต่าง ๆ เช่น input [text ,date ,phone ,password ,number ,…] รวมถึง textarea ได้อีกด้วย ตัวอย่างการใช้งาน cy.get(‘{selector}’).type(‘commade type’)
clear ใช้ clear value ในช่อง input ตัวอย่างการใช้งาน cy.clear()
as ทำหน้าที่เป็น command กำหนด alias เป็นการกำหนด reference ให้กับ command ที่กำลังสนใจ หรือ ใช้งานซ้ำ ๆ สามารถ reuse เรียกใช้งานซ้ำได้ ตัวอย่างการใช้งาน cy.(‘{selector}’).as(‘mySelector’) อยากต้องการระบุอ้างอิงถึงก็เพียงแค่ cy.get(‘@mySelector’) ถึงชื่อ alias ก็สามารถใช้ทำงานได้เหมือนการเรียก cy.get(‘{selector}’) นั้นอีกครั้ง
check ใช้กับ input [radio ,checkbox] กำหนด state ให้กับ element เหล่านี้เป็นการ checked ตัวอย่างการใช้งาน cy.get(‘{selector}’).check() โดยที่ {selector} นั้นต้องเป็น radio ,checkbox command ถึงจะทำงานได้ถูกต้อง
click ชื่อก็บอกอยู่แล้ว กำหนด event click ให้กับ element ที่กำลังสนใจ เกือบแทบทุก element สามารถเรียก command นี้ได้เกือบหมด (ถ้าไม่ถูก Disabled) ตัวอย่างการใช้งานจะใช้งานร่วมกับ command get คือ cy.get(‘{selector}’).click()
contains สำหรับเทียบค่าข้อความที่คาดหวังว่าจะมีใน element ที่เรากำลังสนใจ (คล้ายกับการ LIKE ‘%%’) ตัวอย่างการใช้งาน cy.get(‘{selector}’).contains(‘ข้อความ’) ถ้าหา element ที่มีข้อความไม่พบจะตก fail case เลย
each การ LOOP elements ที่ได้จากการ get(‘{selector}’) แล้วต้องการที่จะทำงานอะไรบ้างอย่างกับ element ที่ selector ได้ ก็จะเรียก command each นี้ โดย command จะมี callback function ให้เขียนคำสั่งอื่นได้ ๆ ตัวอย่างการใช้งาน cy.get(‘{selector}’).each(()=> {/* todo something */ })
eq ตัวย่อของ equal ที่แปลว่า เท่ากัน case ที่ใช้งานบ่อย คือการเทียบหาลำดับของ elements ที่พบได้มากกว่า 1 element ตัวอย่างการใช้งาน cy.get(‘{selector}’).eq(0) โดย 0 คือ index ของ elements ที่เจอ
find ต้องการค้นหา element เป้าหมายเพียง element เดียว หลังจากที่ selector ได้ elements มากกว่า 1 หรือจะระบุ selector ตั้งแต่แรกเริ่ม get เลยก็ได้ ตัวอย่างการใช้งาน cy.get(‘{selector}’).find(‘.class-unique’) command จะ return element มาเพียง 1 element เท่านั้น
log แสดง variable หรือ text ออกทางหน้าจอ run ui คล้ายกับ console.log(”) ของ javascript เพื่อแต่พื้นที่การแสดงอยู่คนละที่กัน
next ถัดไป จะ selector element ตำแหน่งถัดไปของ element ก่อนเรียก command นี้ โดยสังเกตุจะเป็น element level เดียวกับ element ที่เรียก command นี้ ตัวอย่างการใช้งาน cy.get(‘{selector}’).next() จะได้ element ถัดไปทันที
prev ก่อนหน้า เมื่อมี element ถัดไป (next command) ก็ต้องมีการหา element ก่อนหน้า ดังนั้น command นี้จะทำหน้าที่หา element ก่อนหน้าที่จะเรียก command นี้
not เป็นนิเสธน์ ใช้กรอง element ไม่สนใจ elements ที่ถูกเรียกภายใน command นี้ ตัวอย่างการเรียกใช้งาน cy.get(‘{selectors}’).not(‘{.not-use-element}’)
Cypress เป็น Test Framework ที่ถูก Design ขึ้นมาในปี 2014 (ถูกสร้างขึ้นหลัง Selenium 10 ปีเอง) โดยตัว Cypress จะถูกรันขึ้นมาอยู่ใน Run Loop Process เดียวกับ Web Application ของเรา ซึ่งเบื้องหลังการทำงานของมันคือ NodeJS Server