cypress - E2E test tool

코드위의승부사·2020년 7월 26일
0

The more your tests resemble the way your software is used, the more confidence they can give you.

conduit - project real world app

{
  "articles": [
    {
      "title": "Article 1",
      "slug": "article-1",
      "body": "This is article 1 body",
      "createdAt": "2020-06-22T11:20:16.812Z",
      "updatedAt": "2020-06-22T11:20:16.812Z",
      "tagList": ["tag1", "tag2"],
      "description": "This is article 1 description",
      "author": {
        "username": "user1",
        "bio": null,
        "image": "https://s3.amazonaws.com/uifaces/faces/twitter/nilshelmersson/128.jpg",
        "following": false
      },
      "favorited": false,
      "favoritesCount": 0
    },
    {
      "title": "Article 2",
      "slug": "article-2",
      "body": "This is article 2",
      "createdAt": "2020-06-28T11:22:35.833Z",
      "updatedAt": "2020-06-28T11:22:35.833Z",
      "tagList": ["tag1"],
      "description": "This is article 2 description",
      "author": {
        "username": "user1",
        "bio": null,
        "image": "https://s3.amazonaws.com/uifaces/faces/twitter/praveen_vijaya/128.jpg",
        "following": false
      },
      "favorited": true,
      "favoritesCount": 1
    },
    {
      "title": "Article 3",
      "slug": "article-3",
      "body": "This is article 3",
      "createdAt": "2020-06-25T00:49:44.430Z",
      "updatedAt": "2020-06-25T00:49:44.430Z",
      "tagList": [],
      "description": "This is article 3 description",
      "author": {
        "username": "user2",
        "bio": null,
        "image": "https://s3.amazonaws.com/uifaces/faces/twitter/nilshelmersson/128.jpg",
        "following": false
      },
      "favorited": false,
      "favoritesCount": 0
    }
  ],
  "articlesCount": 3
}

step - 1

describe('Global Feed', () => {
  beforeEach(() => {
    cy.server()

    cy.route('**/api/articles**', 'fx:global_feed.json').as('getArticles')
  })

  it('Should show all articles correctly', () => {
    cy.visit('/')
    
    cy.get('div.article-preview').should('have.length', 3)

    // Article 1 start
    cy.get('div.article-preview')
      .eq(0)
      .find('div.article-meta > a > img')
      .should(
        'have.attr',
        'src',
        'https://s3.amazonaws.com/uifaces/faces/twitter/praveen_vijaya/128.jpg'
      )

    cy.get('div.article-preview')
      .eq(0)
      .find('div.article-meta > div.info > a.author')
      .should('have.text', 'user1')

    cy.get('div.article-preview')
      .eq(0)
      .find('div.article-meta > div.info > span.date')
      .should('have.text', 'Mon Jun 22 2020')

    cy.get('div.article-preview')
      .eq(0)
      .find('div.article-meta > div.pull-xs-right > button.btn')
      .should('contain', '0')

    cy.get('div.article-preview')
      .eq(0)
      .find('a.preview-link > h1')
      .should('have.text', 'Article 1')

    cy.get('div.article-preview')
      .eq(0)
      .find('a.preview-link > p')
      .should('have.text', 'This is article 1 description')

    cy.get('div.article-preview')
      .eq(0)
      .find('a.preview-link > ul.tag-list > li.tag-default')
      .eq(0)
      .should('have.text', 'tag1')

    cy.get('div.article-preview')
      .eq(0)
      .find('a.preview-link > ul.tag-list > li.tag-default')
      .eq(1)
      .should('have.text', 'tag2')
    // Article 1 end

    // Article 2 start
    cy.get('div.article-preview')
      .eq(1)
      .find('div.article-meta > a > img')
      .should(
        'have.attr',
        'src',
        'https://s3.amazonaws.com/uifaces/faces/twitter/praveen_vijaya/128.jpg'
      )

    cy.get('div.article-preview')
      .eq(1)
      .find('div.article-meta > div.info > a.author')
      .should('have.text', 'user1')

    cy.get('div.article-preview')
      .eq(1)
      .find('div.article-meta > div.info > span.date')
      .should('have.text', 'Sun Jun 28 2020')

    cy.get('div.article-preview')
      .eq(1)
      .find('div.article-meta > div.pull-xs-right > button.btn')
      .should('contain', '1')

    cy.get('div.article-preview')
      .eq(1)
      .find('a.preview-link > h1')
      .should('have.text', 'Article 2')

    cy.get('div.article-preview')
      .eq(1)
      .find('a.preview-link > p')
      .should('have.text', 'This is article 2 description')

    cy.get('div.article-preview')
      .eq(1)
      .find('a.preview-link > ul.tag-list > li.tag-default')
      .eq(0)
      .should('have.text', 'tag1')
    // Article 2 end

    // Article 3 start
    cy.get('div.article-preview')
      .eq(2)
      .find('div.article-meta > a > img')
      .should(
        'have.attr',
        'src',
        'https://s3.amazonaws.com/uifaces/faces/twitter/nilshelmersson/128.jpg'
      )

    cy.get('div.article-preview')
      .eq(2)
      .find('div.article-meta > div.info > a.author')
      .should('have.text', 'user2')

    cy.get('div.article-preview')
      .eq(2)
      .find('div.article-meta > div.info > span.date')
      .should('have.text', 'Thu Jun 25 2020')

    cy.get('div.article-preview')
      .eq(2)
      .find('div.article-meta > div.pull-xs-right > button.btn')
      .should('contain', '0')

    cy.get('div.article-preview')
      .eq(2)
      .find('a.preview-link > h1')
      .should('have.text', 'Article 3')

    cy.get('div.article-preview')
      .eq(2)
      .find('a.preview-link > p')
      .should('have.text', 'This is article 3 description')

    cy.get('div.article-preview')
      .eq(2)
      .find('a.preview-link > ul.tag-list')
      .should('be.empty')
    // Article 3 end
  })
})

step - 2

implicit -> explicit relationships의 전환으로 articles 데이터를 다이나믹하게 처리

  • forEach - index dynamic
import { DateFormats } from '../support/enums'

describe('Global Feed', () => {
  beforeEach(() => {
    cy.server()

    cy.route('**/api/articles**', 'fx:global_feed.json').as('getArticles')
    
    // cy.route('**/api/articles**', ).as('getArticles')
    cy.route('**/api/tags**', 'fx:popular_tags.json').as('getTags')
  })

  it('Should show all articles correctly', () => {
    cy.visit('/')

    /*
    cy.fixture('global_feed.json').then(({articles})) => {
    	articles.forEach((article:Article, index:number) => {
        cy.get('div.article-preview')
        
        ...
        ...
        })
    }
    */

    // wait request and full request -> xhr 
    cy.wait('@getArticles').then((xhr) => {
      const { articles } = xhr.responseBody as GetArticles

      cy.get('div.article-preview')
        .should('have.length', articles.length)
        .each(($articlePreview, index) => {
          const article: Article = articles[index]

          cy.wrap($articlePreview)
            .as('articlePreview')
            .find('div.article-meta > a > img')
            .should('have.attr', 'src', article.author.image)

          cy.get('@articlePreview')
            .find('div.article-meta > div.info > a.author')
            .should('have.text', article.author.username)

        // DataFormats라는 공용함수를 이용해서 Date 변환
          cy.get('@articlePreview')
            .find('div.article-meta > div.info > span.date')
            .should(
              'have.text',
              Cypress.moment(article.createdAt).format(
                DateFormats.ArticlePreview
              )
            )

          cy.get('@articlePreview')
            .find('div.article-meta > div.pull-xs-right > button.btn')
            .should('contain', article.favoritesCount)

          cy.get('@articlePreview')
            .find('a.preview-link > h1')
            .should('have.text', article.title)

          cy.get('@articlePreview')
            .find('a.preview-link > p')
            .should('have.text', article.description)

        /**
        article.tagList.forEach((tag, tagIndex) => {
        	cy.get('div.article-preview')
            	.eq(index)
                .find('a.preview-link > ul.tag-list > li.tag-default')
                .eq(0)
                .should('have.text','tag1')
        })
        */
          if (Cypress._.isEmpty(article.tagList)) {
            cy.get('@articlePreview')
              .find('a.preview-link > ul.tag-list')
              .should('be.empty')
          } else {
            cy.get('@articlePreview')
              .find('a.preview-link > ul.tag-list > li.tag-default')
              .each(($tag, tagIndex) => {
                expect($tag).to.have.text(article.tagList[tagIndex])
              })
          }
        }).
    })
  })
})

step - 3

import { DateFormats } from '../support/enums'

describe('Global Feed', () => {
  beforeEach(() => {
    cy.server()

    cy.route('**/api/articles**', 'fx:global_feed.json').as('getArticles')
  })

  it('Should show all articles correctly', () => {
    cy.visit('/')

    cy.wait('@getArticles').then((xhr) => {
      const { articles } = xhr.responseBody as GetArticles

      	/** cy.findByTestId('article-preview') 
        -> data-test-id={`article-preview-${article.slug}`} 
        -> cy.findByTestId('article-preview-${article.slug}')
        **/
      cy.findAllByTestId('article-preview')
        .should('have.length', articles.length)
        .each(($articlePreview, index) => {
          const article: Article = articles[index]

          cy.wrap($articlePreview).as('articlePreview')
        
          cy.get('@articlePreview').findByRole('img')
          // elememts get a role using Aria attributes 
          .should('have.attr', 'src', article.author.image)

          // Addressed in step 3
          // cy.get('@articlePreview').findByRole('link').should('exist')

          cy.get('@articlePreview')
            .findByText(
              Cypress.moment(article.createdAt).format(
                DateFormats.ArticlePreview
              )
            )
            .should('exist')

          cy.get('@articlePreview')
            .findByRole('button')
            .should('contain', article.favoritesCount)

          cy.get('@articlePreview')
            .findByRole('heading')
            .should('have.text', article.title)

          cy.get('@articlePreview')
            .findByText(article.description)
            .should('exist')

          if (Cypress._.isEmpty(article.tagList)) {
            cy.get('@articlePreview').findByRole('list').should('be.empty')
          } else {
            cy.get('@articlePreview')
              .findAllByRole('listitem')
           	  // query multiple items 
              .each(($tag, tagIndex) => {
                expect($tag).to.have.text(article.tagList[tagIndex])
              })
          }
        })
    })
  })
})

step - 4

import { DateFormats } from '../support/enums'

describe('Global Feed', () => {
  beforeEach(() => {
    cy.server()

    cy.route('**/api/articles**', 'fx:global_feed.json').as('getArticles')
  })

  it('Should show all articles correctly', () => {
    cy.visit('/')

    cy.wait('@getArticles').then((xhr) => {
      const { articles } = xhr.responseBody as GetArticles

      cy.findAllByTestId('article-preview')
        .should('have.length', articles.length)
        .each(($articlePreview, index) => {
          const article: Article = articles[index]

          cy.wrap($articlePreview).within(() => {
            cy.findByRole('img', { name: /Article author avatar/i }).should(
              'have.attr',
              'src',
              article.author.image
            )

            cy.findByRole('link', { name: article.author.username }).should(
              'exist'
            )

            cy.findByText(
              Cypress.moment(article.createdAt).format(
                DateFormats.ArticlePreview
              )
            ).should('exist')

            cy.findByRole('button', { name: /Favorite article/i }).should(
              'contain',
              article.favoritesCount
            )

            cy.findByRole('heading', { name: article.title }).should('exist')

            cy.findByText(article.description, { selector: 'p' }).should(
              'exist'
            )

            cy.findByRole('list', { name: /Tags/i }).within(($tagList) => {
              if (Cypress._.isEmpty(article.tagList)) {
                expect($tagList).to.be.empty
              } else {
                cy.findAllByRole('listitem').each(($tag, tagIndex) => {
                  expect($tag).to.have.text(article.tagList[tagIndex])
                })
              }
            })
          })
        })
    })
  })
})
  1. Use the date
  2. Query the dom at the highest level possible
  3. Narrow your scope
  4. Always optimize for change

References

profile
함께 성장하는 개발자가 되고 싶습니다.

0개의 댓글