Skip to content

Commit

Permalink
Merge pull request #131 from MADE-Apps/feature/pageobjectgenerator
Browse files Browse the repository at this point in the history
Implemented basic page object generator
  • Loading branch information
tom-made authored Jun 5, 2022
2 parents 0929448 + 223effa commit 1eb33f6
Show file tree
Hide file tree
Showing 19 changed files with 673 additions and 27 deletions.
13 changes: 7 additions & 6 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
## Fixes #
## Resolves #
<!-- Add the issue ID after the '#' to automatically close the issue once the PR is merged -->

<!-- Please provide a description below of the changes made and how it has been tested -->

## PR checklist

- [ ] Sample tests have been added/updated and pass
- [ ] [Documentation](/docs) has been added/updated for changes
- [ ] Code styling has been met on new source file changes
- [ ] Contains **NO** breaking changes
- [ ] Have Uno Platform samples and Legerity tests been added or updated, run locally, and all pass
- [ ] Have added or updated support for Uno Platform element wrappers been reflected in the Page Object Generator
- [ ] Have code styling rules been run on all new source file changes
- [ ] Have relevant articles in the docs been added or updated for all new source file changes
- [ ] Have major breaking changes been made and are documented

<!-- If a breaking change has been made, please provide a detailed description below of the impact and the migration path -->

## Other information
<!-- Please provide any additional information, links, or screenshots below if applicable -->
<!-- Provide any additional information below that may be relevant to the changes made (e.g. app screenshots, documentation links, or existing PR reference) -->
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ on:
- samples/**
- tests/**
- build/**
- tools/**
- .github/workflows/ci.yml
- global.json
pull_request:
Expand All @@ -21,6 +22,7 @@ on:
- samples/**
- tests/**
- build/**
- tools/**
- .github/workflows/ci.yml
- global.json
workflow_dispatch:
Expand All @@ -29,8 +31,8 @@ jobs:
build:

env:
BUILD_CONFIG: 'Release'
SOLUTION: 'Legerity.Uno.NoSamples.slnf'
BUILD_CONFIG: 'ReleasePackages'
SOLUTION: 'Legerity.Uno.sln'

runs-on: ubuntu-latest

Expand Down
9 changes: 0 additions & 9 deletions Legerity.Uno.NoSamples.slnf

This file was deleted.

179 changes: 172 additions & 7 deletions Legerity.Uno.sln

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ As many developers know, projects like Legerity for Uno Platform are built and m
| ------ | ------ | ------ |
| Packages | [![CI](https://github.com/MADE-Apps/legerity-uno/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/MADE-Apps/legerity-uno/actions/workflows/ci.yml) | [![Nuget](https://img.shields.io/nuget/v/Legerity.Uno.svg)](https://www.nuget.org/packages/Legerity.Uno/) |
| Docs | [![Docs](https://github.com/MADE-Apps/legerity-uno/actions/workflows/docs.yml/badge.svg?branch=main)](https://github.com/MADE-Apps/legerity-uno/actions/workflows/docs.yml) | N/A |
| [Page Object Generator](tools/Legerity.Uno.PageObjectGenerator) | [![CI](https://github.com/MADE-Apps/legerity-uno/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/MADE-Apps/legerity-uno/actions/workflows/ci.yml) | [![Nuget](https://img.shields.io/nuget/v/Legerity.Uno.PageObjectGenerator.svg)](https://www.nuget.org/packages/Legerity.Uno.PageObjectGenerator/) |

## Installation 💾

Expand Down
7 changes: 5 additions & 2 deletions samples/UnoSampleAppTests/Pages/ControlsPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ namespace UnoSampleAppTests.Pages
using Legerity.Uno.Extensions;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium.Windows;
using OpenQA.Selenium.Remote;
using Shouldly;
using ByExtensions = Legerity.Windows.Extensions.ByExtensions;

public class ControlsPage : BasePage
{
private const string SampleControlPrefix = "Sample";

/// <summary>
/// Gets a given trait of the page to verify that the page is in view.
/// </summary>
protected override By Trait => this.DetermineTrait();

public Button Button => this.App.FindElementByAutomationId($"{SampleControlPrefix}{nameof(this.Button)}");
Expand Down Expand Up @@ -115,8 +119,7 @@ public ControlsPage SetDate(DateTime date)

public ControlsPage VerifyDate(DateTime expectedDate)
{
Thread.Sleep(
500); // Uno Wasm applications run too fast to ensure the selected date is set correctly before executing.
Thread.Sleep(500); // Uno Wasm applications run too fast to ensure the selected date is set correctly before executing.
this.DatePicker.SelectedDate.ShouldBe(expectedDate);
return this;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<PackageReleaseNotes>https://github.com/MADE-Apps/legerity-uno/releases</PackageReleaseNotes>
<NeutralLanguage>en</NeutralLanguage>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>8.0</LangVersion>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
32 changes: 32 additions & 0 deletions tools/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project>

<PropertyGroup>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<Version>1.0.0.0</Version>
<Authors>MADE Apps</Authors>
<Company>MADE Apps</Company>
<Copyright>Copyright (C) MADE Apps. All rights reserved.</Copyright>
<PackageProjectUrl>https://github.com/MADE-Apps/legerity-uno</PackageProjectUrl>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageIcon>ProjectLogo.png</PackageIcon>
<PackageReleaseNotes>https://github.com/MADE-Apps/legerity-uno/releases</PackageReleaseNotes>
<NeutralLanguage>en</NeutralLanguage>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<None Include="..\..\assets\ProjectLogo.png" Pack="true" PackagePath=""/>
<None Include="..\..\LICENSE" Pack="true" PackagePath=""/>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers; buildtransitive"/>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace Legerity.Uno.Features.Generator.Models;

using System.Collections.Generic;

internal class GeneratorTemplateData
{
public GeneratorTemplateData(string ns, string page, string baseElementType)
{
this.Namespace = ns;
this.Page = page;
this.Type = baseElementType;
}

public string Page { get; set; }

public string Type { get; set; }

public string Namespace { get; set; }

public List<UiElement> Elements { get; set; } = new();

public override string ToString()
{
return $"[Page] {this.Page}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Legerity.Uno.Features.Generator.Models;

internal class UiElement
{
public UiElement(string type, string name, string by, string value)
{
this.Type = type;
this.Name = name;
this.By = by;
this.Value = value;
}

public string Type { get; set; }

public string Name { get; set; }

public string By { get; set; }

public string Value { get; set; }

public override string ToString()
{
return $"[Type] {this.Type}; [Name] {this.Name}; [By] {this.By}; [Value] {this.Value};";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
namespace Legerity.Uno.Features.Generator;

using System.Text;
using System.Xml.Linq;
using Infrastructure.Extensions;
using Infrastructure.IO;
using Legerity.Uno.Features.Generator.Models;
using MADE.Collections.Compare;
using MADE.Data.Validation.Extensions;
using Scriban;
using Serilog;

internal class XamlPageObjectGenerator
{
private const string XamlNamespace = "http://schemas.microsoft.com/winfx/2006/xaml";

private static readonly GenericEqualityComparer<string> SimpleStringComparer = new(s => s.ToLower());

private const string BaseElementType = "RemoteWebElement";

private static IEnumerable<string> SupportedUnoPlatformElements => new List<string>
{
"AppBarButton",
"AppBarToggleButton",
"Button",
"CheckBox",
"ComboBox",
"CommandBar",
"DatePicker",
"HyperlinkButton",
"TextBlock",
"TextBox",
"TimePicker"
};

internal async Task GenerateAsync(string ns, string inputPath, string outputPath)
{
IEnumerable<string>? filePaths = GetXamlFilePaths(inputPath)?.ToList();

if (filePaths == null || !filePaths.Any())
{
Log.Warning("No XAML files found in {InputPath}", inputPath);
return;
}

foreach (string filePath in filePaths)
{
Log.Information($"Processing {filePath}");

await using FileStream fileStream = File.Open(filePath, FileMode.Open);
var xaml = XDocument.Load(fileStream);

if (xaml.Root != null && xaml.Root.Name.ToString().Contains("Page"))
{
var templateData =
new GeneratorTemplateData(ns, Path.GetFileNameWithoutExtension(filePath), BaseElementType);

Log.Information($"Generating template for {templateData}");

IEnumerable<XElement> elements = this.FlattenElements(xaml.Root.Elements());
foreach (XElement element in elements)
{
string? automationId = element.Attribute("AutomationProperties.AutomationId")?.Value;
string? uid = element.Attribute(XName.Get("Uid", XamlNamespace))?.Value;
string? name = element.Attribute(XName.Get("Name", XamlNamespace))?.Value;

string? byLocatorType = GetByLocatorType(uid, automationId, name);

if (byLocatorType == null || byLocatorType.IsNullOrWhiteSpace())
{
continue;
}

string? wrapperAutomationId = uid ?? automationId;
string? byQueryValue = wrapperAutomationId ?? name;

if (byQueryValue == null || byQueryValue.IsNullOrWhiteSpace())
{
continue;
}

var uiElement = new UiElement(GetElementWrapperType(element.Name.LocalName),
byQueryValue.Capitalize(),
byLocatorType,
byQueryValue);

Log.Information($"Element found on page - {uiElement}");

templateData.Elements.Add(uiElement);
}

await GeneratePageObjectClassFileAsync(templateData, outputPath);
}
else
{
Log.Warning($"Skipping {filePath} as a page was not detected");
}
}
}

private static async Task GeneratePageObjectClassFileAsync(
GeneratorTemplateData templateData,
string outputFolder)
{
var pageObjectTemplate = Template.Parse(await EmbeddedResourceLoader.ReadAsync("Legerity.Uno.Templates.UnoPageObject.template"));

string outputFile = $"{templateData.Page}.cs";

Log.Information($"Generating {outputFile} page object file");
string result = await pageObjectTemplate.RenderAsync(templateData);

FileStream output = File.Create(Path.Combine(outputFolder, outputFile));
var outputWriter = new StreamWriter(output, Encoding.UTF8);

await using (outputWriter)
{
await outputWriter.WriteAsync(result);
}
}

private static string? GetByLocatorType(string? uid, string? automationId, string? name)
{
if ((uid != null && !uid.IsNullOrWhiteSpace()) || (automationId != null && !automationId.IsNullOrWhiteSpace()))
{
return "AutomationId";
}

return name != null && !name.IsNullOrWhiteSpace() ? "Name" : null;
}

private static IEnumerable<string>? GetXamlFilePaths(string searchFolder)
{
string[]? filePaths = default;

try
{
filePaths = Directory.GetFiles(searchFolder, "*.xaml", SearchOption.AllDirectories);
}
catch (UnauthorizedAccessException uae)
{
Log.Error(
"An error occurred while retrieving XAML files for processing",
uae);
}

return filePaths;
}

private static string GetElementWrapperType(string elementName)
{
return SupportedUnoPlatformElements.Contains(elementName, SimpleStringComparer) ? elementName : BaseElementType;
}

private IEnumerable<XElement> FlattenElements(IEnumerable<XElement> elements)
{
return elements.SelectMany(c => this.FlattenElements(c.Elements())).Concat(elements);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Legerity.Uno.Infrastructure.Configuration;

using CommandLine;

internal class Options
{
[Option('i', "input",
HelpText =
"The path to the folder where platform pages exist that will be generating page objects for. Default to the executing folder.")]
public string InputPath { get; set; } = @"C:\S\Personal\MADE\legerity-uno\samples\UnoSampleApp\UnoSampleApp\UnoSampleApp.Shared";

[Option('o', "output",
HelpText =
"The path to the folder where the generated page object files should be stored. Default to the 'Generated' folder in the executing folder.")]
public string OutputPath { get; set; } = System.IO.Path.Combine(Environment.CurrentDirectory, "Generated");

[Option('n', "namespace",
HelpText =
"The namespace to apply to the output page objects. Default to 'LegerityUnoTests.Pages'.")]
public string Namespace { get; set; } = "LegerityUnoTests.Pages";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Legerity.Uno.Infrastructure.Extensions;

using System.Linq;

internal static class StringExtensions
{
internal static string Capitalize(this string value)
{
return value.First().ToString().ToUpper() + value.Substring(1);
}
}
Loading

0 comments on commit 1eb33f6

Please sign in to comment.