From 2b8bc3114c0c7b320910eefdf30441c3649a638f Mon Sep 17 00:00:00 2001 From: Pavel Vostretsov Date: Sun, 14 Jan 2024 21:07:27 +0500 Subject: [PATCH] playwright playground --- DbViewer.Tests/DbViewer.Tests.csproj | 1 + .../FrontTests/AutoFill/PwAutoFill.cs | 60 +++++++++ .../BusinessObjectsChangeValuesTest.cs | 97 +++++++------- .../FrontTests/BusinessObjectsDeleteTest.cs | 80 ++++++----- .../FrontTests/BusinessObjectsSearchTests.cs | 91 +++++++------ .../FrontTests/Controls/Accordion.cs | 31 +++++ .../Controls/AccordionFieldValue.cs | 33 +++++ .../FrontTests/Controls/AccordionRow.cs | 25 ++++ .../FrontTests/Controls/AccordionToggle.cs | 16 +++ .../Controls/BusinessObjectFilter.cs | 28 ++++ .../Controls/BusinessObjectGroup.cs | 21 +++ .../FrontTests/Controls/BusinessObjectItem.cs | 16 +++ .../Controls/BusinessObjectTableRow.cs | 24 ++++ .../Controls/ConfirmDeleteObjectModal.cs | 17 +++ .../FrontTests/Controls/FilterModal.cs | 30 +++++ .../Pages/PwBusinessObjectDetailsPage.cs | 25 ++++ .../Pages/PwBusinessObjectTablePage.cs | 51 +++++++ .../FrontTests/Pages/PwBusinessObjectsPage.cs | 32 +++++ .../Playwright/AssertionExtensions.cs | 125 ++++++++++++++++++ .../FrontTests/Playwright/Browser.cs | 113 ++++++++++++++++ .../FrontTests/Playwright/PwButton.cs | 12 ++ .../FrontTests/Playwright/PwCheckbox.cs | 12 ++ .../FrontTests/Playwright/PwControlBase.cs | 17 +++ .../FrontTests/Playwright/PwControlList.cs | 48 +++++++ .../FrontTests/Playwright/PwDatePicker.cs | 12 ++ .../FrontTests/Playwright/PwInput.cs | 12 ++ .../FrontTests/Playwright/PwLabel.cs | 12 ++ .../FrontTests/Playwright/PwLink.cs | 12 ++ .../FrontTests/Playwright/PwPageBase.cs | 17 +++ .../FrontTests/Playwright/PwSelect.cs | 12 ++ DbViewer.Tests/FrontTests/PlaywrightSetup.cs | 66 +++++++++ global.json | 2 +- 32 files changed, 1025 insertions(+), 125 deletions(-) create mode 100644 DbViewer.Tests/FrontTests/AutoFill/PwAutoFill.cs create mode 100644 DbViewer.Tests/FrontTests/Controls/Accordion.cs create mode 100644 DbViewer.Tests/FrontTests/Controls/AccordionFieldValue.cs create mode 100644 DbViewer.Tests/FrontTests/Controls/AccordionRow.cs create mode 100644 DbViewer.Tests/FrontTests/Controls/AccordionToggle.cs create mode 100644 DbViewer.Tests/FrontTests/Controls/BusinessObjectFilter.cs create mode 100644 DbViewer.Tests/FrontTests/Controls/BusinessObjectGroup.cs create mode 100644 DbViewer.Tests/FrontTests/Controls/BusinessObjectItem.cs create mode 100644 DbViewer.Tests/FrontTests/Controls/BusinessObjectTableRow.cs create mode 100644 DbViewer.Tests/FrontTests/Controls/ConfirmDeleteObjectModal.cs create mode 100644 DbViewer.Tests/FrontTests/Controls/FilterModal.cs create mode 100644 DbViewer.Tests/FrontTests/Pages/PwBusinessObjectDetailsPage.cs create mode 100644 DbViewer.Tests/FrontTests/Pages/PwBusinessObjectTablePage.cs create mode 100644 DbViewer.Tests/FrontTests/Pages/PwBusinessObjectsPage.cs create mode 100644 DbViewer.Tests/FrontTests/Playwright/AssertionExtensions.cs create mode 100644 DbViewer.Tests/FrontTests/Playwright/Browser.cs create mode 100644 DbViewer.Tests/FrontTests/Playwright/PwButton.cs create mode 100644 DbViewer.Tests/FrontTests/Playwright/PwCheckbox.cs create mode 100644 DbViewer.Tests/FrontTests/Playwright/PwControlBase.cs create mode 100644 DbViewer.Tests/FrontTests/Playwright/PwControlList.cs create mode 100644 DbViewer.Tests/FrontTests/Playwright/PwDatePicker.cs create mode 100644 DbViewer.Tests/FrontTests/Playwright/PwInput.cs create mode 100644 DbViewer.Tests/FrontTests/Playwright/PwLabel.cs create mode 100644 DbViewer.Tests/FrontTests/Playwright/PwLink.cs create mode 100644 DbViewer.Tests/FrontTests/Playwright/PwPageBase.cs create mode 100644 DbViewer.Tests/FrontTests/Playwright/PwSelect.cs create mode 100644 DbViewer.Tests/FrontTests/PlaywrightSetup.cs diff --git a/DbViewer.Tests/DbViewer.Tests.csproj b/DbViewer.Tests/DbViewer.Tests.csproj index bc0b37ad..7025c767 100644 --- a/DbViewer.Tests/DbViewer.Tests.csproj +++ b/DbViewer.Tests/DbViewer.Tests.csproj @@ -10,6 +10,7 @@ + diff --git a/DbViewer.Tests/FrontTests/AutoFill/PwAutoFill.cs b/DbViewer.Tests/FrontTests/AutoFill/PwAutoFill.cs new file mode 100644 index 00000000..643b9add --- /dev/null +++ b/DbViewer.Tests/FrontTests/AutoFill/PwAutoFill.cs @@ -0,0 +1,60 @@ +using System; +using System.Linq; +using System.Reflection; + +using Microsoft.Playwright; + +using SkbKontur.DbViewer.Tests.FrontTests.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.AutoFill +{ + public class PwAutoFill + { + public static TPage InitializePage(IPage page) + where TPage : PwPageBase + { + var newPage = (TPage)Activator.CreateInstance(typeof(TPage), page)!; + InitializeControls(newPage, newPage.Page, null); + return newPage; + } + + public static void InitializeControls(object instance, IPage page, ILocator? parent) + { + var properties = instance + .GetType() + .GetProperties(BindingFlags.Instance | BindingFlags.Public) + .Where(p => p.CanWrite && typeof(PwControlBase).IsAssignableFrom(p.PropertyType) && !p.GetCustomAttributes().Any()); + + foreach (var property in properties) + { + var locator = LocatorForProperty(property, page, parent); + var value = Activator.CreateInstance(property.PropertyType, locator)!; + InitializeControls(value, page, locator); + property.SetValue(instance, value); + } + } + + public static ILocator LocatorForProperty(PropertyInfo property, IPage page, ILocator? parent) + { + var selector = property + .GetCustomAttributes() + .Select(x => x.Selector.ToString()) + .FirstOrDefault(); + + if (string.IsNullOrEmpty(selector)) + return GetByTestId(page, parent, property.Name); + + var selectors = selector.Split(" ", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + return selectors + .Aggregate( + parent, + (current, s) => s.StartsWith("##") + ? GetByTestId(page, current, s[2..]) + : GetLocator(page, current, s) + )!; + } + + private static ILocator GetByTestId(IPage page, ILocator? parent, string tid) => parent == null ? page.GetByTestId(tid) : parent.GetByTestId(tid); + private static ILocator GetLocator(IPage page, ILocator? parent, string loc) => parent == null ? page.Locator(loc) : parent.Locator(loc); + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/BusinessObjectsChangeValuesTest.cs b/DbViewer.Tests/FrontTests/BusinessObjectsChangeValuesTest.cs index 0d28ab74..0fbd20e9 100644 --- a/DbViewer.Tests/FrontTests/BusinessObjectsChangeValuesTest.cs +++ b/DbViewer.Tests/FrontTests/BusinessObjectsChangeValuesTest.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Threading.Tasks; using Cassandra.Data.Linq; @@ -16,6 +17,7 @@ using SkbKontur.DbViewer.TestApi.Impl.Document; using SkbKontur.DbViewer.Tests.FrontTests.Helpers; using SkbKontur.DbViewer.Tests.FrontTests.Pages; +using SkbKontur.DbViewer.Tests.FrontTests.Playwright; namespace SkbKontur.DbViewer.Tests.FrontTests { @@ -33,38 +35,42 @@ public BusinessObjectsChangeValuesTest(string documentName) /// Проверяем, что заданные значения сохраняются /// [Test] - public void TestChangeOrganizationName() + public async Task TestChangeOrganizationName() { var documentId = CreateDocument(documentName); - using var browser = new BrowserForTests(); - var businessObjectEditingPage = browser.LoginAsSuperUser().SwitchTo(documentName, $"Id={documentId}"); + await using var browser = new Browser(); - var filledNumberRow = businessObjectEditingPage.RootAccordion.FindField("DocumentNumber"); - filledNumberRow.FieldValue.WaitText("123"); - filledNumberRow.Edit.Click(); - filledNumberRow.FieldValue.Input.ClearAndInputText("2qwe123QWE2"); - filledNumberRow.Save.Click(); - filledNumberRow.FieldValue.WaitText("2qwe123QWE2"); + var adminBrowser = await browser.LoginAsSuperUser(); + var businessObjectEditingPage = await adminBrowser.SwitchTo(documentName, $"Id={documentId}"); - browser.WebDriver.Navigate().Refresh(); + var filledNumberRow = businessObjectEditingPage.RootAccordion.FindField("DocumentNumber"); + await filledNumberRow.FieldValue.WaitText("123"); + await filledNumberRow.Edit.Click(); + await filledNumberRow.FieldValue.Input.ClearAndInputText("2qwe123QWE2"); + await filledNumberRow.Key.Click(); + await Task.Delay(1000); + await filledNumberRow.Save.Click(); + await filledNumberRow.FieldValue.WaitText("2qwe123QWE2"); + + await businessObjectEditingPage.Page.ReloadAsync(); filledNumberRow = businessObjectEditingPage.RootAccordion.FindField("DocumentNumber"); - filledNumberRow.FieldValue.WaitText("2qwe123QWE2"); + await filledNumberRow.FieldValue.WaitText("2qwe123QWE2"); GetDocument(documentName, documentId).DocumentNumber.Should().Be("2qwe123QWE2"); - businessObjectEditingPage.RootAccordion.FindAccordionToggle("DocumentContent").ToggleButton.Click(); + await businessObjectEditingPage.RootAccordion.FindAccordionToggle("DocumentContent").ToggleButton.Click(); var unfilledNumberRow = businessObjectEditingPage.RootAccordion.FindField("DocumentContent_OrdersNumber"); - unfilledNumberRow.FieldValue.WaitText("null"); - unfilledNumberRow.Edit.Click(); - unfilledNumberRow.FieldValue.Input.ClearAndInputText("123"); - unfilledNumberRow.Save.Click(); - unfilledNumberRow.FieldValue.WaitText("123"); - - browser.WebDriver.Navigate().Refresh(); - businessObjectEditingPage.RootAccordion.FindAccordionToggle("DocumentContent").ToggleButton.Click(); + await unfilledNumberRow.FieldValue.WaitText("null"); + await unfilledNumberRow.Edit.Click(); + await unfilledNumberRow.FieldValue.Input.ClearAndInputText("123"); + await unfilledNumberRow.Save.Click(); + await unfilledNumberRow.FieldValue.WaitText("123"); + + await businessObjectEditingPage.Page.ReloadAsync(); + await businessObjectEditingPage.RootAccordion.FindAccordionToggle("DocumentContent").ToggleButton.Click(); unfilledNumberRow = businessObjectEditingPage.RootAccordion.FindField("DocumentContent_OrdersNumber"); - unfilledNumberRow.FieldValue.WaitText("123"); + await unfilledNumberRow.FieldValue.WaitText("123"); GetDocument(documentName, documentId).DocumentContent.OrdersNumber.Should().Be("123"); } @@ -74,46 +80,47 @@ public void TestChangeOrganizationName() /// Проверяем, что заданные значения сохраняются /// [Test] - public void TestChangeDocumentDates() + public async Task TestChangeDocumentDates() { var documentId = CreateDocument(documentName); - using var browser = new BrowserForTests(); - var businessObjectEditingPage = browser.LoginAsSuperUser().SwitchTo(documentName, $"Id={documentId}"); + await using var browser = new Browser(); + var adminBrowser = await browser.LoginAsSuperUser(); + var businessObjectEditingPage = await adminBrowser.SwitchTo(documentName, $"Id={documentId}"); var filledDateRow = businessObjectEditingPage.RootAccordion.FindField("DocumentDate"); - filledDateRow.FieldValue.WaitTextContains("2014-12-11"); - filledDateRow.Edit.Click(); + await filledDateRow.FieldValue.WaitTextContains("2014-12-11"); + await filledDateRow.Edit.Click(); var expectedOffset = documentName == "CqlDocument" ? TimeSpan.Zero : DateTimeOffset.Now.Offset; var expectedOffsetStr = $"+{expectedOffset:hh':'mm}"; - filledDateRow.FieldValue.Date.ClearAndInputText("13.12.2014"); - filledDateRow.FieldValue.Time.ClearAndInputText("10:18:13.567"); - filledDateRow.FieldValue.TimeOffsetLabel.WaitText(expectedOffsetStr); - filledDateRow.Save.Click(); + await filledDateRow.FieldValue.Date.ClearAndInputText("13.12.2014"); + await filledDateRow.FieldValue.Time.ClearAndInputText("10:18:13.567"); + await filledDateRow.FieldValue.TimeOffsetLabel.WaitText(expectedOffsetStr); + await filledDateRow.Save.Click(); var expectedStr = $"2014-12-13T10:18:13.567{expectedOffsetStr}"; - filledDateRow.FieldValue.WaitTextContains(expectedStr); + await filledDateRow.FieldValue.WaitTextContains(expectedStr); - browser.WebDriver.Navigate().Refresh(); + await businessObjectEditingPage.Page.ReloadAsync(); filledDateRow = businessObjectEditingPage.RootAccordion.FindField("DocumentDate"); - filledDateRow.FieldValue.WaitTextContains(expectedStr); + await filledDateRow.FieldValue.WaitTextContains(expectedStr); GetDocument(documentName, documentId).DocumentDate.Should().Be(new DateTimeOffset(2014, 12, 13, 10, 18, 13, 567, expectedOffset)); - businessObjectEditingPage.RootAccordion.FindAccordionToggle("DocumentContent").ToggleButton.Click(); + await businessObjectEditingPage.RootAccordion.FindAccordionToggle("DocumentContent").ToggleButton.Click(); var unfilledDateRow = businessObjectEditingPage.RootAccordion.FindField("DocumentContent_DeliveryDate"); - unfilledDateRow.FieldValue.WaitText("null"); - unfilledDateRow.Edit.Click(); - unfilledDateRow.FieldValue.Date.ClearAndInputText("14.12.2014"); - unfilledDateRow.FieldValue.Time.ClearAndInputText("20:19"); - unfilledDateRow.FieldValue.TimeZoneSelect.SelectValueByText("UTC"); - unfilledDateRow.Save.Click(); - unfilledDateRow.FieldValue.WaitText("2014-12-14T20:19:00Z"); - - browser.WebDriver.Navigate().Refresh(); - businessObjectEditingPage.RootAccordion.FindAccordionToggle("DocumentContent").ToggleButton.Click(); + await unfilledDateRow.FieldValue.WaitText("null"); + await unfilledDateRow.Edit.Click(); + await unfilledDateRow.FieldValue.Date.ClearAndInputText("14.12.2014"); + await unfilledDateRow.FieldValue.Time.ClearAndInputText("20:19"); + await unfilledDateRow.FieldValue.TimeZoneSelect.SelectValueByText("UTC"); + await unfilledDateRow.Save.Click(); + await unfilledDateRow.FieldValue.WaitText("2014-12-14T20:19:00Z"); + + await businessObjectEditingPage.Page.ReloadAsync(); + await businessObjectEditingPage.RootAccordion.FindAccordionToggle("DocumentContent").ToggleButton.Click(); unfilledDateRow = businessObjectEditingPage.RootAccordion.FindField("DocumentContent_DeliveryDate"); - unfilledDateRow.FieldValue.WaitText("2014-12-14T20:19:00Z"); + await unfilledDateRow.FieldValue.WaitText("2014-12-14T20:19:00Z"); GetDocument(documentName, documentId).DocumentContent.DeliveryDate.Should().Be(new DateTime(2014, 12, 14, 20, 19, 00, 00, DateTimeKind.Utc)); } diff --git a/DbViewer.Tests/FrontTests/BusinessObjectsDeleteTest.cs b/DbViewer.Tests/FrontTests/BusinessObjectsDeleteTest.cs index e1fc132a..e4d0316d 100644 --- a/DbViewer.Tests/FrontTests/BusinessObjectsDeleteTest.cs +++ b/DbViewer.Tests/FrontTests/BusinessObjectsDeleteTest.cs @@ -1,12 +1,15 @@ using System; using System.Linq; using System.Threading; +using System.Threading.Tasks; using NUnit.Framework; using SkbKontur.DbViewer.TestApi.EntityFramework; -using SkbKontur.DbViewer.Tests.FrontTests.Helpers; using SkbKontur.DbViewer.Tests.FrontTests.Pages; +using SkbKontur.DbViewer.Tests.FrontTests.Playwright; + +using ConfirmDeleteObjectModal = SkbKontur.DbViewer.Tests.FrontTests.Controls.ConfirmDeleteObjectModal; namespace SkbKontur.DbViewer.Tests.FrontTests { @@ -16,20 +19,21 @@ public class BusinessObjectsDeleteTest /// Проверяем, что кнопка с удалением бизнес объекта на странице с таблицей доступна только с SuperUserAccessLevel.God /// [Test] - public void DeleteViaSearchPageRequiresGodAccess() + public async Task DeleteViaSearchPageRequiresGodAccess() { var ftpUser = CreateFtpUser(); - using var browser = new BrowserForTests(); - var businessObjectPage = browser.SwitchTo("FtpUser"); + await using var browser = new Browser(); + var businessObjectPage = await browser.SwitchTo("FtpUser"); - businessObjectPage.OpenFilter.Click(); - businessObjectPage.FilterModal.GetFilter("Login").Input.ClearAndInputText(ftpUser.Login); - businessObjectPage.FilterModal.Apply.Click(); + await businessObjectPage.OpenFilter.Click(); + var filter = await businessObjectPage.FilterModal.GetFilter("Login"); + await filter.Input.ClearAndInputText(ftpUser.Login); + await businessObjectPage.FilterModal.Apply.Click(); - businessObjectPage = browser.RefreshUntil(businessObjectPage, x => x.BusinessObjectItems.IsPresent.Get()); - businessObjectPage.BusinessObjectItems.WaitCount(1); - businessObjectPage.BusinessObjectItems[0].Delete.IsPresent.Wait().That(Is.False, "Delete link should only be present for gods"); + businessObjectPage = await browser.RefreshUntil(businessObjectPage, x => x.BusinessObjectItems.Locator.IsVisibleAsync()); + await businessObjectPage.BusinessObjectItems.WaitCount(1); + await businessObjectPage.BusinessObjectItems[0].Delete.WaitAbsence(); } /// @@ -39,24 +43,27 @@ public void DeleteViaSearchPageRequiresGodAccess() /// [TestCase(true)] [TestCase(false)] - public void DeleteViaSearchPage(bool confirmDeletion) + public async Task DeleteViaSearchPage(bool confirmDeletion) { var ftpUser = CreateFtpUser(); - using var browser = new BrowserForTests(); - var businessObjectPage = browser.LoginAsSuperUser().SwitchTo("FtpUser"); + await using var browser = new Browser(); + + var adminBrowser = await browser.LoginAsSuperUser(); + var businessObjectPage = await adminBrowser.SwitchTo("FtpUser"); - businessObjectPage.OpenFilter.Click(); - businessObjectPage.FilterModal.GetFilter("Login").Input.ClearAndInputText(ftpUser.Login); - businessObjectPage.FilterModal.Apply.Click(); + await businessObjectPage.OpenFilter.Click(); + var filter = await businessObjectPage.FilterModal.GetFilter("Login"); + await filter.Input.ClearAndInputText(ftpUser.Login); + await businessObjectPage.FilterModal.Apply.Click(); - businessObjectPage = browser.RefreshUntil(businessObjectPage, x => x.BusinessObjectItems.IsPresent.Get()); - businessObjectPage.BusinessObjectItems.WaitCount(1); - businessObjectPage.BusinessObjectItems[0].Delete.Click(); - ConfirmDeletion(businessObjectPage.ConfirmDeleteObjectModal, confirmDeletion); + businessObjectPage = await browser.RefreshUntil(businessObjectPage, x => x.BusinessObjectItems.Locator.IsVisibleAsync()); + await businessObjectPage.BusinessObjectItems.WaitCount(1); + await businessObjectPage.BusinessObjectItems[0].Delete.Click(); + await ConfirmDeletion(businessObjectPage.ConfirmDeleteObjectModal, confirmDeletion); if (confirmDeletion) - businessObjectPage.BusinessObjectItems.WaitAbsence(); + await businessObjectPage.BusinessObjectItems.WaitAbsence(); AssertFtpUserExistence(ftpUser.Id, confirmDeletion); } @@ -65,13 +72,13 @@ public void DeleteViaSearchPage(bool confirmDeletion) /// Проверяем, что кнопка с удалением бизнес объекта на странице с конкретным объектом доступна только с SuperUserAccessLevel.God /// [Test] - public void DeleteViaDetailsPageRequiresGodAccess() + public async Task DeleteViaDetailsPageRequiresGodAccess() { var ftpUser = CreateFtpUser(); - using var browser = new BrowserForTests(); - var detailsPage = browser.SwitchTo("FtpUser", $"Id={ftpUser.Id}"); - detailsPage.Delete.IsPresent.Wait().That(Is.False, "Delete link should only be present for gods"); + await using var browser = new Browser(); + var detailsPage = await browser.SwitchTo("FtpUser", $"Id={ftpUser.Id}"); + await detailsPage.Delete.WaitAbsence(); } /// @@ -81,27 +88,28 @@ public void DeleteViaDetailsPageRequiresGodAccess() /// [TestCase(true)] [TestCase(false)] - public void DeleteViaDetailsPage(bool confirmDeletion) + public async Task DeleteViaDetailsPage(bool confirmDeletion) { var ftpUser = CreateFtpUser(); - using var browser = new BrowserForTests(); - var detailsPage = browser.LoginAsSuperUser().SwitchTo("FtpUser", $"Id={ftpUser.Id}"); - detailsPage.Delete.Click(); - ConfirmDeletion(detailsPage.ConfirmDeleteObjectModal, confirmDeletion); + await using var browser = new Browser(); + + var adminBrowser = await browser.LoginAsSuperUser(); + var detailsPage = await adminBrowser.SwitchTo("FtpUser", $"Id={ftpUser.Id}"); + await detailsPage.Delete.Click(); + await ConfirmDeletion(detailsPage.ConfirmDeleteObjectModal, confirmDeletion); if (confirmDeletion) - detailsPage.GoTo(); + detailsPage.GoTo(); AssertFtpUserExistence(ftpUser.Id, confirmDeletion); } - private static void ConfirmDeletion(ConfirmDeleteObjectModal modal, bool confirmDeletion) + private static Task ConfirmDeletion(ConfirmDeleteObjectModal modal, bool confirmDeletion) { - if (confirmDeletion) - modal.Delete.Click(); - else - modal.Cancel.Click(); + return confirmDeletion + ? modal.Delete.Click() + : modal.Cancel.Click(); } private static void AssertFtpUserExistence(Guid userId, bool deletionConfirmed) diff --git a/DbViewer.Tests/FrontTests/BusinessObjectsSearchTests.cs b/DbViewer.Tests/FrontTests/BusinessObjectsSearchTests.cs index db4f32b9..73bbc0ea 100644 --- a/DbViewer.Tests/FrontTests/BusinessObjectsSearchTests.cs +++ b/DbViewer.Tests/FrontTests/BusinessObjectsSearchTests.cs @@ -1,7 +1,9 @@ -using NUnit.Framework; +using System.Threading.Tasks; + +using NUnit.Framework; -using SkbKontur.DbViewer.Tests.FrontTests.Helpers; using SkbKontur.DbViewer.Tests.FrontTests.Pages; +using SkbKontur.DbViewer.Tests.FrontTests.Playwright; namespace SkbKontur.DbViewer.Tests.FrontTests { @@ -12,46 +14,49 @@ public class BusinessObjectsSearchTests /// Вводим в строке поиска запрос в R# стиле, проверяем ожидаемое количество и название каждого объекта /// [Test] - public void TestSearch() + public async Task TestSearch() { - using var browser = new BrowserForTests(); + await using var browser = new Browser(); + + var businessObjectsPage = await browser.SwitchTo(); + await businessObjectsPage.FilterInput.ClearAndInputText("CI"); + await businessObjectsPage.ObjectGroups.WaitCount(1); + + var pagedObjects = await businessObjectsPage.ObjectGroups.GetItemWithText(x => x.Name, "CQL Paged Objects"); + await pagedObjects.ObjectsList + .Select(x => x.ObjectLink) + .WaitText("CqlOrganizationInfo", "CqlUserInfo"); + + await businessObjectsPage.FilterInput.ClearAndInputText("DocMe"); + await businessObjectsPage.ObjectGroups.WaitCount(2); - var businessObjectsPage = browser.SwitchTo(); - businessObjectsPage.FilterInput.ClearAndInputText("CI"); - businessObjectsPage.ObjectGroups.WaitCount(1); - businessObjectsPage.ObjectGroups - .GetItemWithText(x => x.Name.Text, "CQL Paged Objects").ObjectsList - .Wait(x => x.ObjectLink.Text) - .That(Is.EqualTo(new[] {"CqlOrganizationInfo", "CqlUserInfo"})); + var cqlObjects = await businessObjectsPage.ObjectGroups.GetItemWithText(x => x.Name, "CQL Objects"); + await cqlObjects.ObjectsList + .Select(x => x.ObjectLink) + .WaitText(new[] {"DocumentBindingsMeta"}); - businessObjectsPage.FilterInput.ClearAndInputText("DocMe"); - businessObjectsPage.ObjectGroups.WaitCount(2); - businessObjectsPage.ObjectGroups - .GetItemWithText(x => x.Name.Text, "CQL Objects").ObjectsList - .Wait(x => x.ObjectLink.Text) - .That(Is.EqualTo(new[] {"DocumentBindingsMeta"})); - businessObjectsPage.ObjectGroups - .GetItemWithText(x => x.Name.Text, "CQL Paged Objects").ObjectsList - .Wait(x => x.ObjectLink.Text) - .That(Is.EqualTo(new[] {"CqlDocumentMeta"})); + var cqlPagedObjects = await businessObjectsPage.ObjectGroups.GetItemWithText(x => x.Name, "CQL Paged Objects"); + await cqlPagedObjects.ObjectsList + .Select(x => x.ObjectLink) + .WaitText(new[] {"CqlDocumentMeta"}); } /// /// Проверяем, что у таблицы FtpUser есть плашка indexed /// [Test] - public void TestSearchIndexedField() + public async Task TestSearchIndexedField() { - using var browser = new BrowserForTests(); + await using var browser = new Browser(); - var businessObjects = browser.SwitchTo(); - businessObjects.FilterInput.ClearAndInputText("UsersTable"); - businessObjects.ObjectGroups.WaitCount(1); + var businessObjects = await browser.SwitchTo(); + await businessObjects.FilterInput.ClearAndInputText("UsersTable"); + await businessObjects.ObjectGroups.WaitCount(1); - var objects = businessObjects.ObjectGroups.GetItemWithText(x => x.Name.Text, "Postgres Objects"); - objects.IndexedLabel.WaitPresence(); - objects.ObjectsList.WaitCount(1); - objects.ObjectsList[0].ObjectLink.WaitText("UsersTable"); + var objects = await businessObjects.ObjectGroups.GetItemWithText(x => x.Name, "Postgres Objects"); + await objects.IndexedLabel.WaitPresence(); + await objects.ObjectsList.WaitCount(1); + await objects.ObjectsList[0].ObjectLink.WaitText("UsersTable"); } /// @@ -59,15 +64,15 @@ public void TestSearchIndexedField() /// Проверяем что нам выдает ровно одну ссылку /// [Test] - public void TestSearchLink() + public async Task TestSearchLink() { - using var browser = new BrowserForTests(); + await using var browser = new Browser(); - var businessObjectsPage = browser.SwitchTo(); - businessObjectsPage.FilterInput.ClearAndInputText("DocumentPrintingInfo"); - businessObjectsPage.ObjectGroups.WaitCount(1); - businessObjectsPage.ObjectGroups[0].ObjectsList.WaitCount(1); - businessObjectsPage.FindBusinessObjectLink("CQL Objects", "DocumentPrintingInfo").WaitPresence(); + var businessObjectsPage = await browser.SwitchTo(); + await businessObjectsPage.FilterInput.ClearAndInputText("DocumentPrintingInfo"); + await businessObjectsPage.ObjectGroups.WaitCount(1); + await businessObjectsPage.ObjectGroups[0].ObjectsList.WaitCount(1); + await (await businessObjectsPage.FindBusinessObjectLink("CQL Objects", "DocumentPrintingInfo")).WaitPresence(); } /// @@ -75,14 +80,14 @@ public void TestSearchLink() /// Проверяем, что ссылка ведет туда, куда нам нужно /// [Test] - public void TestLinkShouldReferToShowTablePage() + public async Task TestLinkShouldReferToShowTablePage() { - using var browser = new BrowserForTests(); + await using var browser = new Browser(); - var businessObjectsPage = browser.SwitchTo(); - var link = businessObjectsPage.FindBusinessObjectLink("CQL Objects", "DocumentPrintingInfo"); - var page = link.ClickAndGoTo(); - page.Header.WaitText("DocumentPrintingInfo"); + var businessObjectsPage = await browser.SwitchTo(); + var link = await businessObjectsPage.FindBusinessObjectLink("CQL Objects", "DocumentPrintingInfo"); + var page = await link.ClickAndGoTo(); + await page.Header.WaitText("DocumentPrintingInfo"); } } } \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Controls/Accordion.cs b/DbViewer.Tests/FrontTests/Controls/Accordion.cs new file mode 100644 index 00000000..6a272427 --- /dev/null +++ b/DbViewer.Tests/FrontTests/Controls/Accordion.cs @@ -0,0 +1,31 @@ +using Microsoft.Playwright; + +using SkbKontur.DbViewer.Tests.FrontTests.AutoFill; +using SkbKontur.DbViewer.Tests.FrontTests.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Controls +{ + public class Accordion : PwControlBase + { + public Accordion(ILocator locator) + : base(locator) + { + } + + public AccordionRow FindField(string id) + { + var row = new AccordionRow(Locator.GetByTestId(id)); + PwAutoFill.InitializeControls(row, Locator.Page, row.Locator); + + return row; + } + + public AccordionToggle FindAccordionToggle(string id) + { + var toggle = new AccordionToggle(Locator.GetByTestId(id)); + PwAutoFill.InitializeControls(toggle, Locator.Page, toggle.Locator); + + return toggle; + } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Controls/AccordionFieldValue.cs b/DbViewer.Tests/FrontTests/Controls/AccordionFieldValue.cs new file mode 100644 index 00000000..4fcfa653 --- /dev/null +++ b/DbViewer.Tests/FrontTests/Controls/AccordionFieldValue.cs @@ -0,0 +1,33 @@ +using Microsoft.Playwright; + +using SkbKontur.DbViewer.Tests.FrontTests.AutoFill; +using SkbKontur.DbViewer.Tests.FrontTests.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Controls +{ + public class AccordionFieldValue : PwControlBase + { + public AccordionFieldValue(ILocator locator) + : base(locator) + { + } + + public PwLink GoToLink { get; set; } + + [Selector("Input")] + public PwInput Input { get; set; } + + [Selector("Checkbox")] + public PwCheckbox Checkbox { get; set; } + + public PwSelect EnumSelect { get; set; } + public PwSelect BooleanSelect { get; set; } + + public PwLink DownloadLink { get; set; } + + public PwDatePicker Date { get; set; } + public PwInput Time { get; set; } + public PwSelect TimeZoneSelect { get; set; } + public PwLabel TimeOffsetLabel { get; set; } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Controls/AccordionRow.cs b/DbViewer.Tests/FrontTests/Controls/AccordionRow.cs new file mode 100644 index 00000000..0e38d33d --- /dev/null +++ b/DbViewer.Tests/FrontTests/Controls/AccordionRow.cs @@ -0,0 +1,25 @@ +using Microsoft.Playwright; + +using SkbKontur.DbViewer.Tests.FrontTests.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Controls +{ + public class AccordionRow : PwControlBase + { + public AccordionRow(ILocator locator) + : base(locator) + { + } + + public PwLabel Key { get; set; } + + public PwLink GoToLink { get; set; } + + public AccordionFieldValue FieldValue { get; set; } + public PwLabel Value { get; set; } + + public PwButton Edit { get; set; } + public PwButton Save { get; set; } + public PwButton Cancel { get; set; } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Controls/AccordionToggle.cs b/DbViewer.Tests/FrontTests/Controls/AccordionToggle.cs new file mode 100644 index 00000000..02f94d01 --- /dev/null +++ b/DbViewer.Tests/FrontTests/Controls/AccordionToggle.cs @@ -0,0 +1,16 @@ +using Microsoft.Playwright; + +using SkbKontur.DbViewer.Tests.FrontTests.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Controls +{ + public class AccordionToggle : PwControlBase + { + public AccordionToggle(ILocator locator) + : base(locator) + { + } + + public PwButton ToggleButton { get; set; } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Controls/BusinessObjectFilter.cs b/DbViewer.Tests/FrontTests/Controls/BusinessObjectFilter.cs new file mode 100644 index 00000000..8ade8c77 --- /dev/null +++ b/DbViewer.Tests/FrontTests/Controls/BusinessObjectFilter.cs @@ -0,0 +1,28 @@ +using Microsoft.Playwright; + +using SkbKontur.DbViewer.Tests.FrontTests.Pages; +using SkbKontur.DbViewer.Tests.FrontTests.Playwright; + +using SKBKontur.SeleniumTesting.Controls; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Controls +{ + public class BusinessObjectFilter : PwControlBase + { + public BusinessObjectFilter(ILocator locator) + : base(locator) + { + } + + public PwLabel FormCaption { get; set; } + public Select OperatorSelect { get; set; } + public Select EnumSelect { get; set; } + public Select BooleanSelect { get; set; } + public PwInput Input { get; set; } + public Validation InputValidation { get; set; } + public Validation DateTimeValidation { get; set; } + public DatePicker Date { get; set; } + public PwInput Time { get; set; } + public PwInput DateTimeInTicks { get; set; } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Controls/BusinessObjectGroup.cs b/DbViewer.Tests/FrontTests/Controls/BusinessObjectGroup.cs new file mode 100644 index 00000000..fffd567e --- /dev/null +++ b/DbViewer.Tests/FrontTests/Controls/BusinessObjectGroup.cs @@ -0,0 +1,21 @@ +using Microsoft.Playwright; + +using SkbKontur.DbViewer.Tests.FrontTests.AutoFill; +using SkbKontur.DbViewer.Tests.FrontTests.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Controls +{ + public class BusinessObjectGroup : PwControlBase + { + public BusinessObjectGroup(ILocator locator) + : base(locator) + { + } + + public PwLabel Name { get; set; } + public PwLabel IndexedLabel { get; set; } + + [Selector("##ObjectsList ##ObjectItem")] + public PwControlList ObjectsList { get; set; } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Controls/BusinessObjectItem.cs b/DbViewer.Tests/FrontTests/Controls/BusinessObjectItem.cs new file mode 100644 index 00000000..8f7478dc --- /dev/null +++ b/DbViewer.Tests/FrontTests/Controls/BusinessObjectItem.cs @@ -0,0 +1,16 @@ +using Microsoft.Playwright; + +using SkbKontur.DbViewer.Tests.FrontTests.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Controls +{ + public class BusinessObjectItem : PwControlBase + { + public BusinessObjectItem(ILocator locator) + : base(locator) + { + } + + public PwLink ObjectLink { get; set; } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Controls/BusinessObjectTableRow.cs b/DbViewer.Tests/FrontTests/Controls/BusinessObjectTableRow.cs new file mode 100644 index 00000000..63980be1 --- /dev/null +++ b/DbViewer.Tests/FrontTests/Controls/BusinessObjectTableRow.cs @@ -0,0 +1,24 @@ +using Microsoft.Playwright; + +using SkbKontur.DbViewer.Tests.FrontTests.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Controls +{ + public class BusinessObjectTableRow : PwControlBase + { + public BusinessObjectTableRow(ILocator locator) + : base(locator) + { + } + + public PwLink Details { get; set; } + public PwLink Delete { get; set; } + public PwLabel Id { get; set; } + public PwLabel ScopeId { get; set; } + + public PwLabel FindColumn(string tid) + { + return new PwLabel(Locator.GetByTestId(tid)); + } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Controls/ConfirmDeleteObjectModal.cs b/DbViewer.Tests/FrontTests/Controls/ConfirmDeleteObjectModal.cs new file mode 100644 index 00000000..a22c4af1 --- /dev/null +++ b/DbViewer.Tests/FrontTests/Controls/ConfirmDeleteObjectModal.cs @@ -0,0 +1,17 @@ +using Microsoft.Playwright; + +using SkbKontur.DbViewer.Tests.FrontTests.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Controls +{ + public class ConfirmDeleteObjectModal : PwControlBase + { + public ConfirmDeleteObjectModal(ILocator locator) + : base(locator) + { + } + + public PwButton Delete { get; set; } + public PwButton Cancel { get; set; } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Controls/FilterModal.cs b/DbViewer.Tests/FrontTests/Controls/FilterModal.cs new file mode 100644 index 00000000..39580649 --- /dev/null +++ b/DbViewer.Tests/FrontTests/Controls/FilterModal.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; + +using Microsoft.Playwright; + +using SkbKontur.DbViewer.Tests.FrontTests.AutoFill; +using SkbKontur.DbViewer.Tests.FrontTests.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Controls +{ + public class FilterModal : PwControlBase + { + public FilterModal(ILocator locator) + : base(locator) + { + } + + public Task GetFilter(string name) + { + return ObjectFilters.Where(x => x.Locator.Page.GetByTestId(name)).Single(); + } + + [Selector("##ObjectFilters ##Filter")] + public PwControlList ObjectFilters { get; set; } + + public PwButton Apply { get; set; } + public PwLink Clear { get; set; } + public PwInput Id { get; set; } + public PwInput ScopeId { get; set; } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Pages/PwBusinessObjectDetailsPage.cs b/DbViewer.Tests/FrontTests/Pages/PwBusinessObjectDetailsPage.cs new file mode 100644 index 00000000..6981580f --- /dev/null +++ b/DbViewer.Tests/FrontTests/Pages/PwBusinessObjectDetailsPage.cs @@ -0,0 +1,25 @@ +using Microsoft.Playwright; + +using SkbKontur.DbViewer.Tests.FrontTests.Helpers; +using SkbKontur.DbViewer.Tests.FrontTests.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Pages +{ + [PageRoute("BusinessObjects/{scopeId}/details?{id}")] + public class PwBusinessObjectDetailsPage : PwPageBase + { + public PwBusinessObjectDetailsPage(IPage page) + : base(page) + { + } + + public PwLabel Header { get; set; } + + public PwLink Copy { get; set; } + public PwLink Delete { get; set; } + + public Controls.ConfirmDeleteObjectModal ConfirmDeleteObjectModal { get; set; } + + public Controls.Accordion RootAccordion { get; set; } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Pages/PwBusinessObjectTablePage.cs b/DbViewer.Tests/FrontTests/Pages/PwBusinessObjectTablePage.cs new file mode 100644 index 00000000..b7b9a3a1 --- /dev/null +++ b/DbViewer.Tests/FrontTests/Pages/PwBusinessObjectTablePage.cs @@ -0,0 +1,51 @@ +using Microsoft.Playwright; + +using SkbKontur.DbViewer.Tests.FrontTests.AutoFill; +using SkbKontur.DbViewer.Tests.FrontTests.Helpers; +using SkbKontur.DbViewer.Tests.FrontTests.Playwright; + +using SKBKontur.SeleniumTesting.Controls; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Pages +{ + [PageRoute("BusinessObjects/{id}")] + public class PwBusinessObjectTablePage : PwPageBase + { + public PwBusinessObjectTablePage(IPage page) + : base(page) + { + } + + public PwLabel Header { get; set; } + + public PwLink GoBack { get; set; } + public PwLabel NothingFound { get; set; } + + public PwLabel ItemsCountInfo { get; set; } + + public PwLink OpenFilter { get; set; } + + [Selector("portal=FilterModal")] + public Controls.FilterModal FilterModal { get; set; } + + public DownloadLimitModal DownloadLimitModal { get; set; } + + public CountDropdown CountDropdown { get; set; } + public PwLink ClearFilter { get; set; } + public PwLink FieldSettings { get; set; } + public PwLink DownloadLink { get; set; } + + [Selector("Portal:portal ##ColumnSelector")] + public ColumnSelector ColumnSelector { get; set; } + + public Controls.ConfirmDeleteObjectModal ConfirmDeleteObjectModal { get; set; } + + public BusinessObjectTableHeader TableHeader { get; set; } + + [Selector("##Body")] + [ChildSelector("##Row")] + public PwControlList BusinessObjectItems { get; set; } + + public Paging Paging { get; set; } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Pages/PwBusinessObjectsPage.cs b/DbViewer.Tests/FrontTests/Pages/PwBusinessObjectsPage.cs new file mode 100644 index 00000000..adca1f70 --- /dev/null +++ b/DbViewer.Tests/FrontTests/Pages/PwBusinessObjectsPage.cs @@ -0,0 +1,32 @@ +using System.Threading.Tasks; + +using Microsoft.Playwright; + +using SkbKontur.DbViewer.Tests.FrontTests.AutoFill; +using SkbKontur.DbViewer.Tests.FrontTests.Helpers; +using SkbKontur.DbViewer.Tests.FrontTests.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Pages +{ + [PageRoute("BusinessObjects")] + public class PwBusinessObjectsPage : PwPageBase + { + public PwBusinessObjectsPage(IPage page) + : base(page) + { + } + + public PwLabel Header { get; set; } + public PwLink BackLink { get; set; } + public PwInput FilterInput { get; set; } + + [Selector("##ObjectGroups ##ObjectGroup")] + public PwControlList ObjectGroups { get; set; } + + public async Task FindBusinessObjectLink(string groupName, string objectName) + { + var businessObjectsList = (await ObjectGroups.GetItemWithText(x => x.Name, groupName)).ObjectsList; + return (await businessObjectsList.GetItemWithText(x => x.ObjectLink, objectName)).ObjectLink; + } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Playwright/AssertionExtensions.cs b/DbViewer.Tests/FrontTests/Playwright/AssertionExtensions.cs new file mode 100644 index 00000000..10f5ed44 --- /dev/null +++ b/DbViewer.Tests/FrontTests/Playwright/AssertionExtensions.cs @@ -0,0 +1,125 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.Playwright; + +using NUnit.Framework; + +using SkbKontur.DbViewer.Tests.FrontTests.AutoFill; +using SkbKontur.DbViewer.Tests.FrontTests.Pages; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Playwright +{ + public static class AssertionExtensions + { + public static Task GetItemWithText(this PwControlList list, Func f, string text) + where T : PwControlBase + { + return list.Where(x => f(x).Locator.GetByText(text)).Single(); + } + + public static async Task ClickAndGoTo(this PwControlBase control) + where T : PwPageBase + { + await control.Locator.ClickAsync(); + return PwAutoFill.InitializePage(control.Locator.Page); + } + + public static T GoTo(this PwPageBase page) + where T : PwPageBase + { + return PwAutoFill.InitializePage(page.Page); + } + + public static Task ClearAndInputText(this PwInput input, string value) + { + return input.Locator.FillAsync(value); + } + + public static async Task ClearAndInputText(this PwDatePicker datePicker, string value) + { + // await datePicker.Locator.ClickAsync(); + await datePicker.Locator.GetByTestId("InputLikeText__input").PressSequentiallyAsync(value); + } + + public static Task WaitCount(this PwControlList list, int count) + where T : PwControlBase + { + return list.Expect().ToHaveCountAsync(count); + } + + public static async Task Single(this PwControlList list) + where T : PwControlBase + { + await list.WaitCount(1); + return list[0]; + } + + public static Task WaitPresence(this PwControlBase control) + { + return control.Expect().ToBeVisibleAsync(); + } + + public static Task WaitAbsence(this PwControlBase control) + { + return control.Expect().Not.ToBeVisibleAsync(); + } + + public static Task WaitText(this PwControlBase control, string value) + { + return control.Expect().ToHaveTextAsync(value); + } + + public static Task WaitText(this PwControlBase control, params string[] values) + { + return control.Expect().ToHaveTextAsync(values); + } + + public static Task WaitTextContains(this PwControlBase control, string value) + { + return control.Expect().ToContainTextAsync(value); + } + + public static Task SelectValueByText(this PwSelect select, string value) + { + return select.Locator.SelectOptionAsync(value); + } + + public static Task Click(this PwControlBase control) + { + return control.Locator.ClickAsync(); + } + + public static async Task LoginAsSuperUser(this Browser browser) + { + await browser.SwitchToUri(new Uri("Admin", UriKind.Relative)); + return browser; + } + + public static async Task Refresh(this Browser browser, TPage page) + where TPage : PwPageBase + { + await browser.Page.ReloadAsync(); + return browser.GoTo(); + } + + public static async Task RefreshUntil(this Browser browser, TPage page, Func> conditionFunc, string cause = null, int timeout = 30000, int waitTimeout = 100) + where TPage : PwPageBase + { + var w = Stopwatch.StartNew(); + if (await conditionFunc(page)) + return page; + do + { + page = await browser.Refresh(page); + if (await conditionFunc(page)) + return page; + Thread.Sleep(waitTimeout); + } while (w.ElapsedMilliseconds < timeout); + Assert.Fail(cause ?? $"Не смогли дождаться страницу за {timeout} мс"); + return default; + } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Playwright/Browser.cs b/DbViewer.Tests/FrontTests/Playwright/Browser.cs new file mode 100644 index 00000000..dd186afa --- /dev/null +++ b/DbViewer.Tests/FrontTests/Playwright/Browser.cs @@ -0,0 +1,113 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; + +using Microsoft.Playwright; + +using NUnit.Framework; + +using SkbKontur.DbViewer.Tests.FrontTests.AutoFill; +using SkbKontur.DbViewer.Tests.FrontTests.Helpers; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Playwright +{ + public class Browser : IAsyncDisposable + { + public async Task SwitchToUri(Uri uri) + where TPage : PwPageBase + { + await Page.GotoAsync(MakeAbsolute(uri).ToString()); + return PwAutoFill.InitializePage(Page); + } + + public TPage GoTo() + where TPage : PwPageBase + { + var newPage = PwAutoFill.InitializePage(Page); + return newPage; + } + + public async ValueTask DisposeAsync() + { + if (context != null) + { + await context.Tracing.StopAsync(new TracingStopOptions + { + Path = $"{TestContext.CurrentContext.TestDirectory}/out/{TestContext.CurrentContext.Test.Name}.zip" + }); + } + + if (page != null) + await page.CloseAsync(); + + if (context != null) + { + await context.CloseAsync(); + } + } + + private Uri MakeAbsolute(Uri uri) + { + return uri.IsAbsoluteUri ? uri : new Uri(new Uri(baseUrl), uri); + } + + public IPage Page + { + get + { + if (page != null) + return page; + + context = PlaywrightSetup.Browser.NewContextAsync().GetAwaiter().GetResult(); + context.Tracing.StartAsync(new TracingStartOptions + { + Screenshots = true, + Snapshots = true, + Sources = true, + Name = TestContext.CurrentContext.Test.Name + }).GetAwaiter().GetResult(); + + return page = context.NewPageAsync().GetAwaiter().GetResult(); + } + } + + private const string baseUrl = "http://localhost:5000/"; + + private IPage? page; + private IBrowserContext? context; + } + + public static class AttributeNavigationExtensions + { + public static ILocatorAssertions Expect(this PwControlBase controlBase) + { + return Assertions.Expect(controlBase.Locator); + } + + public static Task SwitchTo(this Browser browser, params string[] templateParameters) + where TPage : PwPageBase + { + return browser.SwitchToUri(new Uri(GetPageRoute(templateParameters), UriKind.Relative)); + } + + private static string GetPageRoute(string[] templateParameters) + { + foreach (var attribute in typeof(TPage).GetCustomAttributes()) + { + var route = attribute.Route; + if (templateParameters.Length == 0 && !route.Contains("{id}") && !route.Contains("{scopeId}")) + return route; + if (templateParameters.Length == 1 && route.Contains("{id}") && !route.Contains("{scopeId}")) + return route.Replace("{id}", templateParameters[0]); + if (templateParameters.Length == 2 && route.Contains("{id}") && route.Contains("{scopeId}")) + return route.Replace("{scopeId}", templateParameters[0]).Replace("{id}", templateParameters[1]); + if (templateParameters.Length == 3 && route.Contains("{scopeId}") && route.Contains("{id}") && route.Contains("{versionId}")) + return route + .Replace("{scopeId}", templateParameters[0]) + .Replace("{id}", templateParameters[1]) + .Replace("{versionId}", templateParameters[2]); + } + throw new InvalidOperationException($"Corresponding PageRoute attribute with {templateParameters.Length} arguments not found for {typeof(TPage).Name}"); + } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Playwright/PwButton.cs b/DbViewer.Tests/FrontTests/Playwright/PwButton.cs new file mode 100644 index 00000000..2796df9a --- /dev/null +++ b/DbViewer.Tests/FrontTests/Playwright/PwButton.cs @@ -0,0 +1,12 @@ +using Microsoft.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Playwright +{ + public class PwButton : PwControlBase + { + public PwButton(ILocator locator) + : base(locator) + { + } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Playwright/PwCheckbox.cs b/DbViewer.Tests/FrontTests/Playwright/PwCheckbox.cs new file mode 100644 index 00000000..a5906e36 --- /dev/null +++ b/DbViewer.Tests/FrontTests/Playwright/PwCheckbox.cs @@ -0,0 +1,12 @@ +using Microsoft.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Playwright +{ + public class PwCheckbox : PwControlBase + { + public PwCheckbox(ILocator locator) + : base(locator) + { + } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Playwright/PwControlBase.cs b/DbViewer.Tests/FrontTests/Playwright/PwControlBase.cs new file mode 100644 index 00000000..9ab31a04 --- /dev/null +++ b/DbViewer.Tests/FrontTests/Playwright/PwControlBase.cs @@ -0,0 +1,17 @@ +using JetBrains.Annotations; + +using Microsoft.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Playwright +{ + [MeansImplicitUse(ImplicitUseTargetFlags.Members | ImplicitUseTargetFlags.WithInheritors)] + public class PwControlBase + { + public PwControlBase(ILocator locator) + { + Locator = locator; + } + + public ILocator Locator { get; } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Playwright/PwControlList.cs b/DbViewer.Tests/FrontTests/Playwright/PwControlList.cs new file mode 100644 index 00000000..59fdea15 --- /dev/null +++ b/DbViewer.Tests/FrontTests/Playwright/PwControlList.cs @@ -0,0 +1,48 @@ +using System; + +using Microsoft.Playwright; + +using SkbKontur.DbViewer.Tests.FrontTests.AutoFill; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Playwright +{ + public class PwControlList : PwControlBase + where TControl : PwControlBase + { + public PwControlList(ILocator locator) + : base(locator) + { + } + + public PwControlList Select(Func selector) + where T : PwControlBase + { + var dummyControl = (TControl)Activator.CreateInstance(typeof(TControl), Locator)!; + PwAutoFill.InitializeControls(dummyControl, Locator.Page, Locator); + + return new PwControlList(selector(dummyControl).Locator); + } + + public PwControlList Where(Func predicate) + { + var dummyControl = (TControl)Activator.CreateInstance(typeof(TControl), Locator)!; + PwAutoFill.InitializeControls(dummyControl, Locator.Page, null); + + return new PwControlList(Locator.Filter(new LocatorFilterOptions + { + Has = predicate(dummyControl) + })); + } + + public TControl this[int index] + { + get + { + var controlLocator = Locator.Nth(index); + var control = (TControl)Activator.CreateInstance(typeof(TControl), controlLocator)!; + PwAutoFill.InitializeControls(control, Locator.Page, controlLocator); + return control; + } + } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Playwright/PwDatePicker.cs b/DbViewer.Tests/FrontTests/Playwright/PwDatePicker.cs new file mode 100644 index 00000000..af82a9fa --- /dev/null +++ b/DbViewer.Tests/FrontTests/Playwright/PwDatePicker.cs @@ -0,0 +1,12 @@ +using Microsoft.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Playwright +{ + public class PwDatePicker : PwControlBase + { + public PwDatePicker(ILocator locator) + : base(locator) + { + } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Playwright/PwInput.cs b/DbViewer.Tests/FrontTests/Playwright/PwInput.cs new file mode 100644 index 00000000..c539da44 --- /dev/null +++ b/DbViewer.Tests/FrontTests/Playwright/PwInput.cs @@ -0,0 +1,12 @@ +using Microsoft.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Playwright +{ + public class PwInput : PwControlBase + { + public PwInput(ILocator locator) + : base(locator) + { + } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Playwright/PwLabel.cs b/DbViewer.Tests/FrontTests/Playwright/PwLabel.cs new file mode 100644 index 00000000..eb0079a5 --- /dev/null +++ b/DbViewer.Tests/FrontTests/Playwright/PwLabel.cs @@ -0,0 +1,12 @@ +using Microsoft.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Playwright +{ + public class PwLabel : PwControlBase + { + public PwLabel(ILocator locator) + : base(locator) + { + } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Playwright/PwLink.cs b/DbViewer.Tests/FrontTests/Playwright/PwLink.cs new file mode 100644 index 00000000..8881b3a0 --- /dev/null +++ b/DbViewer.Tests/FrontTests/Playwright/PwLink.cs @@ -0,0 +1,12 @@ +using Microsoft.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Playwright +{ + public class PwLink : PwControlBase + { + public PwLink(ILocator locator) + : base(locator) + { + } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Playwright/PwPageBase.cs b/DbViewer.Tests/FrontTests/Playwright/PwPageBase.cs new file mode 100644 index 00000000..373b8621 --- /dev/null +++ b/DbViewer.Tests/FrontTests/Playwright/PwPageBase.cs @@ -0,0 +1,17 @@ +using JetBrains.Annotations; + +using Microsoft.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Playwright +{ + [MeansImplicitUse(ImplicitUseTargetFlags.Members | ImplicitUseTargetFlags.WithInheritors)] + public class PwPageBase + { + public PwPageBase(IPage page) + { + Page = page; + } + + public IPage Page { get; } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/Playwright/PwSelect.cs b/DbViewer.Tests/FrontTests/Playwright/PwSelect.cs new file mode 100644 index 00000000..8e457e1f --- /dev/null +++ b/DbViewer.Tests/FrontTests/Playwright/PwSelect.cs @@ -0,0 +1,12 @@ +using Microsoft.Playwright; + +namespace SkbKontur.DbViewer.Tests.FrontTests.Playwright +{ + public class PwSelect : PwControlBase + { + public PwSelect(ILocator locator) + : base(locator) + { + } + } +} \ No newline at end of file diff --git a/DbViewer.Tests/FrontTests/PlaywrightSetup.cs b/DbViewer.Tests/FrontTests/PlaywrightSetup.cs new file mode 100644 index 00000000..b6bdbf00 --- /dev/null +++ b/DbViewer.Tests/FrontTests/PlaywrightSetup.cs @@ -0,0 +1,66 @@ +using System.Threading.Tasks; + +using Microsoft.Playwright; + +using NUnit.Framework; + +namespace SkbKontur.DbViewer.Tests.FrontTests +{ + [SetUpFixture] + public class PlaywrightSetup + { + public static IPlaywright Playwright { get; private set; } + public static IBrowser Browser { get; private set; } + + [OneTimeSetUp] + public async Task SetUp() + { + Playwright = await Microsoft.Playwright.Playwright.CreateAsync(); + Playwright.Selectors.SetTestIdAttribute("data-tid"); +// await Playwright.Selectors.RegisterAsync("portal", new SelectorsRegisterOptions +// { +// Script = @" +// // Must evaluate to a selector engine instance. +// { +// // Returns the first element matching given selector in the root's subtree. +// query(root, selector) { +// console.info(root); +// console.info(selector); +// return root.querySelector(selector); +// }, +// +// // Returns all elements matching given selector in the root's subtree. +// queryAll(root, selector) { +// return Array.from(root.querySelectorAll(selector)); +// } +// }" +// }); + + await Playwright.Selectors.RegisterAsync("portal", new SelectorsRegisterOptions + { + Script = @"{ + query(root, selector) { + const portal = root.querySelector(`[data-tid='${selector}']`); + const id = portal.getAttribute('data-render-container-id'); + return document.querySelector(`[data-rendered-container-id='${id}']`); + }, + + queryAll(root, selector) { + const portal = root.querySelector(`[data-tid='${selector}']`); + const id = portal.getAttribute('data-render-container-id'); + return [document.querySelector(`[data-rendered-container-id='${id}']`)]; + } + }" + }); + + Browser = await Playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions {Headless = false}); + } + + [OneTimeTearDown] + public async Task TearDown() + { + await Browser.DisposeAsync(); + Playwright.Dispose(); + } + } +} \ No newline at end of file diff --git a/global.json b/global.json index 447ef432..501e79a8 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "6.0.400", + "version": "8.0.100", "rollForward": "latestFeature" } } \ No newline at end of file