简体   繁体   English

before 和 beforeEach 在 Cypress 中究竟是如何工作的?

[英]How exactly do before and beforeEach work in Cypress?

I think I am missing something about the way the before and beforeEach functions work in Cypress.我想我遗漏了一些关于 before 和 beforeEach 函数在 Cypress 中的工作方式。 I have a spec file that loads data from a fixture in the before method.我有一个 spec 文件,它在 before 方法中从夹具加载数据。 Some of that data is used in the before function, and then again in the beforeEach function, as well as in the actual tests.其中一些数据用于 before function,然后在 beforeEach function 以及实际测试中再次使用。 The spec file contains 2 tests.规范文件包含 2 个测试。 The first test executes as expected.第一个测试按预期执行。 The second fails because the beforeEach says that one of the values from the fixture is undefined.第二个失败,因为 beforeEach 表示来自夹具的值之一未定义。

My expectation is that if I load data from a fixture in the before method it should be available for all tests in the spec file.我的期望是,如果我在 before 方法中从固定装置加载数据,它应该可用于规范文件中的所有测试。

When executing the 'Check the state of the button bar' test the window.console.log(this.user_data) in the beforeEach function outputs the user_data as expected.执行“检查按钮栏的 state”时,测试 window.console.log(this.user_data) 在 beforeEach function 中按预期输出 user_data。

When executing the 'Submit form' test the window.console.log(this.user_data) in the beforeEach function outputs undefined and the test stops.执行“提交表单”测试时,beforeEach function 中的 window.console.log(this.user_data) 输出未定义,测试停止。

What am I missing here?我在这里错过了什么?

describe('Customer Profile', () => {

    before(function () {
        window.console.log('Enter the before function')
        // Load the fixture data. Its asynchronous so if we want to use it right here and now
        // we have to put the things that use inside a callback to be executed after the data
        // loaded. 
        cy.fixture('user').as('user_data').then(function (){
            window.console.log('Fixture has loaded the user data')
            cy.visit('/LaunchPad/login')
            // Fill out the login form
            cy.get('input[name="username"]').type(this.user_data.username)
            cy.get('input[name="password"]').type(this.user_data.password)
            cy.get('button[type="submit"]').click()
        })
    })

    beforeEach(function(){
        window.console.log('Enter the beforeEach function')
        window.console.log(this.user_data)
        // Preserve the cookies for all tests in this suite
        Cypress.Cookies.preserveOnce('SESSION')
        // Open the profile view
        cy.visit('/Manager/'+this.user_data.org+'/config/company-profile')
    })

    it('Check the state of the button bar', function(){
        window.console.log('Running test of button bar')
        cy.get('section.content-header > h1').contains('Company Profile Details')
        // Check the state of the action bar and its buttons
        cy.get('section.action-bar').get('label.btn.btn-sm.btn-primary').contains('Save')
            .should('have.attr', 'for', 'submit-form')
            .should('have.attr', 'tabindex', '0')

        cy.get('section.action-bar').get('#resetButton').contains('Reset')
            .should('have.attr', 'type', 'reset')
            .should('have.attr', 'value', 'Reset')
            .should('have.class', 'btn btn-sm btn-default')

        cy.get('section.action-bar').get('a.btn.btn-sm.btn-default').contains('Cancel')
            .should('have.attr', 'href', '/Manager/'+this.user_data.org+'/')

        cy.get('section.action-bar').get('a').contains('Delete').should('not.exist')

    })

    // This form has no required fields and no validation. So just pick a value or two
    // submit the form and verify the banner is correct
    it('Submit form', function(){
        window.console.log('Running the submit form test')
        cy.fixture('company_profile').as('company_profile')
        cy.get('#companyProfileDto.name').type(this.company_profile.name)
    })


})

UPDATE After doing some more reading based on what Carlos Alfredo responded with I came up this.更新在根据 Carlos Alfredo 的回应做了更多阅读之后,我想到了这个。 1. I still have to visit the login page. 1.我还是要访问登录页面。 We use csrf and OATH and trying to get working example is just taking too much time.我们使用 csrf 和 OATH 并试图让工作示例花费太多时间。 2. I have to use whitelisting for the session cookie because preserveOnce does not work at all. 2. 我必须对 session cookie 使用白名单,因为 preserveOnce 根本不起作用。

Here are the files I have now.这是我现在拥有的文件。 It visits the login page once and gets the session cookies setup.它访问登录页面一次并获得 session cookies 设置。 The two tests proceed as expected.两项测试按预期进行。

support/index.js支持/index.js

before(function(){
    cy.login('bob', 'password')
    cy.fixture('user').as('user_data')
    cy.fixture('company_profile').as('company_profile')
})

beforeEach(function(){
    window.console.log('Enter the global beforeEach function')
    // Load the fixture data

})

support/commands.js支持/commands.js

/*
    We visit the login form despite what the best practise recommendation is because
    we have OAUTH redirects and a CSRF token to deal with. Since the majority of the
    examples and working use cases provided dont deal with those scenarios this is 
    the best I can do at the moment. 
*/
Cypress.Commands.add('login', (username, password, options = {}) => {
    cy.visit('/LaunchPad/login')
    // Fill out the login form
    cy.get('input[name="username"]').type(username)
    cy.get('input[name="password"]').type(password)
    cy.get('button[type="submit"]').click()
})
/*
    We are white listing the cookie because Cypress.Cookies.preserveOnce('SESSION') 
    does not work. https://github.com/cypress-io/cypress/issues/2952

    Because we are forcing Cypress to not clear the cookies, you will have to close
    the test window after the suite is completed other wise the Vision360 apps will
    think your session is alive.
*/
Cypress.Cookies.defaults({
    whitelist: 'SESSION'
})

integration/customer_profile/customer_profile_spec.js集成/customer_profile/customer_profile_spec.js

describe('Customer Profile', () => {

    it('Check the state of the button bar', function(){
        window.console.log('Running test of button bar')

        cy.visit('/Manager/'+this.user_data.org+'/config/company-profile')

        cy.get('section.content-header > h1').contains('Company Profile Details')
        // Check the state of the action bar and its buttons
        cy.get('section.action-bar').get('label.btn.btn-sm.btn-primary').contains('Save')
            .should('have.attr', 'for', 'submit-form')
            .should('have.attr', 'tabindex', '0')

        cy.get('section.action-bar').get('#resetButton').contains('Reset')
            .should('have.attr', 'type', 'reset')
            .should('have.attr', 'value', 'Reset')
            .should('have.class', 'btn btn-sm btn-default')

        cy.get('section.action-bar').get('a.btn.btn-sm.btn-default').contains('Cancel')
            .should('have.attr', 'href', '/Manager/'+this.user_data.org+'/')

        cy.get('section.action-bar').get('a').contains('Delete').should('not.exist')

    })

    // This form has no required fields and no validation. So just pick a value or two
    // submit the form and verify the banner is correct
    it('Submit form', function(){
        window.console.log('Running the submit form test')
        cy.visit('/Manager/'+this.user_data.org+'/config/company-profile')

        // Fill and submit the form
        cy.get('input#companyProfileDto\\.name').clear().type(this.company_profile.name)
        cy.get('section.action-bar').get('label.btn.btn-sm.btn-primary').contains('Save').click()

        // Check the response
        cy.get('.callout-success').contains('Your changes are saved.')
        cy.get('input#companyProfileDto\\.name').should('have.value', this.company_profile.name)
    })

})

I ran into a problem almost like yours with before.我遇到了一个几乎和你之前一样的问题。

before() runs before the next block of code. before()在下一个代码块before()运行。

describe("Some test", function() {
    before(function() {
        //something ...
        cy.request('POST', loginUrl, loginInformation).its('body').as('currentUser')
    })

    // this.currentUser exists here
    it("Should foo", function() {
    })

    // this.currentUser doesn't exist here
    it("Should bar", function() {
    })
})

I fixed my problem moving the before() statement out from the describe scope.我解决了将before()语句从描述范围移出的问题。

before(function() {
    //something ...
    cy.request('POST', loginUrl, loginInformation).its('body').as('currentUser')
})

describe("Some test", function() {     
    // this.currentUser exists here
    it("Should foo", function() {
    })

    // this.currentUser also exists here
    it("Should bar", function() {
    })
})

I hope this can help you solve your problem.我希望这可以帮助您解决您的问题。

It looks like cypress is cleaning your fixtures for each test.看起来 cypress 正在为每个测试清洁您的fixtures

From Cypress guides( https://docs.cypress.io/guides/references/best-practices.html#Dangling-state-is-your-friend )来自赛普拉斯指南( https://docs.cypress.io/guides/references/best-practices.html#Dangling-state-is-your-friend

"We have built Cypress to support this use case. In fact, Cypress does not clean up its own internal state when the test ends. We want you to have dangling state at the end of the test! Things like stubs, spies, even routes are not removed at the end of the test. This means your application will behave identically while it is running Cypress commands or when you manually work with it after a test ends." “我们已经构建了 Cypress 来支持这个用例。事实上,当测试结束时,Cypress 不会清理自己的内部状态。我们希望你在测试结束时有悬空状态!像存根、间谍、甚至路由之类的东西不会在测试结束时删除。这意味着您的应用程序在运行赛普拉斯命令时或在测试结束后手动使用它时的行为将相同。”

There's nothing explicit about fixtures in that page but, it looks like its happening and fixtures are cleaned too.该页面中没有任何关于fixtures的明确说明,但是看起来它正在发生并且fixtures也被清理了。 Thats the beforeEach() works in the first test (Check the state of the button bar), because it runs exactly after the before() .这就是beforeEach()在第一个测试中工作(检查按钮栏的状态),因为它正好在before()之后运行。 For the second test all that has been setted in the before hook is gone and now the beforeEach() is trying to get fixtures that were never defined.对于第二个测试,在before钩子中设置的所有内容都消失了,现在beforeEach()试图获取从未定义的设备。

Hope it helps.希望能帮助到你。

A suggestions your probably now: - Is a good practice to do your login as a custom command skiping the UI, using just the request.( https://docs.cypress.io/api/cypress-api/custom-commands.html#Custom-login-command )您现在可能提出的建议: - 将您的登录作为自定义命令跳过 UI,仅使用请求是一种很好的做法。( https://docs.cypress.io/api/cypress-api/custom-commands.html #自定义登录命令)

  • before() runs once before all your code. before()在所有代码之前运行一次。
  • beforeEach() runs before each of your code blocks. beforeEach()在每个代码块之前运行。

As such any code, you put in the before() function will only run once and due to cypress clearing states before each test, any code that is put in the before() function will be cleared.因此,您放入before() function 中的任何代码都只会运行一次,并且由于赛普拉斯在每次测试之前都会清除状态,所以放入before() function 中的任何代码都将被清除。

You need to put the code in the beforeEach() function to be able to use it in subsequent blocks.您需要将代码放在beforeEach() function 中,以便能够在后续块中使用它。

If you want to make a fixture accessible to all its blocks you need to put it in the beforeEach() function.如果你想让一个夹具可以被它的所有块访问,你需要把它放在beforeEach() function 中。

A simpler way to achieve this for JSON fixtures is to use an import statement, for example:为 JSON 灯具实现此目的的更简单方法是使用导入语句,例如:

import user from '../../fixtures/users.json'

In your it block you can call it:在您的 it 块中,您可以调用它:

it('do something with the fixture',()=>{
  cy.log(users)
})

If you save out a set of session cookies, the fastest method maybe to hard-code / brute force them into life such as:如果您保存了一组会话 cookie,最快的方法可能是硬编码 / 蛮力使其生效,例如:

  beforeEach(() => {
    Cypress.Cookies.preserveOnce('SESSION')     // remember cookies for this session:
    cy.setCookie('logged_in_cookie_name', '000000000000000000000') // hard coded session cookie
    cy.setCookie('security_salt_etc', '000000000000000000000') // its like a revokable password but more secure
  })
  after(() => {    // logout!
    cy.clearCookies()
  })

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM