---
# System prepended metadata

title: 變數和別名

---

# 變數和別名
:::info
* 如何處理非同步命令
* 什麼是別名? 以及別名如何能簡化您的代碼
* 為什麼在 Cypress 中很少需要用到變數
* 如何為物件、元素和路由使用別名
:::
---
## 返回值
Cypress 的新用戶一開始可能會發現使用我們 API 的異步特性很有挑戰性。
:::success
**別擔心！**

有多種方法可以引用、比較和利用 Cypress 指令所生成的物件。

一旦你掌握了異步代碼的竅門，你就會意識到你可以同步做所有你能做的事情。

本指南探討了許多常見模式,用於編寫良好的 Cypress 代碼，甚至可以處理最複雜的情況。
:::

非同步 API 將保留在 JavaScript 中。它們在現在的代碼中隨處可見。
事實上，大多數新瀏覽器的 API 都是非同步的，許多核心 Node 模組也是非同步的。

我們將在下面探討的模式在 Cypress 內外都很有用。

您應該認識到的第一個也是最重要的概念是......

:::danger
**返回值**

您不能分配或使用任何 Cypress 命令的返回值。命令被非同步運行。
:::
```javascript=
// 下列程式碼是錯誤的
const button = cy.get('button')
const form = cy.get('form')

button.click()

```

## 關閉
想使用 cypress 命令所產生的每的內容,你可以使用 then().
```javascript=
cy.get('button').then(($btn) => {
  // $btn is the object that the previous
  // command yielded us
})

```
如果您熟悉JS的原生 Promise ，Cypress 的.then()使用方式與之相同。您可以在尾段繼續接著.then().

每個鏈式命令都可以接受到前一命令中的值。 這在可讀性上非常的好。
```javascript=
cy.get('button').then(($btn) => {

  // store the button's text
  const txt = $btn.text()

  // submit a form
  cy.get('form').submit()

  // compare the two buttons' text
  // and make sure they are different
  cy.get('button').should(($btn2) => {
    expect($btn2.text()).not.to.eq(txt)
  })
})

// these commands run after all of the
// other previous commands have finished
cy.get(...).find(...).should(...)
```

在.then()所有鏈式命令完成之前，在外部的命令是不會執行的。
:::info
通過使用回調函數，我們創建了一個 閉包。閉包使我們能夠保留引用以引用在先前命令中完成的工作。
:::

## 除錯 (debugger)
在使用.then()函數時,是使用 debugger 的良好時機. 這可以幫助您了解命令的運行順序。這也使您能夠檢查 Cypress 在每個命令中生成的物件
```javascript=
cy.get('button').then(($btn) => {
  // inspect $btn <object>
  debugger

  cy.get('#countries')
    .select('USA')
    .then(($select) => {
      // inspect $select <object>
      debugger

      cy.url().should((url) => {
        // inspect the url <string>
        debugger

        $btn // is still available
        $select // is still available too
      })
    })
})
```

## 變數
在 Cypress 中，您幾乎不需要用到const, let, 或var。使用閉包時您可以訪問物件生產給您的，而無需分配它們。

此規則的一個例外是當您處理可變物件（更改狀態）時。當事情改變狀態時，您通常希望將物件的前一個值與下一個值進行比較。

以下是一個 const 比較的範例.

```javascript=
<button>increment</button>

you clicked button <span id="num">0</span> times
```

```javascript=
// app code
let count = 0

$('button').on('click', () => {
  $('#num').text((count += 1))
})
```

```java=
// cypress test code
cy.get('#num').then(($span) => {
  // capture what num is right now
  const num1 = parseFloat($span.text())

  cy.get('button')
    .click()
    .then(() => {
      // now capture it again
      const num2 = parseFloat($span.text())

      // make sure it's what we expected
      expect(num2).to.eq(num1 + 1)
    })
})
```

使用const的原因是因為對 $span 物件是可變的。
每當您擁有可變的物件,並且嘗試比較它們時，您都需要存儲它們的值。使用const 是一個完美的方法來做到這一點。

## 別名
使用.then()回調函數來處理前一鍊式中的值是對的,
但當你在 hooks 中執行像是 before 或 beforeEachc 會發生什麼式呢?
```javascript=
beforeEach(() => {
  cy.button().then(($btn) => {
    const text = $btn.text()
  })
})

it('does not have access to text', () => {
  // 我們該如何拿到text ?!?!
})

```
我們該如何拿到 text ?
我們可以透過 let 寫一些髒code來訪問text
:::danger
千萬別這樣做!!
以下程式法只用於示範
:::
```javascript=

describe('a suite', () => {
  // this creates a closure around
  // 'text' so we can access it
  let text

  beforeEach(() => {
    cy.button().then(($btn) => {
      // redefine text reference
      text = $btn.text()
    })
  })

  it('does have access to text', () => {
    // now text is available to us
    // but this is not a great solution :(
    text
  })
})
```

很幸運的是我們無需再使用一些花禮胡俏的寫法, 
透過 Cypress 我們可以更好的處理此況

:::success
**別介紹名**

別名是 Cypress 中的一個強大的結構，有很多用途。

我們將在下面探討它們的每一項功能。

首先，我們使用別名在 hooks 和 tests 之間共享 objects 。
:::

## 共享 Context
共享 Context 是別名中最簡單的方法。
使用別名中的.as()命令 來共享您想要的的東西。
讓我們看一下别名的使用範例。
```javascript=
beforeEach(() => {
  // alias the $btn.text() as 'text'
  cy.get('button').invoke('text').as('text')
})

it('has access to text', function () {
  this.text // is now available
})
```
在底層，別名的機本物件和語法是利用 Mocha的context物件, 也就是說，別名也可以利用 this.*.

Mocha 自動為我們在每個測試中的所有 hooks 中共享 Context。此外，這些別名和屬性會在每次測試後自動清除。

```javascript=
describe('parent', () => {
  beforeEach(() => {
    cy.wrap('one').as('a')
  })

  context('child', () => {
    beforeEach(() => {
      cy.wrap('two').as('b')
    })

    describe('grandchild', () => {
      beforeEach(() => {
        cy.wrap('three').as('c')
      })

      it('can access all aliases as properties', function () {
        expect(this.a).to.eq('one') // true
        expect(this.b).to.eq('two') // true
        expect(this.c).to.eq('three') // true
      })
    })
  })
})

```

## 訪問 fixture
共享 Context 中最常見用例子, 是在處理 cy.fixture() 時.

很多時候你可能會在 beforeEach hooks 中加載一個 fixture ，但希望利用測試中的值。

```javascript=
beforeEach(() => {
  // alias the users fixtures
  cy.fixture('users.json').as('users')
})

it('utilize users in some way', function () {
  // access the users property
  const user = this.users[0]

  // make sure the header contains the first
  // user's name
  cy.get('header').should('contain', user.name)
})
```

:::danger
注意非同不命令

不要忘記 Cypress 是非同步的!

你不能用 this.*，直到 .as() 命令執行。
:::
```javascript=
it('is not using aliases correctly', function () {
  cy.fixture('users.json').as('users')

  // nope this won't work
  //
  // this.users is not defined
  // because the 'as' command has only
  // been enqueued - it has not run yet
  const user = this.users[0]
})
```

我們以前介紹過很多次的原則也適用於這種情況。
如果你想要訪問一個命令產生的結果，你必須在閉包中使用.then()。
```javascript=
// yup all good
cy.fixture('users.json').then((users) => {
  // now we can avoid the alias altogether
  // and use a callback function
  const user = users[0]

  // passes
  cy.get('header').should('contain', user.name)
})
```
## 避免使用

:::warning
箭頭涵式

使用此属性訪問别名。如果您在測試中使用箭頭函式或 hook, 那 this.* 將不起作用。

這就是什麼我們的範例都是使用常規的 function () {} 語法, 而不是 () => {} 箭頭涵式 
:::

除了 this.* 語法, 還有另一種方式可以讓你訪問到別名(aliases)
cy.get() 是一個能夠使用 @ 特殊語法,來訪問到别名的命令
```javascript=
beforeEach(() => {
  // alias the users fixtures
  cy.fixture('users.json').as('users')
})

it('utilize users in some way', function () {
  // use the special '@' syntax to access aliases
  // which avoids the use of 'this'
  cy.get('@users').then((users) => {
    // access the users argument
    const user = users[0]

    // make sure the header contains the first
    // user's name
    cy.get('header').should('contain', user.name)
  })
})
```
藉由 cy.get()語法, 我們可以避免使用 this

請記住，這兩中方式都有作用，因為它們有不同的情境用法。

當使用 this.users 時, 我們是以同步的方式訪問

然而，當使用cy.get('@users')時，它就變成了一個非同步命令

您可以將cy.get('@users') 視為與cy.wrap(this.users) 相同的操作。

## 元素
在與DOM元素一起使用時，別名還有其他特殊特徵。

在給DOM元素起了別名之後，可以在以後訪問它們以便重用。
```javascript=
// alias all of the tr's found in the table as 'rows'
cy.get('table').find('tr').as('rows')
```
在内部，Cypress引用<tr>集合作为别名“rows”返回。要在以后引用这些相同的“行”，可以使用cy.get()命令。
```javascript=
// Cypress returns the reference to the <tr>'s
// which allows us to continue to chain commands
// finding the 1st row.
cy.get('@rows').first().click()
```
因為我們在cy.get()中使用了@字符，而不是在DOM中查詢元素，cy.get()尋找一個名為rows的現有別名並返回引用(如果找到它)。

## 舊元素
在許多單頁面JavaScript應用程序中，DOM不斷地重新呈現應用程序的某些部分。如果在使用別名調用cy.get()時為已從DOM中刪除的DOM元素添加別名，Cypress將自動重新查詢DOM,以再次查找這些元素。   
```javascript=
<ul id="todos">
  <li>
    Walk the dog
    <button class="edit">edit</button>
  </li>
  <li>
    Feed the cat
    <button class="edit">edit</button>
  </li>
</ul>
```

讓我們想像一下，當我們單擊.edit按鈕時，我們的<li>將在DOM中重新呈現。它沒有顯示編輯按鈕，而是顯示了一個<input />文本字段，允許您編輯todo。前面的<li>已經從DOM中完全刪除，取而代之的是一個新的<li>。

```javascript=
cy.get('#todos li').first().as('firstTodo')
cy.get('@firstTodo').find('.edit').click()
cy.get('@firstTodo')
  .should('have.class', 'editing')
  .find('input')
  .type('Clean the kitchen')
```

當我們引用@firstTodo時，Cypress會檢查它所引用的所有元素是否仍然在DOM中。如果是，則返回那些現有的元素。如果不是，Cypress將會重新執行命令,直到別名被定義。

在我們的例子中，它將重新發出命令:cy.get('#todos li').first()。一切正常，因為找到了新的<li>。

:::warning

通常，重新執行之前的命令會返回您所期望的結果，但並不總是如此。建議您盡快為元素設置別名，而不是進一步使用命令鏈。

- cy.get('#nav header .user').as('user') :heavy_check_mark: (good)
- cy.get('#nav').find('header').find('.user').as('user') :warning: (bad)

當有疑問時，您隨時可以發出常規的cy.get()來再次查詢元素。
:::

## 攔截
別名也可以與cy.intercept()一起使用。將攔截路由別名，你可以:

- 確保應用程序發出預期的請求
- 等待服務器發送響應
- 訪問斷言的實際請求物件
- 
![](https://docs.cypress.io/_nuxt/img/aliasing-routes.af26c33.jpg)

這裡有一個別名攔截路由的例子，並等待它完成。
```javascript=
cy.intercept('POST', '/users', { id: 123 }).as('postUser')

cy.get('form').submit()

cy.wait('@postUser').then(({ request }) => {
  expect(request.body).to.have.property('name', 'Brian')
})

cy.contains('Successfully created user: Brian')
```

:::info
新 Cypress?
我們有一個更詳細和全面的路由網路請求指南。
:::

## Requests

別名也可以用於Requests。

下面是一個將 Requests 別名 並稍後訪問其屬性的範例。

```javascript=
cy.request('https://jsonplaceholder.cypress.io/comments').as('comments')

// other test code here

cy.get('@comments').should((response) => {
  if (response.status === 200) {
      expect(response).to.have.property('duration')
    } else {
      // whatever you want to check here
    }
  })
})
```

## 別名會在每次測試之前重制

注意:所有別名在每次測試前都被重置。一個常見的用戶錯誤是使用before鉤子創建別名。這樣的別名只在第一次測試中起作用!
```javascript=
// 🚨 THIS EXAMPLE DOES NOT WORK
before(() => {
  // notice this alias is created just once using "before" hook
  cy.wrap('some value').as('exampleValue')
})

it('works in the first test', () => {
  cy.get('@exampleValue').should('equal', 'some value')
})

// NOTE the second test is failing because the alias is reset
it('does not exist in the second test', () => {
  // there is not alias because it is created once before
  // the first test, and is reset before the second test
  cy.get('@exampleValue').should('equal', 'some value')
})
```

解決方案是在每次測試之前使用 beforeEach hook 創建別名

```javascript=
// ✅ THE CORRECT EXAMPLE
beforeEach(() => {
  // we will create a new alias before each test
  cy.wrap('some value').as('exampleValue')
})

it('works in the first test', () => {
  cy.get('@exampleValue').should('equal', 'some value')
})

it('works in the second test', () => {
  cy.get('@exampleValue').should('equal', 'some value')
})
```

## 另請參閱 
- 從Cypress自定義命令加載裝置解釋瞭如何加載或導入裝置以在Cypress自定義命令中使用。
(https://glebbahmutov.com/blog/fixtures-in-custom-commands/)