Как запустить тесты Internet Explorer Selenium в качестве конкретного пользователя домена?

У меня есть веб-сайт ASP.NET MVC, который использует проверку подлинности Windows для контроля доступа. Я хотел бы иметь тест селеневого потока, который проверяет правильность конфигурации, пытаясь посетить сайт как неавторизованный пользователь.

Поскольку мы используем учетные записи домена для контроля доступа, нет экрана входа имени пользователя и пароля. Учетные данные текущего пользователя автоматически передаются на сайт браузером.

Итак, для моего теста Selenium мне нужно иметь возможность запускать Internet Explorer в качестве конкретного пользователя.

Я нашел несколько статей о олицетворении окон, и я могу переключиться на своего тестового пользователя во время запуска теста (используя код из http://support.microsoft.com/kb/306158). Однако, если я создаю InternetExplorerDriver, он запускает Internet Explorer с моими учетными данными, а не тестовым пользователем (хотя этот вопрос и ответ подсказывают, что он должен работать https://sqa.stackexchange.com/info/2277/using-selenium-webdriver-with-windows-authentication).

Я также могу явно запустить процесс Internet Explorer в качестве моего тестового пользователя, но я не вижу способа привязки InternetExplorerDriver к уже запущенному процессу Internet Explorer, поэтому это может быть тупиком.

Мой код, в основном взятый с приведенной выше страницы MSDN, приведен ниже. В отладчике я вижу, что WindowsIdentity.GetCurrent(). Имя является "testUser" на всех этапах теста.

namespace MyProject.Specs
{
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.IE;
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using TechTalk.SpecFlow;

[Binding]
public class AuthorisationSteps
{
    public const int LOGON32_LOGON_INTERACTIVE = 2;
    public const int LOGON32_PROVIDER_DEFAULT = 0;
    private static WindowsImpersonationContext impersonationContext;
    private static IWebDriver driver;

    [BeforeScenario]
    public static void impersonateUser()
    {
        if (!impersonateValidUser("testUser", "testDomain", "password"))
        {
            throw new Exception();
        }
        driver = new InternetExplorerDriver();
    }

    [AfterScenario]
    public static void cleanupUser()
    {
        undoImpersonation();
        driver.Quit();
    }

    [Given(@"I am an unauthorised user")]
    public void GivenIAmAnUnauthorisedUser()
    {
        var temp = WindowsIdentity.GetCurrent().Name;
    }

    [When(@"I go to the home page")]
    public void WhenIGoToTheHomePage()
    {
        var temp = WindowsIdentity.GetCurrent().Name;
        driver.Navigate().GoToUrl(BaseUrl);
    }

    [Then(@"I should see an error page")]
    public void ThenIShouldSeeAnErrorPage()
    {
        var temp = WindowsIdentity.GetCurrent().Name;
        Assert.That(driver.Title.Contains("Error"));
    }

    [DllImport("advapi32.dll")]
    public static extern int LogonUserA(String lpszUserName,
                                        String lpszDomain,
                                        String lpszPassword,
                                        int dwLogonType,
                                        int dwLogonProvider,
                                        ref IntPtr phToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int DuplicateToken(IntPtr hToken,
                                            int impersonationLevel,
                                            ref IntPtr hNewToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool RevertToSelf();

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    public static extern bool CloseHandle(IntPtr handle);

    private static bool impersonateValidUser(String userName, String domain, String password)
    {
        WindowsIdentity tempWindowsIdentity;
        var token = IntPtr.Zero;
        var tokenDuplicate = IntPtr.Zero;

        if (RevertToSelf())
        {
            if (LogonUserA(userName, domain, password, LOGON32_LOGON_INTERACTIVE,
                LOGON32_PROVIDER_DEFAULT, ref token) != 0)
            {
                if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                {
                    tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                    impersonationContext = tempWindowsIdentity.Impersonate();
                    if (impersonationContext != null)
                    {
                        CloseHandle(token);
                        CloseHandle(tokenDuplicate);
                        return true;
                    }
                }
            }
        }
        if (token != IntPtr.Zero)
        {
            CloseHandle(token);
        }
        if (tokenDuplicate != IntPtr.Zero)
        {
            CloseHandle(tokenDuplicate);
        }
        return false;
    }

    private static void undoImpersonation()
    {
        impersonationContext.Undo();
    }
}

}

Ответы

Ответ 1

Это действительно возможно. Я столкнулся с точной проблемой. В основном, вот шаги, которые вам нужно выполнить.

  • Запустите драйвер браузера вручную с другими учетными данными пользователя в фоновом режиме

    Process driverProcess;
    string driverPath; // The path to Selenium IE driver.
    ProcessStartInfo info = new ProcessStartInfo(driverPath)
    {
        UserName = "UserName", // The user name.
        Password = new SecureString(), // The password for the user.
        UseShellExecute = false,
        LoadUserProfile = true,
        Arguments = "about:blank"
    };
    // Start the driver in background thread
    Thread startThread = new Thread(
        () => {
            try
            {
                driverProcess = Process.Start(info);
                driverProcess.WaitForExit();
            }
            catch
            {
                // Close the process.
            }
        })
    {
        IsBackground = true
    };
    startThread.Start();
    
  • Используйте Remote Web Driver для подключения экземпляра драйвера браузера, запущенного вручную.

    var remoteDriver = new RemoteWebDriver(Uri("http://localhost:5555"), DesiredCapabilities.InternetExplorer());
    
  • Не забудьте закрыть/закрыть/завершить процесс драйвера и экземпляр браузера, когда вы закончите.

    // Close the process when done.
    if (driverProcess != null)
    {
        // Free managed resources
        if (!driverProcess.HasExited)
        {
            driverProcess.CloseMainWindow();
            driverProcess.WaitForExit(5000);
            // Kill the process if the process still alive after the wait
            if (!driverProcess.HasExited)
            {
                driverProcess.Kill();
            }
    
            driverProcess.Close();
        }
    
        driverProcess.Dispose();
        driverProcess = null;
    }
    

Ответ 2

У нас есть много корпоративных клиентов, которые используют проверку подлинности Windows для приложений, обращенных к интрасети, и мы запускаем много тестов Selenium для подтверждения, регрессии и т.д.

Мы взяли полезный код из ответа Стивена и реорганизовали его в повторно используемый класс, похожий на другие сообщения Impersonate, которые просто не работали для нас, потому что мы хотели, чтобы тесты работали как локально в разработке, так и развернуты как часть процесса выпуска Visual Studio Team System.

Метод uri не работал локально, и ни один из них не выдавал себя за использование методов Win32.

Это работало так, вот оно.

Пример теста с использованием кода Стивена, реорганизованного в помощник

[TestMethod]
public void ThisApp_WhenAccessedByUnathorizedUser_ShouldDisallowAccess()
{
    string userName = "ThisAppNoAccess";
    string password = "123456";
    string domainName = Environment.MachineName;
    using (new Perkins.Impersonator(userName, domainName, password))
    {
        // - Use Remote Web Driver to hook up the browser driver instance launched manually.
        using (var driver = new RemoteWebDriver(new Uri("http://localhost:9515"), DesiredCapabilities.Chrome()))
        {
            var desiredUri = Helper.Combine(Helper.BaseURL, "/ThisApp/#/appGrid");
            TestContext.WriteLine("desiredUri: {0}", desiredUri);
            driver.Navigate().GoToUrl(desiredUri);
            Helper.WaitForAngular(driver);
            var noPermissionNotificationElement = driver.FindElementByXPath("//div[@ng-show='!vm.authorized']/div/div/div/p");
            var showsNoPermissionNotification = noPermissionNotificationElement.Text.Contains("You do not have permissions to view ThisApp.");
            Assert.AreEqual(true, showsNoPermissionNotification, "The text `You do not have permissions to view ThisApp.` is not being displayed!");
        }
    }
}

Вспомогательный класс

// Idea from http://stackoverflow.com/a/34406336/16008
// - Launch the browser driver manually with other user credentials in background
public class Perkins
{
    public class Impersonator : IDisposable
    {
        Process _driverProcess = null;
        string _driverPath = @"chromedriver.exe";
        /// <summary>
        /// Impersonates the specified user account by launching the selenium server under that account.  Connect to it via RemoteWebDriver and localhost on port 9515.
        /// </summary>
        /// <remarks>
        /// We may later want to enhance this by allowing for different ports, etc.
        /// </remarks>
        /// <param name="userName">Name of the user</param>
        /// <param name="domainName">Name of the domain or computer if using a local account.</param>
        /// <param name="password">The password</param>
        public Impersonator(string userName, string domainName, string password)
        {
            ProcessStartInfo processStartInfo = new ProcessStartInfo(_driverPath);
            processStartInfo.UserName = userName;
            System.Security.SecureString securePassword = new System.Security.SecureString();
            foreach (char c in password)
            {
                securePassword.AppendChar(c);
            }
            processStartInfo.Password = securePassword;
            processStartInfo.Domain = domainName; // this is important, mcollins was getting a 'stub received bad data' without it, even though rglos was not
            processStartInfo.UseShellExecute = false;
            processStartInfo.LoadUserProfile = true; // this seemed to be key, without this, I get Internal Server Error 500
            Thread startThread = new Thread(() =>
            {
                _driverProcess = Process.Start(processStartInfo);
                _driverProcess.WaitForExit();
            })
            { IsBackground = true };
            startThread.Start();
        }
        public void Dispose()
        {
            // - Remember to close/exit/terminate the driver process and browser instance when you are done.
            if (_driverProcess != null)
            {
                // Free managed resources
                if (!_driverProcess.HasExited)
                {
                    _driverProcess.CloseMainWindow();
                    _driverProcess.WaitForExit(5000);
                    // Kill the process if the process still alive after the wait
                    if (!_driverProcess.HasExited)
                    {
                        _driverProcess.Kill();
                    }
                    _driverProcess.Close();
                }
                _driverProcess.Dispose();
                _driverProcess = null;
            }
        }
    }
}

Возможно, это поможет кому-то еще с той же проблемой.

Ответ 3

Этот похожий вопрос ссылается на эту статью поддержки Microsoft. По сути вам нужно

System.Security.Principal.WindowsImpersonationContext impersonationContext;
impersonationContext = 
((System.Security.Principal.WindowsIdentity)User.Identity).Impersonate();
IWebDriver webDriver = new InternetExplorerDriver();
// do your stuff here.
impersonationContext.Undo();

В статье поддержки содержится дополнительный код о выдаче себя за конкретного пользователя.

Ответ 4

Есть ли у вас несколько старых компьютеров? Или емкость для некоторых виртуальных машин?

Если это так, создайте настройку Selenium Grid и настройте ее для автоматического входа в систему как желаемого пользователя домена, а другой - для пользователя, не являющегося доменом.
http://code.google.com/p/selenium/wiki/Grid2

Ответ 5

У меня была такая же проблема, когда я делал проект автоматизации для веб-приложения, требующего проверки подлинности Windows. Тем не менее, я достиг этого с помощью firefox, следуя инструкциям по его достижению.

НАСТРОЙКА FIREFOX

  • ОТКРЫТЬ ДИАЛОГ ИЗ ВАШЕЙ СИСТЕМЫ И ТИП 'firefox.exe -p' (ЗАКРЫВАЙТЕ ПРОСМОТР ПРОТИВ FIREFOX ПЕРЕД НАЧАЛОМ ЭТОЙ КОМАНДЫ) http://www.wikihow.com/Create-a-Firefox-Profile
  • НАЖМИТЕ НА СОЗДАНИЕ ПРОФИЛЯ И ДАЙТЕ ИМЯ КАК REQUURIED
  • ВЫБЕРИТЕ СОЗДАННЫЙ ПРОФИЛЬ И СТАРТ БРАУЗЕР И ОТКРЫТЫЙ МЕНЕДЖЕР ДОБАВЛЕНИЙ (ИНСТРУМЕНТЫ - ДОБАВКИ)
  • ПОИСК "АвтоАвто" и УСТАНОВИТЕ ЭТО. ЭТО БУДЕТ ЗАПРЕЩАЕТСЯ ОТКРЫТЬ, ДЕЛАТЬ ЭТО.
  • ОСТАВАЙТЕСЬ, ЧТО ПОКРЫВАЕТСЯ FIREFOX, ТО ОТКРЫТЫЙ URL ЭТО ПРОСИТ ВАС ДЛЯ АУТЕНТИФИКАЦИИ
  • ВВОД ПОЛЬЗОВАТЕЛЯ И ПАРОЛЯ - ПРЕДОСТАВЛЯЙТЕ ЭТО, FIREFOX ПРОСИТ ВАС ПОМНИТЬ ПАРОЛЬ
  • НАЖМИТЕ ПОМНИТЬ, И ЭТО СОХРАНИТЬ ПАРОЛЬ В ПРОФИЛЕ FIREFOX
  • КОПИРОВАТЬ СОЗДАННЫЙ ПРОФИЛЬ FIREFOX И СОХРАНИТЬ, ЧТО ТРЕБУЕТСЯ ПАПКА
  • В ВАШЕМ СЕЛЕНИИ SCRIPT ВЫБИРАЙТЕ ВЫШЕ СОЗДАННОГО ПРОФИЛЯ С ВОДИТЕЛЕЙ FIREFOX И ПРОХОДИТЕ ОДНОМ URL, ЭТО НЕ СПРОСИТЕ ДЛЯ ДИАЛОГА АУТЕНТИФИКАЦИИ

Это очень успешно работает в моем проекте.

Ответ 7

Таким образом, проблема, с которой этот вопрос пытается обходить, связана с NTLM Auto Login. См. Автозапуск Google Chrome и NTLM с использованием проверки подлинности Windows

Решения, приведенные выше, не работали для меня, так как автоматический вход успешно прошел аутентификацию с любым пользователем в моей системе, поэтому неважно, какой пользователь я использовал для олицетворения.

Однако я заметил, что вы можете перехитрить автозапуск, заменив localhost на любое другое доменное имя, например локальный IP-адрес. Не требуется олицетворения:)