Skip to content

Commit 8d58b9c

Browse files
mialeskaDmitryBogatko
authored andcommitted
Feature/basic implementation (#2)
* repare a basic implementation of core functionality #1 Author: Alaksiej Mialeška <[email protected]> * replace GetText() with property. make constants internal * fix logging and settings resources. Add some tests * Fix ConditionalWait and tests * add general tests and some documentation. Enhanced Startup for flexibility * fix review comments in LoggerConfiguration and Element * removed internal access modifiers * Set up CI with Azure Pipelines [skip ci] * Update azure-pipelines.yml for Azure Pipelines * Update azure-pipelines.yml for Azure Pipelines * fix webDriverVersion for tests * fix WebDriverVersion dowloaded for tests at azure pipeline. closes #3 * Update azure-pipelines.yml for Azure Pipelines * fix webDriverVersion * fix yml file * Add tests for WinAppDriver and ElementFactory (#7) * Add tests for WinAppDriver and ElementFactory to resolve #5. Related to #6. Corrected Element's public interface * Update azure-pipelines.yml for Azure Pipelines use WinAppDriver task * Update azure-pipelines.yml for Azure Pipelines * replace calc application * Fixed ElementFactoryTests for new application * Feature/abstract application manager (#8) * Extract abstract ApplicationManager from WIndowsApp/ApplicationManager * reworked ApplicationManager for chrome app tests * Enhanced abstract ApplicationManager, add ability to provide custom dependencies. Create tests to ensure that ApplicationManager could have custom dependencies registered in container.
1 parent 9d9efa9 commit 8d58b9c

File tree

69 files changed

+3163
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+3163
-0
lines changed

.gitignore

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
*.swp
2+
*.*~
3+
project.lock.json
4+
.DS_Store
5+
*.pyc
6+
nupkg/
7+
8+
# Visual Studio Code
9+
.vscode
10+
11+
# Rider
12+
.idea
13+
14+
# User-specific files
15+
*.suo
16+
*.user
17+
*.userosscache
18+
*.sln.docstates
19+
20+
# Build results
21+
[Dd]ebug/
22+
[Dd]ebugPublic/
23+
[Rr]elease/
24+
[Rr]eleases/
25+
x64/
26+
x86/
27+
build/
28+
bld/
29+
[Bb]in/
30+
[Oo]bj/
31+
[Oo]ut/
32+
msbuild.log
33+
msbuild.err
34+
msbuild.wrn
35+
36+
# Visual Studio 2015
37+
.vs/
38+
39+
Log/
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.29009.5
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{613FE616-73B3-4C48-A84A-86C635363482}"
7+
EndProject
8+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aquality.Selenium.Core", "src\Aquality.Selenium.Core\Aquality.Selenium.Core.csproj", "{DCE7E884-9495-4B8B-BF82-2CB1AAEF8704}"
9+
EndProject
10+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{020FB8C3-77FD-48AE-BDD4-69B5186C0A2E}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aquality.Selenium.Core.Tests", "tests\Aquality.Selenium.Core.Tests\Aquality.Selenium.Core.Tests.csproj", "{BE6E2E8F-597C-46C4-BC3B-259A74FBA5C7}"
13+
EndProject
14+
Global
15+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
16+
Debug|Any CPU = Debug|Any CPU
17+
Release|Any CPU = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
20+
{DCE7E884-9495-4B8B-BF82-2CB1AAEF8704}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{DCE7E884-9495-4B8B-BF82-2CB1AAEF8704}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{DCE7E884-9495-4B8B-BF82-2CB1AAEF8704}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{DCE7E884-9495-4B8B-BF82-2CB1AAEF8704}.Release|Any CPU.Build.0 = Release|Any CPU
24+
{BE6E2E8F-597C-46C4-BC3B-259A74FBA5C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25+
{BE6E2E8F-597C-46C4-BC3B-259A74FBA5C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
26+
{BE6E2E8F-597C-46C4-BC3B-259A74FBA5C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
27+
{BE6E2E8F-597C-46C4-BC3B-259A74FBA5C7}.Release|Any CPU.Build.0 = Release|Any CPU
28+
EndGlobalSection
29+
GlobalSection(SolutionProperties) = preSolution
30+
HideSolutionNode = FALSE
31+
EndGlobalSection
32+
GlobalSection(NestedProjects) = preSolution
33+
{DCE7E884-9495-4B8B-BF82-2CB1AAEF8704} = {613FE616-73B3-4C48-A84A-86C635363482}
34+
{BE6E2E8F-597C-46C4-BC3B-259A74FBA5C7} = {020FB8C3-77FD-48AE-BDD4-69B5186C0A2E}
35+
EndGlobalSection
36+
GlobalSection(ExtensibilityGlobals) = postSolution
37+
SolutionGuid = {E4DEE5C3-7FA4-4D43-A57E-1E9EE245FE98}
38+
EndGlobalSection
39+
EndGlobal
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using System;
3+
using System.Threading;
4+
5+
namespace Aquality.Selenium.Core.Applications
6+
{
7+
public abstract class ApplicationManager<TManager, TApplication>
8+
where TManager : ApplicationManager<TManager, TApplication>
9+
where TApplication : class, IApplication
10+
{
11+
private static readonly ThreadLocal<TApplication> AppContainer = new ThreadLocal<TApplication>();
12+
private static readonly ThreadLocal<IServiceProvider> ServiceProviderContainer = new ThreadLocal<IServiceProvider>();
13+
14+
public static bool IsStarted => AppContainer.IsValueCreated && AppContainer.Value.Driver.SessionId != null;
15+
16+
protected static TApplication GetApplication(Func<IServiceProvider, TApplication> startApplicationFunction, IServiceCollection serviceCollection = null)
17+
{
18+
if (!IsStarted)
19+
{
20+
AppContainer.Value = startApplicationFunction(
21+
GetServiceProvider(service => GetApplication(startApplicationFunction), serviceCollection));
22+
}
23+
return AppContainer.Value;
24+
}
25+
26+
protected static IServiceProvider GetServiceProvider(Func<IServiceProvider, TApplication> applicationSupplier, IServiceCollection serviceCollection = null)
27+
{
28+
if (!ServiceProviderContainer.IsValueCreated)
29+
{
30+
var services = serviceCollection;
31+
if (services == null)
32+
{
33+
services = new ServiceCollection();
34+
new Startup().ConfigureServices(services, applicationSupplier);
35+
}
36+
ServiceProviderContainer.Value = services.BuildServiceProvider();
37+
}
38+
return ServiceProviderContainer.Value;
39+
}
40+
}
41+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using OpenQA.Selenium.Remote;
3+
4+
namespace Aquality.Selenium.Core.Applications
5+
{
6+
/// <summary>
7+
/// Interface of any application controlled by Selenium WebDriver API
8+
/// </summary>
9+
public interface IApplication
10+
{
11+
/// <summary>
12+
/// Current instance of driver
13+
/// </summary>
14+
RemoteWebDriver Driver { get; }
15+
16+
/// <summary>
17+
/// Sets implicit wait timeout to browser.
18+
/// Method was extracted with purpose not to pass it to Driver if it is similar to previous value.
19+
/// Simpliest implementation is: Driver.Manage().Timeouts().ImplicitlyWait = timeout
20+
/// </summary>
21+
/// <param name="timeout">timeout to set</param>
22+
void SetImplicitWaitTimeout(TimeSpan timeout);
23+
}
24+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using Aquality.Selenium.Core.Configurations;
2+
using Aquality.Selenium.Core.Elements;
3+
using Aquality.Selenium.Core.Elements.Interfaces;
4+
using Aquality.Selenium.Core.Localization;
5+
using Aquality.Selenium.Core.Logging;
6+
using Aquality.Selenium.Core.Utilities;
7+
using Aquality.Selenium.Core.Waitings;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using System;
10+
using System.Reflection;
11+
12+
namespace Aquality.Selenium.Core.Applications
13+
{
14+
/// <summary>
15+
/// Allows to resolve dependencies for all services in the Aquality.Selenium.Core library
16+
/// </summary>
17+
public class Startup
18+
{
19+
/// <summary>
20+
/// Used to configure dependencies for services of the current library
21+
/// </summary>
22+
/// <param name="services">Collection of service descriptors</param>
23+
/// <param name="applicationProvider">function that provides an instance of <see cref="IApplication"/></param>
24+
/// <param name="settingsFile"><see cref="JsonFile"/> with settings for configuration of dependencies.
25+
/// Pass the result of <see cref="GetSettings"/> if you need to get settings from the embedded resource of your project.</param>
26+
public void ConfigureServices(IServiceCollection services, Func<IServiceProvider, IApplication> applicationProvider, JsonFile settingsFile = null)
27+
{
28+
var settings = settingsFile ?? GetSettings();
29+
services.AddScoped(applicationProvider);
30+
31+
services.AddSingleton<ITimeoutConfiguration>(new TimeoutConfiguration(settings));
32+
services.AddTransient<ConditionalWait>();
33+
services.AddSingleton<ILoggerConfiguration>(new LoggerConfiguration(settings));
34+
services.AddSingleton(Logger.Instance);
35+
services.AddSingleton<LocalizationManager>();
36+
services.AddSingleton<LocalizationLogger>();
37+
services.AddSingleton<IRetryConfiguration>(new RetryConfiguration(settings));
38+
services.AddSingleton<ElementActionRetrier>();
39+
40+
services.AddTransient<IElementFinder, ElementFinder>();
41+
services.AddTransient<IElementFactory, ElementFactory>();
42+
}
43+
44+
/// <summary>
45+
/// Provides a <see cref="JsonFile"/> with settings.
46+
/// If "profile" environment variable is defined, it will be used in the name : $"settings.{profile}.json";
47+
/// Otherwise, will use default name of settings file: "settings.json".
48+
/// Will look for the resource file (copied to binaries/Resources/ folder);
49+
/// If not found, will look for embedded resource in the calling assembly of this method
50+
/// </summary>
51+
/// <returns>An instance of settings JsonFile</returns>
52+
public JsonFile GetSettings()
53+
{
54+
var profileNameFromEnvironment = EnvironmentConfiguration.GetVariable("profile");
55+
var settingsProfile = profileNameFromEnvironment == null ? "settings.json" : $"settings.{profileNameFromEnvironment}.json";
56+
Logger.Instance.Debug($"Get settings from: {settingsProfile}");
57+
58+
var jsonFile = FileReader.IsResourceFileExist(settingsProfile)
59+
? new JsonFile(settingsProfile)
60+
: new JsonFile($"Resources.{settingsProfile}", Assembly.GetCallingAssembly());
61+
return jsonFile;
62+
}
63+
}
64+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
6+
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
7+
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
8+
<Authors>aquality automation committers</Authors>
9+
<Company>aquality automation</Company>
10+
<Description>Core functions for Selenium-based libraries for .NET</Description>
11+
<PackageDescription>Core functions for Selenium-based libraries for .NET</PackageDescription>
12+
<PackageLicenseExpression></PackageLicenseExpression>
13+
<RepositoryUrl>https://github.com/aquality-automation/aquality-selenium-dotnet</RepositoryUrl>
14+
<RepositoryType>git</RepositoryType>
15+
<PackageTags>selenium webdriver automation</PackageTags>
16+
<PackageLicenseFile>LICENSE</PackageLicenseFile>
17+
<Copyright>Copyright 2019 Aquality Automation</Copyright>
18+
<IsPackable>true</IsPackable>
19+
</PropertyGroup>
20+
21+
<ItemGroup>
22+
<None Remove="Resources\Localization\be.json" />
23+
<None Remove="Resources\Localization\en.json" />
24+
<None Remove="Resources\Localization\ru.json" />
25+
<None Remove="Resources\settings.json" />
26+
</ItemGroup>
27+
28+
<ItemGroup>
29+
<EmbeddedResource Include="Resources\Localization\be.json" />
30+
<EmbeddedResource Include="Resources\Localization\en.json" />
31+
<EmbeddedResource Include="Resources\Localization\ru.json" />
32+
<EmbeddedResource Include="Resources\settings.json" />
33+
</ItemGroup>
34+
<ItemGroup>
35+
<None Include="..\..\..\LICENSE">
36+
<Pack>True</Pack>
37+
<PackagePath></PackagePath>
38+
</None>
39+
</ItemGroup>
40+
<ItemGroup>
41+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
42+
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
43+
<PackageReference Include="NLog" Version="4.6.6" />
44+
<PackageReference Include="Selenium.Support" Version="3.141.0" />
45+
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
46+
</ItemGroup>
47+
</Project>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Aquality.Selenium.Core.Configurations
2+
{
3+
/// <summary>
4+
/// Describes logger configuration.
5+
/// </summary>
6+
public interface ILoggerConfiguration
7+
{
8+
/// <summary>
9+
/// Gets language of framework.
10+
/// </summary>
11+
/// <value>Supported language.</value>
12+
string Language { get; }
13+
}
14+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
3+
namespace Aquality.Selenium.Core.Configurations
4+
{
5+
/// <summary>
6+
/// Describes retry configuration.
7+
/// </summary>
8+
public interface IRetryConfiguration
9+
{
10+
/// <summary>
11+
/// Gets the number of attempts during retry.
12+
/// </summary>
13+
/// <value>Number of retry attempts.</value>
14+
int Number { get; }
15+
16+
/// <summary>
17+
/// Gets the polling interval used in retry.
18+
/// </summary>
19+
/// <value>Polling interval for retry.</value>
20+
TimeSpan PollingInterval { get; }
21+
}
22+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
3+
namespace Aquality.Selenium.Core.Configurations
4+
{
5+
/// <summary>
6+
/// Describes timeouts configuration.
7+
/// </summary>
8+
public interface ITimeoutConfiguration
9+
{
10+
/// <summary>
11+
/// Gets WedDriver ImplicitWait timeout.
12+
/// </summary>
13+
TimeSpan Implicit { get; }
14+
15+
/// <summary>
16+
/// Gets default ConditionalWait timeout.
17+
/// </summary>
18+
TimeSpan Condition { get; }
19+
20+
/// <summary>
21+
/// Gets ConditionalWait polling interfal.
22+
/// </summary>
23+
TimeSpan PollingInterval { get; }
24+
25+
/// <summary>
26+
/// Gets Command timeout.
27+
/// </summary>
28+
TimeSpan Command { get; }
29+
}
30+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Aquality.Selenium.Core.Utilities;
2+
3+
namespace Aquality.Selenium.Core.Configurations
4+
{
5+
/// <summary>
6+
/// Provides logger configuration
7+
/// </summary>
8+
public class LoggerConfiguration : ILoggerConfiguration
9+
{
10+
private const string defaultLanguage = "en";
11+
private readonly JsonFile settingsFile;
12+
13+
/// <summary>
14+
/// Instantiates class using JSON file with general settings.
15+
/// </summary>
16+
/// <param name="settingsFile">JSON settings file.</param>
17+
public LoggerConfiguration(JsonFile settingsFile)
18+
{
19+
this.settingsFile = settingsFile;
20+
}
21+
22+
public string Language => settingsFile.GetValueOrDefault(".logger.language", defaultLanguage);
23+
}
24+
}

0 commit comments

Comments
 (0)