iT邦幫忙

2021 iThome 鐵人賽

DAY 26
0
Modern Web

前端工程師在工作中的各種實戰技能 (Vue 3)系列 第 26

[Day26] Vue3 E2E Testing: Cypress 實戰之 Todo MVC (中)

前情提要

昨天,我們為了讓大家更加了解 Cypress 的語法以及要如何攥寫 E2E 測試,所以開始規劃為 Vue.js • TodoMVC 攥寫 E2E 測試,列出了我們要測試的內容,也實際地為了第一個 case 攥寫測試程式。

https://i.imgur.com/w29OiBy.png

大家可以透過連結自由操作一下,會對於接下來的測試例子更佳有感!

詳細完整的內容,就請大家直接查看昨天的文章吧!那我們就馬上開始今天的內容!

變數宣告

在實際攥寫 Case 2 之前,先帶大家看一下程式碼的樣子以及額外宣告的變數

  • selectors - 由於我們會不斷地需要透過 get() 取得目標元素並進行操作,因此將選擇器 (selector) 用物件的方式儲存下來,增加程式碼的閱讀性和維護性。
  • TODO_ITEM_ONE 和 TODO_ITEM_TWO - 待會 Case 2 測試中會使用的 Todo 變數。
describe('Todo MVC', () => {
  const selectors = {
    main: '.main',
    footer: '.footer',
    todoItems: '.todo-list .todo',
    newTodo: '.new-todo',
    lastOne: '.todo-list .todo:last-child'
  }

  const TODO_ITEM_ONE = 'Item 1'
  const TODO_ITEM_TWO = 'Item 2'

  beforeEach(() => {
    cy.visit('https://todomvc.com/examples/vue')
  })

  context('Case 1: Initial State', () => {
    it('Case 1-1: start with zero todo item', () => {
      cy.get(selectors.todoItems).should('have.length', 0)
    })

    it('Case 1-2: hide .main and .footer', () => {
      cy.get(selectors.main).should('not.be.visible')
      cy.get(selectors.footer).should('not.be.visible')
    })
  })

  context('Case 2: New Todo', () => {
    // ...
  })

  context('Case 3: Edit Todo', () => {
    // ...
  })
})

Case 2: 新增 Todo

  • Case 2-1 可以新增一至多個 Todo。
  • Case 2-2: Todo 新增完後,輸入框的文字會被清除。
  • Case 2-3: Todo 會被新增至清單的最後一個。
  • Case 2-4: 當有 Todo 新增後,Todo 清單以及 Footer 將會顯示。
context('Case 2: New Todo', () => {
  it('Case 2-1: create items', () => {

    // create first item
    cy.get(selectors.newTodo).type(`${TODO_ITEM_ONE}{enter}`)

    cy.get(selectors.todoItems)
      .eq(0)
      .find('label')
      .should('contain', TODO_ITEM_ONE)

    // create second item
    cy.get(selectors.newTodo).type(`${TODO_ITEM_TWO}{enter}`)

    cy.get(selectors.todoItems)
      .eq(1)
      .find('label')
      .should('contain', TODO_ITEM_TWO)

    cy.get(selectors.todoItems).should('have.length', 2)
  })

  it('Case 2-2: append new items to the bottom of the list', () => {
    const TODO_ITEM_LAST_ONE = 'Item Last One'

    for (let i = 0; i < 10; i++) {
      cy.get(selectors.newTodo).type(`Item ${i}{enter}`)
    }

    cy.get(selectors.newTodo).type(`${TODO_ITEM_LAST_ONE}{enter}`)

    cy.get(selectors.lastOne)
      .find('label')
      .should('contain', TODO_ITEM_LAST_ONE)
  })

  it('Case 2-3: clear input text after an item is added', () => {
    cy.get(selectors.newTodo).type(`${TODO_ITEM_ONE}{enter}`)
    cy.get(selectors.newTodo).should('have.text', '')
  })

  it('Case 2-4: show .main and .footer when items added', () => {
    cy.get(selectors.newTodo).type(`${TODO_ITEM_ONE}{enter}`)
    cy.get(selectors.main).should('be.visible')
    cy.get(selectors.footer).should('be.visible')
  })
})

語法說明:

  • type(): 輸入文字至 DOM 中,不過傳給 type() 的字串可能包含特殊字符,這些特殊字符是用來表示在輸入文字期間發出的事件,例如此處的 {enter} 即表示按下 enter 鍵,又或者是 {selectall} 表示選取輸入的文字。(更完整的列表可參考此文件)

  • eq(): 取得元素陣列中特定 index 的 DOM 元素,不過特別的是 index 可以為負值,表示從尾巴開始在元素陣列中查找元素。

    <ul>
      <li>1</li>
      <li>2</li>
      <li>3</li>
      <li>4</li>
      <li>5</li>
    </ul>
    
    cy.get('li').eq(0).should('contain', '1')
    cy.get('li').eq(1).should('contain', '2')
    cy.get('li').eq(-1).should('contain', '5')
    cy.get('li').eq(-2).should('contain', '4')
    
  • find(): 從特定選擇器的元素後代開始尋找特定的元素。

    // success
    cy.get('.todo-list .todo:last-child').find('label')
    
    // error
    cy.find('.todo-list .todo:last-child label') 
    

Case 3: 編輯 Todo

  • Case 3-1: 對 Todo 連擊兩下後進入編輯模式,並在 blur 時完成編輯。
  • Case 3-2: 如果編輯後的 Todo 為空內容,則自動刪除。
context('Case 3: Edit Todo', () => {
  it('Case 3-1: save edits on blur', () => {
    cy.get(selectors.newTodo).type(`${TODO_ITEM_ONE}{enter}`)

    cy.get(selectors.todoItems)
      .eq(0)
      .find('label')
      .should('contain', TODO_ITEM_ONE)
      .dblclick()

    cy.get(selectors.todoItems)
      .eq(0)
      .find('.edit')
      .type('{selectall}{backspace}') // same as .clear()
      .type(`${TODO_ITEM_TWO}`)
      .blur()

    cy.get(selectors.todoItems)
      .eq(0)
      .find('label')
      .should('contain', TODO_ITEM_TWO)
  })
  it('Case 3-2: remove the item if an empty text was entered', () => {
    cy.get(selectors.newTodo).type(`${TODO_ITEM_ONE}{enter}`)

    cy.get(selectors.todoItems)
      .eq(0)
      .find('label')
      .should('contain', TODO_ITEM_ONE)
      .dblclick()

    cy.get(selectors.todoItems)
      .eq(0)
      .find('.edit')
      .clear() // same as .type('{selectall}{backspace}')
      .blur()

    cy.get(selectors.todoItems).should('have.length', 0)
  })
})

語法說明:

  • type('{selectall}{backspace}')clear(): 在 Case2 的語法說明中有介紹到 type 可以傳入表示事件的特殊字符,而如果我們想要清除輸入框中的文字就可以使用'{selectall}{backspace}' 來達到選取後按下刪除鍵,不過我們也可以直接使用 clear() 來達到同樣的功能。

參考資料


今天的分享就到這邊,如果大家對我分享的內容有興趣歡迎點擊追蹤 & 訂閱系列文章,如果對內容有任何疑問,或是文章內容有錯誤,都非常歡迎留言討論或指教的!

我們明天見!


上一篇
[Day25] Vue3 E2E Testing: Cypress 實戰之 Todo MVC (上)
下一篇
[Day27] Vue3 E2E Testing: Cypress 實戰之 Todo MVC (下)
系列文
前端工程師在工作中的各種實戰技能 (Vue 3)30

尚未有邦友留言

立即登入留言