如何使用 Cypress 和 Auth0 测试单页应用程序

How to test single page application with Cypress and Auth0

我有一个隐藏在 Auth0 锁后面的单页应用程序,使用 @auth0/auth0-spa-js. I would like to test it using Cypress, so I have decided to follow the official Auth0 blog post, as well as Johnny Reilly blog post

我能够使用建议的请求从 auth0 成功检索到有效的 JWT 令牌。我不知道该怎么办:(

我面临的问题是,上述两种方法都依赖于应用程序在本地存储 JWT 令牌(在 cookie 或 localstorage 中)。然而,@auth0/auth0-spa-js 使用不同的方法,我假设所有相关的 cookies/localstorage 都存储在 auth0 域中。

你有什么想法,是否有办法绕过它?

2018 年 7 月 here 报告了一个类似的问题,但并未真正提供任何解决方案

我在 @auth0/auth0-spa-js github 上发现了一个已解决的问题。 approach suggested by cwmrowe 似乎有效

解决方案是使用在 e2e 测试端生成的令牌模拟 oauth/token 端点的响应。

这种方法似乎对我们有用

我正在复制 cwmrowe 提供的示例代码

Cypress.Commands.add(
  'login',
  (username, password, appState = { target: '/' }) => {
    cy.log(`Logging in as ${username}`);
    const options = {
      method: 'POST',
      url: Cypress.env('Auth0TokenUrl'),
      body: {
        grant_type: 'password',
        username,
        password,
        audience: Cypress.env('Auth0Audience'),
        scope: 'openid profile email',
        client_id: Cypress.env('Auth0ClientId'),
        client_secret: Cypress.env('Auth0ClientSecret')
      }
    };
    cy.request(options).then(({ body }) => {
      const { access_token, expires_in, id_token } = body;

      cy.server();

      // intercept Auth0 request for token and return what we have
      cy.route({
        url: 'oauth/token',
        method: 'POST',
        response: {
          access_token,
          expires_in,
          id_token,
          token_type: 'Bearer'
        }
      });

      // Auth0 SPA SDK will check for value in cookie to get appState
      // and validate nonce (which has been removed for simplicity)
      const stateId = 'test';
      const encodedAppState = encodeURI(JSON.stringify(appState));
      cy.setCookie(
        `a0.spajs.txs.${stateId}`,
        `{%22appState%22:${encodedAppState}%2C%22scope%22:%22openid%20profile%20email%22%2C%22audience%22:%22default%22}`
      );

      const callbackUrl = `/auth/callback?code=test-code&state=${stateId}`;
      return cy.visit(callbackUrl);
    });
  }
);

declare namespace Cypress {
  interface Chainable<Subject> {
    login(
      username: string,
      password: string,
      appState?: any
    ): Chainable<Subject>;
  }
}

虽然不建议使用 UI 登录,但我在所有测试之前自己做了一次,然后使用静默身份验证进行测试:- cy.visit("/") 静默身份验证并允许访问应用程序。

integration/app.js

describe("App", () => {
  before(() => {
    Cypress.config("baseUrl", "http://localhost:3000");
    cy.login();
  });

  /** Uses silent auth for successive tests */
  beforeEach(() => {
    cy.restoreLocalStorage();
  });

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

  /** tests */

support/commands.js

/**
 * Auth0 login
 * https://github.com/cypress-io/cypress/issues/461#issuecomment-392070888
 *
 * Allows silent auth login between tests
 */
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]);
  });
});

Cypress.Commands.add("clearLocalStorage", () => {
  LOCAL_STORAGE_MEMORY = {};
});

对于 Google 登录 Cypress 有问题的用户,请查看插件:https://github.com/lirantal/cypress-social-logins/

  it('Login through Google', () => {
    const username = Cypress.env('googleSocialLoginUsername')
    const password = Cypress.env('googleSocialLoginPassword')
    const loginUrl = Cypress.env('loginUrl')
    const cookieName = Cypress.env('cookieName')
    const socialLoginOptions = {
      username,
      password,
      loginUrl,
      headless: false,
      isPopup: true,
      logs: false,
      loginSelector: 'a[href="/auth/auth0/google-oauth2"]',
      postLoginSelector: '.account-panel'
    }

    return cy.task('GoogleSocialLogin', socialLoginOptions).then(({cookies}) => {
      cy.clearCookies()

      const cookie = cookies.filter(cookie => cookie.name === cookieName).pop()
      if (cookie) {
        cy.setCookie(cookie.name, cookie.value, {
          domain: cookie.domain,
          expiry: cookie.expires,
          httpOnly: cookie.httpOnly,
          path: cookie.path,
          secure: cookie.secure
        })

        Cypress.Cookies.defaults({
          whitelist: cookieName
        })
      }
    })
  });