Cypress - 从函数设置全局变量

7
我是一款翻译引擎,只能翻译文本,并不能理解其含义。以下是您需要翻译的内容:

我有以下流程:

1)在一个页面上获取字段的值:

 var myID;
 cy.get('#MYID').
        then(($txt) => {
            myID=  $txt.text();
        })
       .should('not.equal', null); 

2) 然后我导航到一个新页面并尝试检查该新页面是否包含此ID:

cy.get('#myTable').find('td').contains(myID);

提示说myID未定义。我知道第一个函数是异步的,根据文档,我可以使用别名。问题是别名需要在beforeEach()函数中使用,但是由于某些原因,在这个测试用例中我不能使用它。我尝试使用async/await,但似乎并没有起作用,因为myID仍然未定义。


我然后导航到一个新页面。如果加载了一个新页面,所有先前页面的内容都将消失,包括JS变量。 - Teemu
根据您的问题,我理解#MYID在一个页面上,我们称之为页面1,而#myTable在另一个页面上...是这样吗? - soccerway
是的,没错。基本上我需要从第一页中提取myID的值,并检查它是否存在于第二页的表格中。 - user2924127
好的,创建一个函数在commands.js文件中返回'id',并在page2.testspec文件中调用...如果你卡住了就告诉我。 - soccerway
@soccerway 谢谢,我是cypress的新手,不知道这个文件。昨晚我按照文档https://docs.cypress.io/api/cypress-api/custom-commands.html尝试了一下,但是无法使其工作。我不确定如何“返回”它或者是否正确使用该文件。 - user2924127
4个回答

5
这里的基本问题是Cypress命令与创建它们的测试代码异步运行。如果在您的代码中放置控制台日志,您可以看到这一点。
var myID;
cy.get('#MYID')
  .then(($txt) => {
    myID=  $txt.text();
    console.log('1', myID);
  })
 .should('not.equal', null); 

console.log('2', myID);

这将打印出来

2 undefined
1 myText

你可以使用别名来解决这个问题,并将值传递到命令链中。

请参阅文档部分,其中展示了与你在“不要使用此示例”中使用的代码类似的模式。

但是,别名在测试之间被清除,因此你应该设置beforeEach()以获取所需ID的新副本用于每个测试。

你获取文本值的方式还有另一个问题。

如果没有返回语句,则.then()命令会将它接收到的任何主题传递给下一个命令。请见then-Yields

此外,回调函数中最后一个Cypress命令的结果将作为新主题生成,并流入下一个命令(如果没有返回)

因此,.should('not.equal',null)测试的是元素不为空,而不是文本不为空。

更好的方法是使用.invoke('text'),它等同于$txt.text()并将文本值提供给.should()
此外,.should('not.equal', null)不能测试内容是否存在,因为空元素从element.text()返回一个空字符串。请改用.should('not.equal', '')通过别名保存
describe('grabbing ID for use in multiple tests', () => {

  beforeEach(() => {
    cy.visit('my-page-1.html')
    cy.get('#MYID')
      .invoke('text')
      .as('mySavedID')
  })

  it('ID should not be null', () => {

    cy.get('@mySavedID')
      .should('not.equal', '')

  })

  it('ID should be found in table', () => {

    cy.visit('app/navigate-to-new-page-2.html');
    cy.get('@mySavedID').then(myID => {
      cy.get('#myTable').find('td').contains(myID);
    })

  })
})

通过排队设置变量来节省

如果访问页面#1需要很长时间,则别名模式可能不是理想的选择。

在这种情况下,您可以通过自定义命令来保存变量。不同之处在于,您在.then()中的代码被移动到一个排队的命令中,因此异步问题不会发生。

describe('grabbing ID for use in multiple tests', () => {

  let savedVariable;

  Cypress.Commands.add("saveVariable", {prevSubject: true}, (value) => {
    savedVariable = value;
  });

  it('id should not be null', () => {

    cy.visit('my-page-1')
    cy.get('#someId')
      .invoke('text')
      .should('not.equal', '')
      .saveVariable()

    // OR test the variable separately like this

    cy.wrap(savedVariable)
      .should('not.equal', '')
  })

  it('id should be found in table', () => {

    cy.visit('my-page-2');
    cy.get('#myTable').find('td').contains(savedVariable);

  })
})

注意
如果两个页面在同一个域内,例如SPA的两个页面,则上述内容有效。否则,当遇到新域时,测试运行程序会重置自身,并且所有JavaScript变量都会丢失。


这个回答太长了,而且不是很清晰。你能否修改一下呢?我也遇到了同样的问题,但是不知道你是怎么解决的。 - Rsaleh
1
嘿@Rsaleh,不幸的是这个问题很难解释。要理解的关键是Cypress有一个命令队列,它与测试中的javascript异步运行,因此事情发生的顺序是错乱的。您可以通过在各个点上放置console.log()语句来观察执行顺序。 - Richard Matsen
我实际上使用了then()建议来解决我的问题,但不好的是,我嵌套了步骤,只是为了确保所有在then内部的操作都会在before then操作之后开始。是的,cy.log()也很有用。谢谢@Richard。 - Rsaleh

3

在插件级别保存数据,并通过任务访问它。当调用 cy.visit 时,其他级别将重新加载。在 plugins/index.js 中添加 data 和任务:

// plugins/index.js   
/// <reference types="cypress" />
module.exports = (on, config) => {

  // data will be stored here
  const data = {};

  // configuring tasks
  on('task', {
    setValue: (params) => {
      const { key, value } = params;
      data[key] = value;
      return value;
    },
    getValue: (params) => {
      const { key } = params;
      return data[key] || null;
    }
  })        
}

并在场景中使用:

// my.spec.js
/// <reference types="cypress" />
context('keeps data safe when changing pages', () => {
  it('visits first page and saves data', () => {
    return cy.visit('https://google.com').then(() => {
      // saving data 
      return cy.task('setValue', { key: 'visited', value: 'google' });
    })
  });

  it('visits another page and checks that data is available', () => {
    return cy.visit('https://example.com').then(() => {
      // getting data
      return cy.task('getValue', { key: 'visited' });
    }).then((value) => {
      expect(value).to.equal('google');
    });
  })
});

1
太好了!只需记住:您必须包装您的返回值。cy.task('getValue', { key: 'visited' }).then((response) {在此处使用response进行魔法操作}); - Trip

0

不确定这是否是一个好主意,但您可以将数据保存在Cypress全局对象上。

context('Pass data via the Cypress object', () => {

  before(() => {
    Cypress._savedData = {}
  })

  it('visits first page and saves data', () => {

    cy.visit('https://google.com');
    cy.get('#MYID')
      .should('not.equal', null)
      .then($el => Cypress._savedData[myID] = $el.text() )

  });

  it('visits another page and checks that data is available', () => {

    cy.visit('https://example.com');
    cy.get('#myTable').find('td').contains(Cypress._savedData[myID]);

  })
});

0
在你的情况下,由于你有页面导航,使用别名可能行不通,特别是如果变量在别名创建之前被访问(由于异步执行和排队的then块)。
设置变量的最简单方法是利用浏览器的window对象,并将值分配给sessionStorage。
代码片段 -
var myID;
 cy.get('#MYID').
        then(($txt) => {
            window.sessionStorage.setItem(myID, $txt.text());
        })
       .should('not.equal', null); 

可以通过以下方式在测试文件中访问(甚至从另一个测试用例中):
let myID = window.sessionStorage.getItem('myID');
cy.get('#myTable').find('td').contains(myID);

请注意,这是一种反模式,即一个测试依赖于另一个测试的输出。这也在Cypress 最佳实践中有所描述。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接