Всем привет.
Пытаюсь запустить тесты параллельно с использованием ThreadLocal.
Подскажите, в ThreadLocal нужно ложить драйвер или экземпляр класса,в котором есть драйвер?
Как правильно делать driver.Quit() в таком случае? Натыкаюсь на “No active session with ID” exception.
И вообще, есть ли другие способы распаралелить UI тесты без использования ThreadLocal?
Через ThreadLocal вполне нормальный вариант.
Вот простой пример.
Делаем поле.
private static readonly ThreadLocal<IWebDriver> DriverThreadLocal = new ThreadLocal<IWebDriver>(true);
Где-нибудь пишем строку создания драйвера (либо сделать сеттер в свойстве):
DriverThreadLocal.Value = new ChromeDriver(cds, chromeOptions, TimeSpan.FromSeconds(ParametersCmd.DriverCmdTimeout));
Для удобного обращения к драйверу в тестах делаем свойство
public static IWebDriver Driver
{
get
{
if (!DriverThreadLocal.IsValueCreated)
throw new ArgumentNullException(
"Драйвер не проинициализирован.");
return DriverThreadLocal.Value;
}
}
А убивать такой драйвер можно либо через свойство, либо напрямую.
Например через свойство выше:
Driver?.Quit();
Driver?.Dispose();
Cделал все так, как описано выше.
Тесты запускаются параллельно, но есть нюанс с Driver.Quit().
Не получается закрыть сессию. И самое интересное то, что эту штука воспроизводится только при Run режиме. В Debug режиме все хорошо.
Т.е при вызове метода Driver.Quit(), драйвер уже null по понятно почему.
Код можно посмотреть тут: https://github.com/Roman1137/Tests_For_TestInfrastructure_Course/tree/experiment
Скачал ваш проект, выбрал DirectConnection, запустил все тесты - всё прошло без ошибок.
Как воспроизвести?
В общем, посмотрел внимательнее.
Проблема в том, что вы вызываете килл браузера в [OneTimeTearDown]
.
Браузер надо уничтожать после каждого теста, в [TearDown]
.
Так же как и создавать браузер надо для теста в [Setup]
, а не в [OneTimeSetUp]
В вашей же реализации, поток один раз создаёт Application, и в дальнейшем уже не вызывает для каждого теста метод инициализации драйвера, ибо он вшит в конструктор.
Спасибо большое!
Дело в том, что я хочу распаралелить тесты по фикстурам: чтобы на один потом запускалась одна сессия и в этой сессии ранились тесты а одного класса.
Насколько это имеет смысл? Или лучше создавать драйвер для каждого теста, т.е для каждого теста будет отдельная сессия и отдельно будет подниматься браузер?
Бесспорно, это изолирует тесты друг от друга, но требует больше времени на выполнение (запуск и закрытие браузера)
Одна сессия - один тест.
Это избавит вас от лишних проблем.
Самая банальная из которых - сессия внезапно сдохнет и все ваши недозапущенные тесты так и не запустятся.
Эта зависимость тестов друг от друга которая не несет никакого смысла.
К тому же, при использовании Selenoid, время подъема браузера в контейнере настолько маленькое, что этим можно пренебречь.
Значит при использовании NUnit нужно использовать Parallelizable(ParallelScope.All) вместо Parallelizable(ParallelScope.Fixtures)?
Хотя, нет. У меня получилось запустить тесты с ParallelScope.Fixtures.
Думаю, что разница между ParallelScope.All и ParallelScope.Fixtures состоит в том, что с использованием ParallelScope.Fixtures - мы будем иметь ровно столько потоков, сколько фикстур у нас есть. В каждом потоке будут запускаться по очереди тесты из фикстур. Если мы используем ParallelScope.All - то будет выделяться поток на каждый тест.
Ограничить количество поток можно с в обеих случаях помощью [assembly:LevelOfParallelism(3)].
Думаю, что если запускать для каждого теста свой браузер, то ParallelScope.Fixtures не имеет смысла. Лучше использовать ParallelScope.All - так все потоки будут равномерно нагружены(например может быть ситуция, когда в одной фикстуре 1 тест, а в другой 50 и если использовать ParallelScope.Fixtures - то поток с 1 тестом будет простаивать)
Итого, тесты получилось запустить параллельно двумя способами:
-
С использованием ThreadLocal.
private static ThreadLocal<IWebDriver> DriverThreadlocal = new ThreadLocal<IWebDriver>(); public static IWebDriver Driver { get { if (!DriverThreadlocal.IsValueCreated) { throw new ArgumentException("Driver is not initialized!"); } return DriverThreadlocal.Value; } }
2)А так же, мне посоветовали использовать потокобезопасный словарь.
private static ConcurrentDictionary<string, IWebDriver> DriverCollection = new ConcurrentDictionary<string, IWebDriver>();
public static IWebDriver Driver
{
get
{
return DriverCollection.First(pair => pair.Key == TestContext.CurrentContext.Test.ID).Value;
}
set => DriverCollection.TryAdd(TestContext.CurrentContext.Test.ID, value);
}
Работающие тесты можно найти по ссылкам
- Вариант со Threadlocal - GitHub - Roman1137/Tests_For_TestInfrastructure_Course at parallel_experiment_threadlocal,
- Вариант с ConcurrentDictionary -
GitHub - Roman1137/Tests_For_TestInfrastructure_Course