使用 Cypress 进行 UI 测试,并使用 ADFS 对 Azure AD 进行身份验证

Dav*_*ndo 5 adfs ui-testing azure azure-ad-msal cypress

这些是我关于如何使用 MSAL.js 和 ADFS(在我们的示例中是本地部署)以及与令牌创建和本地存储过程关联的架构对 Azure AD 单页应用程序进行 UI 测试的注释。

教程中:“它使用 ROPC 身份验证流程来获取测试用户帐户的令牌,并在运行测试之前将它们注入到浏览器本地存储中。这样,MSAL.js 就不会尝试获取令牌,因为它已将令牌存储在缓存中”。

观看精彩视频后: https://www.youtube.com/watch ?v=OZh5RmCztrU

...并浏览此处的存储库: https://github.com/juunas11/AzureAdUiTestAutomation

我一直试图将本地 ADFS 与 MSAL.js 2.0 和会话存储的使用与上述教程和代码相匹配。因此,如果您使用以/adfs/oauth2/token结尾的 Azure 链接 (而不是 oAuth /oauth2/v2.0/token ),请按照以下步骤操作!

我所做的大部分更改来自 auth.js:https://github.com/juunas11/AzureAdUiTestAutomation/blob/main/UiTestAutomation.Cypress/cypress/support/auth.js

只需按照教程复制该内容,然后更改以下内容:

  • 常量环境='';(我的是公司域名而不是login.windows.net)
  • 对于帐户实体 (const buildAccountEntity) 使用:
    authorityType: 'ADFS', ...并删除行:clientInfo: "",
  • 对于访问令牌实体: (const buildAccessTokenEntity): ...添加行: tokenType: 'bearer',
  • 为刷新令牌(新)实体添加新函数:
    const buildRefreshTokenEntity = (homeAccountId: string, accessToken: string) => {
        return {
            clientId,
            credentialType: 'RefreshToken',
            environment,
            homeAccountId,
            secret: accessToken,
        };
    };
Run Code Online (Sandbox Code Playgroud)
  • 接下来,我必须通过使用 VS Code 在本地运行它并登录来匹配我的 sessionStorage TOKEN,然后对所存储的内容进行逆向工程所需的键值对(结果在下一个代码块中!)。
  • 具体来说,我保留了“家庭帐户”的区分大小写,我删除了一些值,并且必须添加 RefreshToken 部分,并且我使用了会话存储(不是本地存储),并将延长的过期时间与相同的值相匹配(基于仅在我的示例中运行):
const injectTokens = (tokenResponse: any) => {
    const scopes = ['profile', 'openid'];
    const idToken: JwtPayload = decode(tokenResponse.id_token) as JwtPayload;
    const localAccountId = idToken.sub; // in /oauth2/v2.0/token this would be: idToken.oid || idToken.sid;  however we are using /adfs/oauth2/token
    const realm = ''; // in /oauth2/v2.0/token this would be: idToken.tid;  however we are using /adfs/oauth2/token
    const homeAccountId = `${localAccountId}`; // .${realm}`;
    const homeAccountIdLowerCase = `${localAccountId}`.toLowerCase(); // .${realm}`;
    const usernameFromToken = idToken.upn; // in /oauth2/v2.0/token this would be: idToken.preferred_username;  however we are using /adfs/oauth2/token
    const name = ''; // in /oauth2/v2.0/token this would be: idToken.name;  however we are using /adfs/oauth2/token
    const idTokenClaims = JSON.stringify(idToken);

    const accountKey = `${homeAccountIdLowerCase}-${environment}-${realm}`;
    const accountEntity = buildAccountEntity(homeAccountId, realm, localAccountId, idTokenClaims, usernameFromToken, name);

    const idTokenKey = `${homeAccountIdLowerCase}-${environment}-idtoken-${clientId}-${realm}-`;
    const idTokenEntity = buildIdTokenEntity(homeAccountId, tokenResponse.id_token, realm);

    const accessTokenKey = `${homeAccountIdLowerCase}-${environment}-accesstoken-${clientId}-${realm}-${scopes.join(' ')}`;
    const accessTokenEntity = buildAccessTokenEntity(
        homeAccountId,
        tokenResponse.access_token,
        tokenResponse.expires_in,
        tokenResponse.expires_in, // ext_expires_in,
        realm,
        scopes,
    );

    const refreshTokenKey = `${homeAccountIdLowerCase}-${environment}-refreshtoken-${clientId}-${realm}`;
    const refreshTokenEntity = buildRefreshTokenEntity(homeAccountId, tokenResponse.access_token);

    // localStorage was not working, needs to be in sessionStorage
    sessionStorage.setItem(accountKey, JSON.stringify(accountEntity));
    sessionStorage.setItem(idTokenKey, JSON.stringify(idTokenEntity));
    sessionStorage.setItem(accessTokenKey, JSON.stringify(accessTokenEntity));
    sessionStorage.setItem(refreshTokenKey, JSON.stringify(refreshTokenEntity));
};
Run Code Online (Sandbox Code Playgroud)
  • 最后,在登录函数中,我使用了 /adfs 链接,因为我们使用本地 ADFS 和 MSAL.js v2.0,并且不需要 client_secret:
export const login = (cachedTokenResponse: any) => {
    let tokenResponse: any = null;
    let chainable: Cypress.Chainable = cy.visit('/'); // need to visit root to be able to store Storage against this site

    if (!cachedTokenResponse) {
        chainable = chainable.request({
            url: authority + '/adfs/oauth2/token', // was this '/oauth2/v2.0/token',
            method: 'POST',
            body: {
                grant_type: 'password',
                client_id: clientId,
                // client_secret: clientSecret,
                scope: ['profile openid'].concat(apiScopes).join(' '),
                username,
                password,
            },
            form: true,
        });
***... MORE CODE OMITTED*** 
Run Code Online (Sandbox Code Playgroud)
  • 最后我使用 VSCode 终端 1(yarn start)然后终端 2(yarn run cypress open)运行

打字稿使用:

  • 将所有文件从 .js 重命名为 .ts
  • 更新 tsconfig 以包含此行中的 cypress 类型:
"types": ["node", "cypress"],
Run Code Online (Sandbox Code Playgroud)

现在,当我运行 Cypress 时,我可以在我的网站上导航,并且我已经通过了身份验证!希望这可以帮助您节省一两个小时!