在Cypress中跨测试保留cookies/localStorage会话

59

我希望能够保存/持久化/保留由 cy.request() 设置的cookie或localStorage令牌,这样我就不必在每次测试中使用自定义命令登录。这应该适用于像jwt(json web tokens)这样存储在客户端localStorage中的令牌。

11个回答

58

更新这个线程,现在已经有了更好的解决方案来保留cookie(由@bkucera提供); 但是现在还可以使用一种解决方法来保存和恢复测试之间的本地存储(如果需要的话)。我最近遇到了这个问题,并发现这个解决方案可行。

通过使用辅助命令并在测试中使用它们来实现此解决方案,

在- cypress/support/<some_command>.js内部

let LOCAL_STORAGE_MEMORY = {};

Cypress.Commands.add("saveLocalStorage", () => {
  Object.keys(localStorage).forEach(key => {
    LOCAL_STORAGE_MEMORY[key] = localStorage[key];
  });
});

Cypress.Commands.add("restoreLocalStorage", () => {
  Object.keys(LOCAL_STORAGE_MEMORY).forEach(key => {
    localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]);
  });
});

然后在测试中,

beforeEach(() => {
  cy.restoreLocalStorage();
});

afterEach(() => {
  cy.saveLocalStorage();
});

参考:https://github.com/cypress-io/cypress/issues/461#issuecomment-392070888


1
我确实按照这样做了,但是对我不起作用 :( 我看到每次测试后键都消失了。 - Citronex

53

来自Cypress 文档

保留 cookie:默认情况下,Cypress 在每次测试前自动清除所有 cookie 以防止状态建立。

您可以使用 Cypress.Cookies api 配置特定的 cookie 跨测试保留:

// now any cookie with the name 'session_id' will
// not be cleared before each test runs
Cypress.Cookies.defaults({
  preserve: "session_id"
})

注意:在 Cypress v5.0 之前,配置键是“whitelist”,而不是“preserve”。

用于持久化 localStorage:目前还没有内置方法,但是您可以手动实现,因为清除本地存储的方法已经作为 Cypress.LocalStorage.clear 公开暴露。

您可以备份此方法,并根据发送的密钥进行覆盖。

const clear = Cypress.LocalStorage.clear

Cypress.LocalStorage.clear = function (keys, ls, rs) {
  // do something with the keys here
  if (keys) {
    return clear.apply(this, arguments)
  }

}

2
注意:对于cookie,现在似乎是使用“preserve”而不是“whitelist”。 - webnoob
3
不好意思,我是个新手:那么我应该在哪里加入这个方法呢?是在command.js文件中吗?如果我想要添加一个特定的键,我该如何使用它?谢谢。 - Citronex
@Citronex 关于 Cypress 10.8.0 版本,请将此方法放在 support/e2e.js 中。 - O.Badr

11
你可以在Cypress中添加自己的登录命令,并使用cypress-localstorage-commands包在测试之间保留localStorage。
support/commands文件中:
import "cypress-localstorage-commands";

Cypress.Commands.add('loginAs', (UserEmail, UserPwd) => {
  cy.request({
    method: 'POST',
    url: "/loginWithToken",
    body: {
      user: {
        email: UserEmail,
        password: UserPwd,
      }
    }
  })
    .its('body')
    .then((body) => {
      cy.setLocalStorage("accessToken", body.accessToken);
      cy.setLocalStorage("refreshToken", body.refreshToken);
    });
});

在你的测试中:

describe("when user FOO is logged in", ()=> {
  before(() => {
    cy.loginAs("foo@foo.com", "fooPassword");
    cy.saveLocalStorage();
  });

  beforeEach(() => {
    cy.visit("/your-private-page");
    cy.restoreLocalStorage();
  });

  it('should exist accessToken in localStorage', () => {
    cy.getLocalStorage("accessToken").should("exist");
  });

  it('should exist refreshToken in localStorage', () => {
    cy.getLocalStorage("refreshToken").should("exist");
  });
});

6

我对本地存储不确定,但是对于 cookie,我最终做了以下操作,以便在测试之间 仅一次 存储所有 cookie。

beforeEach(function () {
  cy.getCookies().then(cookies => {
    const namesOfCookies = cookies.map(c => c.name)
    Cypress.Cookies.preserveOnce(...namesOfCookies)
  })
})

根据文档,Cypress.Cookies.defaults会在此后的每个测试运行中保留更改。我认为这不是理想的做法,因为这增加了测试套件之间的耦合度。
我在Cypress问题中添加了一个更健壮的响应:https://github.com/cypress-io/cypress/issues/959#issuecomment-828077512 我知道这是一个旧问题,但无论如何希望分享我的解决方案以防有人需要。

6

1
请问您能否解释一下这个解决方案是如何解决问题的?https://stackoverflow.com/help/how-to-answer - Marcello B.
每个规范都有一个describe(),其中包含一系列测试,以单个登录开始。每个测试都在it()中,并且我们希望在使用JWT登录时在it()之间保留DOM和localStorage。 - Leo Davtyan
请将此添加到您的答案中。 - Marcello B.
1
这只是一个部分回答,你的函数缺少相当多的信息并且不完整。也许更新以显示整个过程。 - Daniel

2

2023年更新的Cypress v12或更高版本:

自Cypress版本12以来,您可以使用新的cy.session()

它会缓存和恢复cookies、localStorage和sessionStorage(即会话数据),以便在测试之间重新创建一致的浏览器上下文。

以下是如何使用它:

// Caching session when logging in via page visit
cy.session(name, () => {
  cy.visit('/login')
  cy.get('[data-test=name]').type(name)
  cy.get('[data-test=password]').type('s3cr3t')
  cy.get('form').contains('Log In').click()
  cy.url().should('contain', '/login-successful')
})

1
我不明白你是如何恢复那个会话的,例如在同一个规范文件中的另一个测试中... - Stiegi
是的,您也可以在我上面添加的文档链接中查看它,缓存和恢复cookie、localStorage和sessionStorage(即会话数据),以便在测试之间重新创建一致的浏览器上下文。 - Ahmed Khashaba

1
为了保留谷歌令牌 cookie,有一个叫做 cypress-social-login 的库。似乎它也支持其他 OAuth 提供商。这是 cypress 团队推荐的,并且可以在 cypress 插件页面找到。

https://github.com/lirantal/cypress-social-logins

这个Cypress库使得第三方登录(比如OAuth)变得可能,支持GitHub、Google或Facebook等服务。它通过将登录流程委托给puppeteer流程来实现登录,并返回应用程序的cookie,以便调用Cypress流程在测试期间设置cookie。

1

尝试这个,这在我的情况下有效

// Save session data
let savedCookies;
let savedLocalStorage;
let savedSessionStorage;

before(() => {
  // Save session data
  cy.visit('https://example.com/login'); // Visit the login page

  // Log in and save session data
  cy.get('#username').type('your_username');
  cy.get('#password').type('your_password');
  cy.get('#login-button').click();

  // Save cookies
  cy.getCookies().then((cookies) => {
    savedCookies = cookies;
  });

  // Save localStorage
  savedLocalStorage = window.localStorage;

  // Save sessionStorage
  savedSessionStorage = window.sessionStorage;
});

// Restore session data
beforeEach(() => {
  // Restore cookies
  cy.clearCookies();
  savedCookies.forEach((cookie) => {
    cy.setCookie(cookie.name, cookie.value, {
      domain: cookie.domain,
      path: cookie.path,
      secure: cookie.secure,
      httpOnly: cookie.httpOnly,
      expiry: cookie.expires,
    });
  });

  // Restore localStorage
  Object.keys(savedLocalStorage).forEach((key) => {
    window.localStorage.setItem(key, savedLocalStorage.getItem(key));
  });

  // Restore sessionStorage
  Object.keys(savedSessionStorage).forEach((key) => {
    window.sessionStorage.setItem(key, savedSessionStorage.getItem(key));
  });
});

// Your Cypress test code
describe('My tests', () => {
  it('Test 1', () => {
    // The user is already logged in due to the before() hook

    // Your test assertions
  });

  it('Test 2', () => {
    // The user is still logged in due to the before() hook

    // Your test assertions
  });

  // More tests...
});


0
对于Cypress 12,我们可以在`cy.session`函数中传递选项`cacheAcrossSpecs`。如果我们有多个需要会话的规范文件,这将非常有用。我们只需请求一次会话。
这是来自Cypress文档的参考。参考链接

0

我可以看到建议使用白名单。但在期间似乎不起作用。 分别在before()和beforeEach()中尝试了以下方法:

Cypress.Cookies.defaults({
  whitelist: "token"
})

并且

Cypress.Cookies.preserveOnce('token');

但似乎没有一个方法有效。但在使用cypress open即图形界面模式时,这两种方法都很好用。有没有任何想法,我做错了什么?


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