You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SpecFlow中从浏览器获取AuthToken复用登录状态?

一、核心思路

避免重复执行登录流程,仅在当前Feature的第一个Scenario中完成一次Azure登录,将认证信息(Cookie或Token)存入全局上下文,后续Scenario直接复用该信息跳过登录,解决频繁登录导致的系统过载问题。

二、具体实现方案

方法1:复用浏览器Cookie(推荐,兼容性更强)

登录成功后从WebDriver提取认证Cookie,存入FeatureContext全局上下文,后续Scenario直接加载该Cookie即可保持登录状态:

using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System.Linq;
using TechTalk.SpecFlow;

[Binding]
public class AuthHooks
{
    private readonly IWebDriver _webDriver;
    private readonly FeatureContext _featureContext;

    public AuthHooks(IWebDriver webDriver, FeatureContext featureContext)
    {
        _webDriver = webDriver;
        _featureContext = featureContext;
    }

    // 初始化登录标记,仅在Feature启动时执行一次
    [BeforeFeature(Order = 1)]
    public static void InitLoginFlag(FeatureContext featureContext)
    {
        featureContext["HasLoggedIn"] = false;
    }

    [BeforeScenario(Order = 1)]
    public void PrepareAuthState()
    {
        if (!(bool)_featureContext["HasLoggedIn"])
        {
            // 执行完整登录流程
            ExecuteAzureLogin();
            // 提取并存储认证Cookie(根据实际站点调整Cookie名称)
            var authCookies = _webDriver.Manage().Cookies.AllCookies
                .Where(c => c.Name is ".AspNetCore.Cookies" or "ARRAffinity" or "AzureADAuthCookie")
                .ToList();
            _featureContext["AuthCookies"] = authCookies;
            _featureContext["HasLoggedIn"] = true;
        }
        else
        {
            // 复用已存储的Cookie
            var storedCookies = (List<Cookie>)_featureContext["AuthCookies"];
            foreach (var cookie in storedCookies)
            {
                _webDriver.Manage().Cookies.AddCookie(cookie);
            }
            // 跳转系统首页,无需再次登录
            _webDriver.Navigate().GoToUrl("你的系统首页URL");
        }
    }

    private void ExecuteAzureLogin()
    {
        _webDriver.Navigate().GoToUrl("你的Azure登录入口URL");
        
        // 显式等待元素加载,替代隐式等待更可靠
        var wait = new WebDriverWait(_webDriver, TimeSpan.FromSeconds(15));
        
        // 输入账号
        var accountInput = wait.Until(d => d.FindElement(By.Id("i0116")));
        accountInput.SendKeys("测试账号");
        wait.Until(d => d.FindElement(By.Id("idSIButton9"))).Click();
        
        // 输入密码
        var pwdInput = wait.Until(d => d.FindElement(By.Id("i0118")));
        pwdInput.SendKeys("测试密码");
        wait.Until(d => d.FindElement(By.Id("idSIButton9"))).Click();
        
        // 处理保持登录提示(如果存在)
        wait.Until(d => d.FindElement(By.Id("idBtn_Back"))).Click();
        
        // 等待跳转到系统首页,确认登录成功
        wait.Until(d => d.Url.Contains("你的系统首页标识"));
    }
}

方法2:提取并复用localStorage中的OIDC Token

如果系统通过OIDC将Token存储在浏览器localStorage中,可通过JavaScript提取Token,后续接口请求直接携带Token:

// 在登录成功后调用该方法提取Token
private string GetOidcToken()
{
    var jsExecutor = (IJavaScriptExecutor)_webDriver;
    // 替换为实际的localStorage键名,注意转义引号
    var tokenKey = "oidc.user:https://login.microsoftonline.com/你的租户ID/v2.0:你的客户端ID";
    var tokenJson = jsExecutor.ExecuteScript($"return localStorage.getItem('{tokenKey}');") as string;

    if (!string.IsNullOrEmpty(tokenJson))
    {
        // 解析JSON提取access_token,需引用Newtonsoft.Json
        var tokenObj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(tokenJson);
        return tokenObj.access_token;
    }
    return null;
}

提取到Token后,存入FeatureContext,后续Scenario中发起接口请求时,直接在请求头添加Authorization: Bearer {token}即可。

三、关键注意事项

  • 上下文选择:使用FeatureContext而非ScenarioContext,确保同一Feature下的所有Scenario复用同一登录状态;若需跨Feature复用,可通过ScenarioContext.ScenarioContainer注册单例服务存储认证信息。
  • 有效期处理:Azure AD的Cookie和Token存在有效期,测试前确认有效期,避免中途失效导致测试失败。
  • 显式等待:登录流程中必须使用WebDriverWait显式等待元素加载,避免因页面加载延迟引发的元素定位失败。

内容的提问来源于stack exchange,提问作者oisin1min

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 16:17:38