From 6184b350e36dac3178df3e37262238743c30fa40 Mon Sep 17 00:00:00 2001 From: taranovakn Date: Mon, 27 Nov 2023 20:57:22 +0300 Subject: [PATCH 1/5] Add result of course --- .gitignore | 2 ++ sshkeyVS22 | 7 +++++++ sshkeyVS22.pub | 1 + 3 files changed, 10 insertions(+) create mode 100644 sshkeyVS22 create mode 100644 sshkeyVS22.pub diff --git a/.gitignore b/.gitignore index a314ea2..2ba2a89 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ bin Backup obj *.DotSettings +/VacationTests/.vs/VacationTests +/.vs/test-automation-student/v17 diff --git a/sshkeyVS22 b/sshkeyVS22 new file mode 100644 index 0000000..299abe6 --- /dev/null +++ b/sshkeyVS22 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBqwMJ6lNMfP/rQPcIohYxyABrmbee6wcwsNT3dkCuTmAAAAKAJwOdSCcDn +UgAAAAtzc2gtZWQyNTUxOQAAACBqwMJ6lNMfP/rQPcIohYxyABrmbee6wcwsNT3dkCuTmA +AAAECqZsEzRKgVgtPTqdcgz3cYxDI2XJskYcsWh2h04N/4nmrAwnqU0x8/+tA9wiiFjHIA +GuZt57rBzCw1Pd2QK5OYAAAAGHRhcmFub3ZhLmtuQHNrYmtvbnR1ci5ydQECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/sshkeyVS22.pub b/sshkeyVS22.pub new file mode 100644 index 0000000..9f0312d --- /dev/null +++ b/sshkeyVS22.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGrAwnqU0x8/+tA9wiiFjHIAGuZt57rBzCw1Pd2QK5OY taranova.kn@skbkontur.ru From d602a0fa30e1caa145b2898ff38f998eed610c83 Mon Sep 17 00:00:00 2001 From: taranovakn Date: Mon, 27 Nov 2023 20:59:01 +0300 Subject: [PATCH 2/5] Add course result --- VacationTests/DiExample/Container.cs | 56 ++++- .../DiExample/Examples/Ulern_Test.cs | 42 ++-- VacationTests/DiExample/SetUpContainer.cs | 21 ++ VacationTests/DiExample/SmokyTests.cs | 6 +- VacationTests/VacationTests/Claims/Claim.cs | 72 ++++--- .../VacationTests/Claims/ClaimBuilder.cs | 57 ++++- .../VacationTests/Claims/ClaimTests.cs | 47 +++-- .../VacationTests/Claims/Director.cs | 20 +- .../VacationTests/Claims/DirectorBuilder.cs | 28 +++ VacationTests/VacationTests/Data/Directors.cs | 10 +- VacationTests/VacationTests/Helpers/Helper.cs | 31 +++ .../Infrastructure/FirefoxDriverFactory.cs | 21 ++ .../Infrastructure/InjectControlsAttribute.cs | 8 + .../Infrastructure/LocalStorage.cs | 15 +- .../Infrastructure/MyBrowserPool.cs | 43 ++++ .../PageElements/ControlFactory.cs | 34 +-- .../PageElements/AdminClaimItem.cs | 39 ++++ .../PageElements/AdminClaimList.cs | 30 +++ .../PageElements/AverageSalaryRow.cs | 29 +++ .../PageElements/ClaimLightboxFooter.cs | 1 + .../PageElements/DirectorFioCombobox.cs | 1 + .../PageElements/DirectorItem.cs | 1 + .../PageElements/EmployeeClaimItem.cs | 1 + .../VacationTests/PageElements/PageFooter.cs | 7 +- .../PageNavigation/Navigation.cs | 5 + .../VacationTests/PageNavigation/Urls.cs | 2 + .../PageObjects/AdminVacationListPage.cs | 6 + .../AverageDailyEarningsCalculatorPage.cs | 36 ++++ .../PageObjects/ClaimCreationPage.cs | 1 + .../PageObjects/ClaimLightbox.cs | 1 + .../PageObjects/EmployeeVacationListPage.cs | 14 +- .../VacationTests/PageObjects/InfoSidePage.cs | 1 + .../VacationTests/PageObjects/LoginPage.cs | 47 +++-- .../Tests/AdministratorPage/AdminListTests.cs | 177 ++++++++++++++++ .../Tests/ControlTests/ButtonTests.cs | 2 +- .../AverageDailyEarningsCalculatorTests.cs | 78 ++++--- .../EmployeeVacationsListTests.cs | 101 +++++++++ .../EmployeeVacationsListUiTests.cs | 199 ++++++++++++++++++ .../Tests/Navigation/NavidationTests.cs | 18 ++ .../Navigation/NavigationExercise1Tests.cs | 4 +- .../Tests/Storage/LocalStorageTests.cs | 6 +- .../VacationTests/VacationTestBase.cs | 56 ++++- 42 files changed, 1197 insertions(+), 177 deletions(-) create mode 100644 VacationTests/DiExample/SetUpContainer.cs create mode 100644 VacationTests/VacationTests/Helpers/Helper.cs create mode 100644 VacationTests/VacationTests/Infrastructure/FirefoxDriverFactory.cs create mode 100644 VacationTests/VacationTests/Infrastructure/InjectControlsAttribute.cs create mode 100644 VacationTests/VacationTests/Infrastructure/MyBrowserPool.cs create mode 100644 VacationTests/VacationTests/PageElements/AdminClaimItem.cs create mode 100644 VacationTests/VacationTests/PageElements/AdminClaimList.cs create mode 100644 VacationTests/VacationTests/PageElements/AverageSalaryRow.cs create mode 100644 VacationTests/VacationTests/PageObjects/AverageDailyEarningsCalculatorPage.cs create mode 100644 VacationTests/VacationTests/Tests/AdministratorPage/AdminListTests.cs create mode 100644 VacationTests/VacationTests/Tests/EmployeePage/EmployeeVacationsListTests.cs create mode 100644 VacationTests/VacationTests/Tests/EmployeePage/EmployeeVacationsListUiTests.cs create mode 100644 VacationTests/VacationTests/Tests/Navigation/NavidationTests.cs diff --git a/VacationTests/DiExample/Container.cs b/VacationTests/DiExample/Container.cs index 1451381..7456e0f 100644 --- a/VacationTests/DiExample/Container.cs +++ b/VacationTests/DiExample/Container.cs @@ -1,12 +1,60 @@ -using System; +using DiExample.Selenium.Page; +using DiExample.Selenium; +using Microsoft.Extensions.DependencyInjection; +using OpenQA.Selenium.Chrome; +using OpenQA.Selenium; +using System; +using NUnit.Framework; +using System.Collections.Concurrent; +using OpenQA.Selenium.Firefox; namespace DiExample { - public class Container + public static class Container { - public IServiceProvider BuildServiceProvider() + static Container() { - throw new NotImplementedException(); + ServiceProvider = new ServiceCollection() + .AddScoped() + .AddScoped() + .AddScoped() + .BuildServiceProvider(); + } + + private static readonly IServiceProvider ServiceProvider; + + // Потокобезопасный словарь для хранения открытых скоупов + // в качестве ключа может выступать id потока TestContext.CurrentContext.WorkerId + private static ConcurrentDictionary scopeMap { get; } = new(); + private static string scopeKey => TestContext.CurrentContext.WorkerId ?? "debug"; + + // Берем инстанс объекта из скоупа для текущего потока (теста) + public static T GetRequiredService() where T : notnull + { + var scope = scopeMap.GetOrAdd(scopeKey, _ => ServiceProvider.CreateScope()); + return scope.ServiceProvider.GetRequiredService(); + } + + // Метод для очистки скоупа. После теста мы очищаем данные, и возвращаем браузер в пул + public static void ScopeDispose() + { + if (!scopeMap.TryRemove(scopeKey, out var scope)) + { + throw new Exception("Не смогли удалить скоуп из scopeMap"); + } + + scope.Dispose(); + } + + public static IServiceProvider BuildServiceProvider() + { + return ServiceProvider; + } + + [TearDown] + public static void Cleanup() + { + ScopeDispose(); } } } \ No newline at end of file diff --git a/VacationTests/DiExample/Examples/Ulern_Test.cs b/VacationTests/DiExample/Examples/Ulern_Test.cs index ff93cff..46aae88 100644 --- a/VacationTests/DiExample/Examples/Ulern_Test.cs +++ b/VacationTests/DiExample/Examples/Ulern_Test.cs @@ -11,7 +11,7 @@ public void DiTest_AddScoped_And_Singleton_UseCommonToken() { // должен использоваться общий токен var container = new ServiceCollection() - .AddScoped() + .AddSingleton() .AddScoped() .AddScoped() .BuildServiceProvider(); @@ -47,16 +47,16 @@ public void DiTest_AllAddScoped_UseRandomToken() Assert.Multiple(() => - { - Assert.AreEqual(instance_1_1, instance_2_1, - "в разных СКОУПАХ для 1 сервиса должны быть разные токены, т.к. токен добавлен через AddScoped"); - Assert.AreEqual(instance_2_1, instance_2_2, - "для разных сервисах внутри общего скоупа должен быть общий токен, т.к. токен добавлен через AddScoped"); - Assert.AreEqual(instance_1_1, instance_2_2, - "для разных сервисах внутри разных скоупов должны быть разные токены, т.к. токен добавлен через AddScoped"); - Assert.AreEqual(instanceDoubleToken.Token1, instanceDoubleToken.Token2, - "Для сервиса принимаеющего на вход 2 токена должен использоваться 1 общий из скоупа"); - } + { + Assert.AreNotEqual(instance_1_1.TokenInfo, instance_2_1.TokenInfo, + "в разных СКОУПАХ для 1 сервиса должны быть разные токены, т.к. токен добавлен через AddScoped"); + Assert.AreEqual(instance_2_1.TokenInfo, instance_2_2.TokenInfo, + "для разных сервисах внутри общего скоупа должен быть общий токен, т.к. токен добавлен через AddScoped"); + Assert.AreNotEqual(instance_1_1.TokenInfo, instance_2_2.TokenInfo, + "для разных сервисах внутри разных скоупов должны быть разные токены, т.к. токен добавлен через AddScoped"); + Assert.AreEqual(instanceDoubleToken.Token1, instanceDoubleToken.Token2, + "Для сервиса принимаеющего на вход 2 токена должен использоваться 1 общий из скоупа"); + } ); } @@ -80,16 +80,16 @@ public void DiTest_AddTransientToken() var instanceDoubleToken = sp2.GetRequiredService(); Assert.Multiple(() => - { - Assert.AreEqual(instance_1_1, instance_2_1, - "в разных СКОУПАХ для 1 сервиса должны быть разные токены, т.к. токен добавлен через AddTransient"); - Assert.AreEqual(instance_2_1, instance_2_2, - "для разных сервисах внутри общего скоупа должены быть разные токены, т.к. токен добавлен через AddTransient"); - Assert.AreEqual(instance_1_1, instance_2_2, - "для разных сервисах внутри разных скоупов должны быть разные токены, т.к. токен добавлен через AddTransient"); - Assert.AreEqual(instanceDoubleToken.Token1, instanceDoubleToken.Token2, - "Для сервиса принимаеющего на вход 2 токена должены сгенерироваться 2 разных токена"); - } + { + Assert.AreNotEqual(instance_1_1.TokenInfo, instance_2_1.TokenInfo, + "в разных СКОУПАХ для 1 сервиса должны быть разные токены, т.к. токен добавлен через AddTransient"); + Assert.AreNotEqual(instance_2_1.TokenInfo, instance_2_2.TokenInfo, + "для разных сервисах внутри общего скоупа должены быть разные токены, т.к. токен добавлен через AddTransient"); + Assert.AreNotEqual(instance_1_1.TokenInfo, instance_2_2.TokenInfo, + "для разных сервисах внутри разных скоупов должны быть разные токены, т.к. токен добавлен через AddTransient"); + Assert.AreNotEqual(instanceDoubleToken.Token1, instanceDoubleToken.Token2, + "Для сервиса принимаеющего на вход 2 токена должены сгенерироваться 2 разных токена"); + } ); } } \ No newline at end of file diff --git a/VacationTests/DiExample/SetUpContainer.cs b/VacationTests/DiExample/SetUpContainer.cs new file mode 100644 index 0000000..c641288 --- /dev/null +++ b/VacationTests/DiExample/SetUpContainer.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DiExample +{ + [SetUpFixture] + public class SetUpContainer + { + //public static readonly IServiceProvider ContainerCache + // = new Container().BuildServiceProvider(); + + [OneTimeTearDown] + public async Task TearDown() + => await (Container.GetRequiredService() as ServiceProvider)!.DisposeAsync(); + } +} diff --git a/VacationTests/DiExample/SmokyTests.cs b/VacationTests/DiExample/SmokyTests.cs index 3e369fb..d39c562 100644 --- a/VacationTests/DiExample/SmokyTests.cs +++ b/VacationTests/DiExample/SmokyTests.cs @@ -8,9 +8,9 @@ namespace DiExample { public class SmokyTests { - private readonly IServiceProvider _serviceProvider = new Container().BuildServiceProvider(); - private IBrowser Browser => _serviceProvider.GetRequiredService(); - + //private readonly IServiceProvider _serviceProvider = new Container().BuildServiceProvider(); + private IBrowser Browser => Container.GetRequiredService(); + [TestCase("Контур"), TestCase("экосистема"), TestCase("бизнеса")] public void BrowserShould_BeOpenAndReturn_KonturPage(string substring) { diff --git a/VacationTests/VacationTests/Claims/Claim.cs b/VacationTests/VacationTests/Claims/Claim.cs index 61cd1e0..aa9d732 100644 --- a/VacationTests/VacationTests/Claims/Claim.cs +++ b/VacationTests/VacationTests/Claims/Claim.cs @@ -1,41 +1,53 @@ using System; +using VacationTests.Data; using Newtonsoft.Json; using Newtonsoft.Json.Converters; namespace VacationTests.Claims { - // Об enum https://ulearn.me/course/basicprogramming/Konstanty_i_enum_y_f1740706-b8e2-4bd4-ab87-3cc710a52449 - - public class Claim + public record Claim( + // перечисляем какие свойства будут у класса Claim + string Id, + [property: JsonConverter(typeof(StringEnumConverter))] + ClaimType Type, + ClaimStatus Status, + Director Director, + DateTime StartDate, + DateTime EndDate, + int? ChildAgeInMonths, + string UserId, + bool PaidNow + ) { - // Конструктор класса - public Claim(string id, ClaimType type, ClaimStatus status, Director director, DateTime startDate, - DateTime endDate, int? childAgeInMonths, string userId, bool paidNow) + // добавляем статический метод для создания экземпляра класса со значениями по умолчанию + public static Claim CreateDefault() + { + var random = new Random(); + var randomClaimId = random.Next(1, 101).ToString(); + + return new Claim( + randomClaimId, + ClaimType.Paid, + ClaimStatus.NonHandled, + Directors.Default, + DateTime.Now.Date.AddDays(7), + DateTime.Now.Date.AddDays(12), + null, + "1", + false + ); + } + + // можно также добавить второй метод для создания типичного заявления по уходу за ребёнком + public static Claim CreateChildType() { - Id = id; - Type = type; - Status = status; - Director = director; - StartDate = startDate; - EndDate = endDate; - ChildAgeInMonths = childAgeInMonths; - UserId = userId; - PaidNow = paidNow; + var random = new Random(); + var childAgeInMonths = random.Next(1, 101); + return CreateDefault() with + { + Type = ClaimType.Child, + ChildAgeInMonths = childAgeInMonths + }; } - - // Свойства класса - public string Id { get; } - - [property: JsonConverter(typeof(StringEnumConverter))] - public ClaimType Type { get; } - - public ClaimStatus Status { get; } - - public Director Director { get; } - public DateTime StartDate { get; } - public DateTime EndDate { get; } - public int? ChildAgeInMonths { get; } - public string UserId { get; } - public bool PaidNow { get; } } } \ No newline at end of file diff --git a/VacationTests/VacationTests/Claims/ClaimBuilder.cs b/VacationTests/VacationTests/Claims/ClaimBuilder.cs index d15b449..ba08ccf 100644 --- a/VacationTests/VacationTests/Claims/ClaimBuilder.cs +++ b/VacationTests/VacationTests/Claims/ClaimBuilder.cs @@ -1,4 +1,6 @@ using System; +using VacationTests.Claims; +using VacationTests.Data; namespace VacationTests.Claims { @@ -13,10 +15,55 @@ public class ClaimBuilder private ClaimStatus status = ClaimStatus.NonHandled; private string userId = DefaultUserId; private int? childAgeInMonths; + private Director director = Directors.Default; + private DateTime startDate = DateTime.Now.Date.AddDays(7); + private DateTime endDate = DateTime.Now.Date.AddDays(12); + private bool paidNow; + // Для каждого поля создаем метод With<название свойства>, возвращающий экземпляр этого DirectorBuilder // Метод принимает значение и записывает в соответствующее приватное поле // С помощью таких методов можно будет задать необходимые поляr + + public ClaimBuilder WithStartDate(DateTime newStartDate) + { + startDate = newStartDate.Date; + return this; + } + + public ClaimBuilder WithEndDate(DateTime newEndDate) + { + endDate = newEndDate.Date; + return this; + } + + public ClaimBuilder WithPeriod(DateTime newStartDate, DateTime newEndDate) + { + if (newStartDate > newEndDate) + { + throw new Exception("Дата начала отпуска должна быть раньше даты конца отпуска"); + } + if ((newEndDate - newStartDate).TotalDays < 3) + { + throw new Exception("Минимальный период отпуска должен быть 3 дня"); + } + startDate = newStartDate.Date; + endDate = newEndDate.Date; + return this; + } + + public ClaimBuilder WithPaidNow(bool newPaidNow) + { + paidNow = newPaidNow; + return this; + } + + public ClaimBuilder WithDirector(Director newDirector) + { + director = newDirector; + return this; + } + public ClaimBuilder WithId(string newId) { id = newId; @@ -58,12 +105,12 @@ public ClaimBuilder WithChildAgeInMonths(int newChildAgeInMonths) id, type, status, - new Director(14, "Бублик Владимир Кузьмич", "Директор департамента"), - DateTime.Now.Date.AddDays(7), - DateTime.Now.Date.AddDays(12), + director, + startDate, + endDate, childAgeInMonths, userId, - false - ); + paidNow + ); } } \ No newline at end of file diff --git a/VacationTests/VacationTests/Claims/ClaimTests.cs b/VacationTests/VacationTests/Claims/ClaimTests.cs index ec54e8f..bbe0460 100644 --- a/VacationTests/VacationTests/Claims/ClaimTests.cs +++ b/VacationTests/VacationTests/Claims/ClaimTests.cs @@ -10,12 +10,13 @@ public class ClaimTests [Test] public void Serialize() { - var claim = new Claim("1", ClaimType.Child, ClaimStatus.NonHandled, - new Director(24939, "Захаров Максим Николаевич", "Руководитель направления тестирования"), - new DateTime(2021, 08, 1), new DateTime(2021, 08, 5), - 1, "1", true); + //var claim = new Claim("1", ClaimType.Child, ClaimStatus.NonHandled, + // new Director(24939, "Захаров Максим Николаевич", "Руководитель направления тестирования"), + // new DateTime(2021, 08, 1), new DateTime(2021, 08, 5), + // 1, "1", true); + // todo для курсанта: после создания рекорда (Задание 6) заиспользовать код ниже вместо создания класса напрямую - /*var claim = Claim.CreateDefault() with + var claim = Claim.CreateDefault() with { Id = "1", Status = ClaimStatus.NonHandled, @@ -31,7 +32,7 @@ public void Serialize() Name = "Захаров Максим Николаевич", Position = "Руководитель направления тестирования" } - };*/ + }; var serialized = ClaimStorage.Serialize(claim); var expected = "{\"id\":\"1\",\"type\":\"По уходу за ребенком\",\"status\":2,\"director\":{\"id\":24939,\"name\":\"Захаров Максим Николаевич\",\"position\":\"Руководитель направления тестирования\"},\"startDate\":\"01.08.2021\",\"endDate\":\"05.08.2021\",\"childAgeInMonths\":1,\"userId\":\"1\",\"paidNow\":true}"; @@ -43,12 +44,12 @@ public void Deserialize() { const string localStorageData = "{\"id\":\"1\",\"type\":\"По уходу за ребенком\",\"status\":2,\"director\":{\"id\":24939,\"name\":\"Захаров Максим Николаевич\",\"position\":\"Руководитель направления тестирования\"},\"startDate\":\"01.08.2021\",\"endDate\":\"05.08.2021\",\"childAgeInMonths\":1,\"userId\":\"1\",\"paidNow\":true}"; - var expectClaim = new Claim("1", ClaimType.Child, ClaimStatus.NonHandled, - new Director(24939, "Захаров Максим Николаевич", "Руководитель направления тестирования"), - new DateTime(2021, 08, 1), new DateTime(2021, 08, 5), - 1, "1", true); + //var expectClaim = new Claim("1", ClaimType.Child, ClaimStatus.NonHandled, + // new Director(24939, "Захаров Максим Николаевич", "Руководитель направления тестирования"), + // new DateTime(2021, 08, 1), new DateTime(2021, 08, 5), + // 1, "1", true); // todo для курсанта: после создания рекорда (Задание 6) заиспользовать код ниже вместо создания класса напрямую - /*var expectClaim = Claim.CreateDefault() with + var expectClaim = Claim.CreateDefault() with { Id = "1", Status = ClaimStatus.NonHandled, @@ -64,7 +65,7 @@ public void Deserialize() Name = "Захаров Максим Николаевич", Position = "Руководитель направления тестирования" } - };*/ + }; var deserialized = ClaimStorage.Deserialize(localStorageData); deserialized.Should().BeEquivalentTo(expectClaim); } @@ -75,18 +76,18 @@ public void DeserializeArray() const string localStorageArray = "[{\"endDate\":\"29.03.2022\",\"id\":\"1\",\"paidNow\":false,\"startDate\":\"22.03.2022\",\"status\":1,\"type\":\"Основной\",\"userId\":\"3\",\"director\":{\"id\":24939,\"name\":\"Захаров Максим Николаевич\",\"position\":\"Руководитель направления тестирования\"}},{\"endDate\":\"09.04.2022\",\"id\":\"2\",\"paidNow\":false,\"startDate\":\"29.03.2022\",\"status\":2,\"type\":\"Основной\",\"userId\":\"1\",\"director\":{\"id\":24939,\"name\":\"Захаров Максим Николаевич\",\"position\":\"Руководитель направления тестирования\"}}]"; - var claim1 = new Claim("1", ClaimType.Paid, ClaimStatus.Rejected, - new Director(24939, "Захаров Максим Николаевич", "Руководитель направления тестирования"), - new DateTime(2022, 03, 22), new DateTime(2022, 03, 29), - null, "3", false); - var claim2 = new Claim("2", ClaimType.Paid, ClaimStatus.NonHandled, - new Director(24939, "Захаров Максим Николаевич", "Руководитель направления тестирования"), - new DateTime(2022, 03, 29), new DateTime(2022, 04, 09), - null, "1", false); - var expectClaim = new[] {claim1, claim2}; + //var claim1 = new claim("1", claimtype.paid, claimstatus.rejected, + // new director(24939, "захаров максим николаевич", "руководитель направления тестирования"), + // new datetime(2022, 03, 22), new datetime(2022, 03, 29), + // null, "3", false); + //var claim2 = new claim("2", claimtype.paid, claimstatus.nonhandled, + // new director(24939, "захаров максим николаевич", "руководитель направления тестирования"), + // new datetime(2022, 03, 29), new datetime(2022, 04, 09), + // null, "1", false); + //var expectclaim = new[] { claim1, claim2 }; // После решения Задания 6 заиспользовать код ниже вместо создания класса напрямую - /*var expectClaim = new[] + var expectClaim = new[] { Claim.CreateDefault() with { @@ -120,7 +121,7 @@ public void DeserializeArray() UserId = "1", PaidNow = false } - };*/ + }; var deserialized = ClaimStorage.Deserialize(localStorageArray); deserialized.Should().BeEquivalentTo(expectClaim, options => options.WithoutStrictOrdering()); } diff --git a/VacationTests/VacationTests/Claims/Director.cs b/VacationTests/VacationTests/Claims/Director.cs index edcdbfc..9c4f0c4 100644 --- a/VacationTests/VacationTests/Claims/Director.cs +++ b/VacationTests/VacationTests/Claims/Director.cs @@ -1,16 +1,14 @@ namespace VacationTests.Claims { - public class Director + public record Director( + // Director + int Id, + string Name, + string Position) { - public Director(int id, string name, string position) - { - Id = id; - Name = name; - Position = position; - } - - public int Id { get; } - public string Name { get; } - public string Position { get; } + public static Director CreateDefault() => + new Director(14, " ", " "); + public static Director CreateSuperDirector() => + new Director(24320, " ", " "); } } \ No newline at end of file diff --git a/VacationTests/VacationTests/Claims/DirectorBuilder.cs b/VacationTests/VacationTests/Claims/DirectorBuilder.cs index e140f8f..8205003 100644 --- a/VacationTests/VacationTests/Claims/DirectorBuilder.cs +++ b/VacationTests/VacationTests/Claims/DirectorBuilder.cs @@ -2,6 +2,34 @@ namespace VacationTests.Claims { public class DirectorBuilder { + // Director + private int id = 14; + private string name = " "; + private string position = " "; + public DirectorBuilder WithId(int newId) + { + id = newId; + return this; + } + + public DirectorBuilder WithName(string newName) + { + name = newName; + return this; + } + + public DirectorBuilder WithPosition(string newPosition) + { + position = newPosition; + return this; + } + + // , Director + public Director Build() => new Director( + id, + name, + position + ); } } \ No newline at end of file diff --git a/VacationTests/VacationTests/Data/Directors.cs b/VacationTests/VacationTests/Data/Directors.cs index 0662eee..4d16f65 100644 --- a/VacationTests/VacationTests/Data/Directors.cs +++ b/VacationTests/VacationTests/Data/Directors.cs @@ -2,10 +2,16 @@ namespace VacationTests.Data { + //public static class Directors + //{ + // public static Director Default => new Director(14, "Бублик Владимир Кузьмич", "Директор департамента"); + // public static Director SuperDirector => new Director(24320, "Кирпичников Алексей Николаевич", "Руководитель управления"); + // public static DirectorBuilder New => new DirectorBuilder(); + //} public static class Directors { - public static Director Default => new Director(14, "Бублик Владимир Кузьмич", "Директор департамента"); - public static Director SuperDirector => new Director(24320, "Кирпичников Алексей Николаевич", "Руководитель управления"); + public static Director Default => new DirectorBuilder().WithId(14).WithName("Бублик Владимир Кузьмич").WithPosition("Директор департамента").Build(); + public static Director SuperDirector => new DirectorBuilder().WithId(24320).WithName("Кирпичников Алексей Николаевич").WithPosition("Руководитель управления").Build(); public static DirectorBuilder New => new DirectorBuilder(); } } \ No newline at end of file diff --git a/VacationTests/VacationTests/Helpers/Helper.cs b/VacationTests/VacationTests/Helpers/Helper.cs new file mode 100644 index 0000000..5ce97b5 --- /dev/null +++ b/VacationTests/VacationTests/Helpers/Helper.cs @@ -0,0 +1,31 @@ +using VacationTests.Claims; +using VacationTests.Infrastructure; +using VacationTests.Infrastructure.PageElements; +using VacationTests.PageObjects; + +namespace VacationTests.Helpers +{ + public class Helper: VacationTestBase + { + public void CreateClaimFromUI( + EmployeeVacationListPage page, Claim claim) + { + var claimCreationPage = page.CreateButton.ClickAndOpen(); + claimCreationPage.ClaimTypeSelect.SelectValueByText(claim.Type.GetDescription()); + + if (claim.ChildAgeInMonths != null) + { + var age = claim.ChildAgeInMonths / 12; + claimCreationPage.ChildAgeInput.ClearAndInputText(age.ToString()); + } + + claimCreationPage.ClaimStartDatePicker.SetValue(claim.StartDate.ToString("dd.MM.yyyy")); + claimCreationPage.ClaimEndDatePicker.SetValue(claim.EndDate.ToString("dd.MM.yyyy")); + claimCreationPage.DirectorFioCombobox.SelectValue(claim.Director.Name); + claimCreationPage.DirectorFioCombobox.WaitValue(claim.Director.Name); + + page = claimCreationPage.SendButton.ClickAndOpen(); + page.WaitLoaded(); + } + } +} diff --git a/VacationTests/VacationTests/Infrastructure/FirefoxDriverFactory.cs b/VacationTests/VacationTests/Infrastructure/FirefoxDriverFactory.cs new file mode 100644 index 0000000..b537106 --- /dev/null +++ b/VacationTests/VacationTests/Infrastructure/FirefoxDriverFactory.cs @@ -0,0 +1,21 @@ +using System; +using Kontur.Selone.Extensions; +using Kontur.Selone.WebDrivers; +using OpenQA.Selenium; +using OpenQA.Selenium.Firefox; + +namespace VacationTests.Infrastructure +{ + // Об интерфейсах https://ulearn.me/course/basicprogramming/Interfeysy_3df89dfb-7f0f-4123-82ac-364c3a426396 + public class FirefoxDriverFactory : IWebDriverFactory + { + public IWebDriver Create() + { + var firefoxService = FirefoxDriverService.CreateDefaultService(AppDomain.CurrentDomain.BaseDirectory); + var options = new FirefoxOptions(); + options.AddArguments("--start-maximized", "--disable-extensions", "--width=1280", "--height=960"); + var firefoxDriver = new FirefoxDriver(firefoxService, options, TimeSpan.FromSeconds(180)); + return firefoxDriver; + } + } +} \ No newline at end of file diff --git a/VacationTests/VacationTests/Infrastructure/InjectControlsAttribute.cs b/VacationTests/VacationTests/Infrastructure/InjectControlsAttribute.cs new file mode 100644 index 0000000..dd33a65 --- /dev/null +++ b/VacationTests/VacationTests/Infrastructure/InjectControlsAttribute.cs @@ -0,0 +1,8 @@ +using System; + +namespace VacationTests.Infrastructure +{ + public class InjectControlsAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/VacationTests/VacationTests/Infrastructure/LocalStorage.cs b/VacationTests/VacationTests/Infrastructure/LocalStorage.cs index e919153..097e59c 100644 --- a/VacationTests/VacationTests/Infrastructure/LocalStorage.cs +++ b/VacationTests/VacationTests/Infrastructure/LocalStorage.cs @@ -13,32 +13,33 @@ public LocalStorage(IWebDriver webDriver) } // Получение количества элементов в хранилище - public long Length => (long) 42; // todo для курсанта: написать код + public long Length => (long)webDriver.JavaScriptExecutor() + .ExecuteScript($"return localStorage.length;"); // Очистка всего хранилища public void Clear() { - // todo для курсанта: написать код + webDriver.JavaScriptExecutor().ExecuteScript($"localStorage.clear();"); } // Получение данных по ключу keyName public string GetItem(string keyName) { - // todo для курсанта: написать код - return null; + var item = webDriver.JavaScriptExecutor().ExecuteScript($"return localStorage.getItem(\"{keyName}\");"); + return item?.ToString(); } // Получение ключа на заданной позиции public string Key(int keyNumber) { - // todo для курсанта: написать код - return null; + var key = webDriver.JavaScriptExecutor().ExecuteScript($"return localStorage.key({keyNumber});"); + return key?.ToString(); } // Удаление данных с ключом keyName public void RemoveItem(string keyName) { - // todo для курсанта: написать код + webDriver.JavaScriptExecutor().ExecuteScript($"localStorage.removeItem(\"{keyName}\");"); } // Сохранение пары ключ/значение diff --git a/VacationTests/VacationTests/Infrastructure/MyBrowserPool.cs b/VacationTests/VacationTests/Infrastructure/MyBrowserPool.cs new file mode 100644 index 0000000..692cea1 --- /dev/null +++ b/VacationTests/VacationTests/Infrastructure/MyBrowserPool.cs @@ -0,0 +1,43 @@ +using Kontur.Selone.Extensions; +using Kontur.Selone.WebDrivers; +using NUnit.Framework; +using OpenQA.Selenium; +using System; +using System.Collections.Concurrent; + +namespace VacationTests.Infrastructure +{ + public static class MyBrowserPool + { + private static ConcurrentDictionary webDriversMap; + private static IWebDriverPool pool; + private static string key => TestContext.CurrentContext.Test.ID; + + static MyBrowserPool() + { + webDriversMap = new ConcurrentDictionary(); + var cleaner = new DelegateWebDriverCleaner(x => x.ResetWindows()); + pool = new WebDriverPool(new FirefoxDriverFactory(), cleaner); + + } + + public static IWebDriver Get() + { + return webDriversMap.GetOrAdd(key, _ => pool.Acquire()); + } + + public static void Dispose() + { + pool.Clear(); + } + + public static void Release() + { + if (webDriversMap.TryRemove(key, out var browser)) + { + pool.Release(browser); + } + else throw new ArgumentException(); + } + } +} \ No newline at end of file diff --git a/VacationTests/VacationTests/Infrastructure/PageElements/ControlFactory.cs b/VacationTests/VacationTests/Infrastructure/PageElements/ControlFactory.cs index c7d27bd..453b9c0 100644 --- a/VacationTests/VacationTests/Infrastructure/PageElements/ControlFactory.cs +++ b/VacationTests/VacationTests/Infrastructure/PageElements/ControlFactory.cs @@ -13,7 +13,7 @@ namespace VacationTests.Infrastructure.PageElements public class ControlFactory { private readonly object[] dependencies; - + ISearchContext searchContext = null; public ControlFactory(params object[] dependencies) { this.dependencies = dependencies; @@ -23,14 +23,14 @@ public ControlFactory(params object[] dependencies) /// Должен содержать конструктор, принимающий IWebDriver public TPageElement CreateControl(IContextBy contextBy) { - return (TPageElement) CreateInstance(typeof(TPageElement), contextBy, dependencies.Prepend(this).ToArray()); + return (TPageElement)CreateInstance(typeof(TPageElement), contextBy, dependencies.Prepend(this).ToArray()); } /// Создать страницу типа TPageObject public TPageObject CreatePage(IWebDriver webDriver) { var allDependencies = dependencies.Prepend(this).Prepend(webDriver).ToArray(); - return (TPageObject) CreateInstance(typeof(TPageObject), null, allDependencies); + return (TPageObject)CreateInstance(typeof(TPageObject), null, allDependencies); } /// Создать коллекцию контролов типа TItem @@ -44,6 +44,8 @@ public ElementsCollection CreateElementsCollection(ISearchContext private static object CreateInstance(Type controlType, IContextBy contextBy, object[] dependencies) { + // Проверяем, есть ли атрибут InjectControlsAttribute на классе + var injectAttribute = controlType.GetCustomAttribute(); // У объекта, который хотим создать, проверяем, что конструктор есть и он один var constructors = controlType.GetConstructors(); if (constructors.Length != 1) @@ -65,17 +67,18 @@ private static object CreateInstance(Type controlType, IContextBy contextBy, obj // Вызываем конструктор и передаём ему все входные параметры var value = constructor.Invoke(args.ToArray()); - - // Получаем контекст, по которому будем искать все контролы, входящие в состав нашего объекта - var searchContext = contextBy?.SearchContext.SearchElement(contextBy.By) ?? - dependencies.OfType().SingleOrDefault(); - if (searchContext == null) - throw new NotSupportedException( - "Для автоматической инициализации полей контрола должен быть известен ISearchContext. " + - "Либо укажите IContextBy, либо передайте в зависимости WebDriver."); - // Инициализируем контролы объекта - InitializePropertiesWithControls(value, searchContext, dependencies); - + if (injectAttribute != null) + { + // Получаем контекст, по которому будем искать все контролы, входящие в состав нашего объекта + var searchContext = contextBy?.SearchContext.SearchElement(contextBy.By) ?? + dependencies.OfType().SingleOrDefault(); + if (searchContext == null) + throw new NotSupportedException( + "Для автоматической инициализации полей контрола должен быть известен ISearchContext. " + + "Либо укажите IContextBy, либо передайте в зависимости WebDriver."); + // Инициализируем контролы объекта + InitializePropertiesWithControls(value, searchContext, dependencies); + } // Возвращаем экземпляр объекта return value; } @@ -92,7 +95,7 @@ private static void InitializePropertiesWithControls(object control, ISearchCont { // проверяем, что доступен метод set; if (prop.SetMethod is null) continue; - + // находим атрибут BaseSearchByAttribute или его наследника ByTidAttribute var attribute = prop.GetCustomAttribute(true); // если атрибут не найден, то берём название самого свойства, @@ -100,7 +103,6 @@ private static void InitializePropertiesWithControls(object control, ISearchCont var contextBy = attribute == null ? searchContext.Search(x => x.WithTid(prop.Name)) : searchContext.Search(attribute.SearchCriteria); - // создаём экземпляр свойства через CreateInstance, // чтобы иницаилизировать у сложных контролов ещё и их свойства var value = CreateInstance(prop.PropertyType, contextBy, dependencies); diff --git a/VacationTests/VacationTests/PageElements/AdminClaimItem.cs b/VacationTests/VacationTests/PageElements/AdminClaimItem.cs new file mode 100644 index 0000000..0d4436a --- /dev/null +++ b/VacationTests/VacationTests/PageElements/AdminClaimItem.cs @@ -0,0 +1,39 @@ +using Kontur.Selone.Extensions; +using Kontur.Selone.Selectors.Context; +using VacationTests.Infrastructure; +using VacationTests.Infrastructure.PageElements; + +namespace VacationTests.PageElements +{ + [InjectControls] + // Класс элемента списка отпусков AdminClaimList наследуем от ControlBase, + // поскольку это тоже контрол и могут понадобиться базовые методы и пропсы + public class AdminClaimItem : ControlBase + { + public AdminClaimItem(IContextBy contextBy, ControlFactory controlFactory) : base(contextBy) + { + TitleLink = controlFactory.CreateControl(Container.Search(x => x.WithTid("TitleLink"))); + UserFioLabel = controlFactory.CreateControl