Compare commits
9 Commits
73f29cea08
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 46150d3a69 | |||
| 85834466f1 | |||
| 7da6f554a8 | |||
| dc0395c7d3 | |||
| 026aebd5ad | |||
| 3974fcfb91 | |||
| 410e7e23b7 | |||
| 6655cdeee7 | |||
| 1f7a0819fc |
@@ -25,7 +25,7 @@ jobs:
|
|||||||
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_CALM_MUD_04916B210 }}
|
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_CALM_MUD_04916B210 }}
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
action: "upload"
|
action: "upload"
|
||||||
app_location: "IGP"
|
app_location: "Web"
|
||||||
api_location: ""
|
api_location: ""
|
||||||
output_location: "./wwwroot"
|
output_location: "./wwwroot"
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_NICE_COAST_0F8B08010 }}
|
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_NICE_COAST_0F8B08010 }}
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
action: "upload"
|
action: "upload"
|
||||||
app_location: "IGP"
|
app_location: "Web"
|
||||||
api_location: ""
|
api_location: ""
|
||||||
output_location: "./wwwroot"
|
output_location: "./wwwroot"
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ DocProject/Help/html
|
|||||||
|
|
||||||
# Click-Once directory
|
# Click-Once directory
|
||||||
publish/
|
publish/
|
||||||
|
publish_release/
|
||||||
|
|
||||||
# Publish Web Output
|
# Publish Web Output
|
||||||
*.[Pp]ublish.xml
|
*.[Pp]ublish.xml
|
||||||
@@ -264,3 +265,6 @@ __pycache__/
|
|||||||
|
|
||||||
**/.vs/
|
**/.vs/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
|
||||||
|
publish_release/
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.0
|
||||||
Vendored
+3
-3
@@ -7,7 +7,7 @@
|
|||||||
"type": "process",
|
"type": "process",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"${workspaceFolder}/IGP/IGP.csproj",
|
"${workspaceFolder}/Web/Web.csproj",
|
||||||
"/property:GenerateFullPaths=true",
|
"/property:GenerateFullPaths=true",
|
||||||
"/consoleloggerparameters:NoSummary"
|
"/consoleloggerparameters:NoSummary"
|
||||||
],
|
],
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"type": "process",
|
"type": "process",
|
||||||
"args": [
|
"args": [
|
||||||
"publish",
|
"publish",
|
||||||
"${workspaceFolder}/IGP/IGP.csproj",
|
"${workspaceFolder}/Web/Web.csproj",
|
||||||
"/property:GenerateFullPaths=true",
|
"/property:GenerateFullPaths=true",
|
||||||
"/consoleloggerparameters:NoSummary"
|
"/consoleloggerparameters:NoSummary"
|
||||||
],
|
],
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"watch",
|
"watch",
|
||||||
"run",
|
"run",
|
||||||
"--project",
|
"--project",
|
||||||
"${workspaceFolder}/IGP/IGP.csproj"
|
"${workspaceFolder}/Web/Web.csproj"
|
||||||
],
|
],
|
||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>IGP.Calculator.Cli</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Services\Services.csproj" />
|
||||||
|
<ProjectReference Include="..\Model\Model.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
+155
@@ -0,0 +1,155 @@
|
|||||||
|
using IGP.Calculator.Cli.Services;
|
||||||
|
using Model.Entity;
|
||||||
|
using Model.Entity.Data;
|
||||||
|
using Services.Immortal;
|
||||||
|
using Services.Website;
|
||||||
|
|
||||||
|
var faction = DataType.FACTION_QRath;
|
||||||
|
var immortal = DataType.IMMORTAL_Orzum;
|
||||||
|
var attackTime = 1500;
|
||||||
|
var entityNames = new List<string>();
|
||||||
|
|
||||||
|
for (var i = 0; i < args.Length; i++)
|
||||||
|
switch (args[i].ToLower())
|
||||||
|
{
|
||||||
|
case "--faction" when i + 1 < args.Length:
|
||||||
|
var f = args[++i].ToLower();
|
||||||
|
faction = f switch
|
||||||
|
{
|
||||||
|
"qrath" => DataType.FACTION_QRath,
|
||||||
|
"aru" => DataType.FACTION_Aru,
|
||||||
|
_ => throw new Exception($"Unknown faction '{args[i]}'. Use QRath or Aru.")
|
||||||
|
};
|
||||||
|
immortal = f switch
|
||||||
|
{
|
||||||
|
"qrath" => DataType.IMMORTAL_Orzum,
|
||||||
|
"aru" => DataType.IMMORTAL_Mala,
|
||||||
|
_ => immortal
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "--immortal" when i + 1 < args.Length:
|
||||||
|
var im = args[++i];
|
||||||
|
immortal = im switch
|
||||||
|
{
|
||||||
|
nameof(DataType.IMMORTAL_Orzum) => DataType.IMMORTAL_Orzum,
|
||||||
|
nameof(DataType.IMMORTAL_Ajari) => DataType.IMMORTAL_Ajari,
|
||||||
|
nameof(DataType.IMMORTAL_Atzlan) => DataType.IMMORTAL_Atzlan,
|
||||||
|
nameof(DataType.IMMORTAL_Mala) => DataType.IMMORTAL_Mala,
|
||||||
|
nameof(DataType.IMMORTAL_Xol) => DataType.IMMORTAL_Xol,
|
||||||
|
_ => throw new Exception($"Unknown immortal '{im}'.")
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "--attack-time" or "-a" when i + 1 < args.Length:
|
||||||
|
attackTime = int.Parse(args[++i]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
entityNames.Add(args[i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var toastService = new ToastService();
|
||||||
|
var storageService = new NullStorageService();
|
||||||
|
var timingService = new TimingService(storageService);
|
||||||
|
timingService.SetAttackTime(attackTime);
|
||||||
|
var buildOrderService = new BuildOrderService(toastService, timingService);
|
||||||
|
var economyService = new EconomyService();
|
||||||
|
|
||||||
|
buildOrderService.Reset(faction);
|
||||||
|
economyService.Calculate(buildOrderService, timingService, 0);
|
||||||
|
|
||||||
|
Console.WriteLine($"Faction: {(faction == DataType.FACTION_QRath ? "Q'Rath" : "Aru")}");
|
||||||
|
Console.WriteLine($"Immortal: {immortal.Replace("IMMORTAL_", "")}");
|
||||||
|
Console.WriteLine($"Attack Time: {attackTime}s");
|
||||||
|
Console.WriteLine(new string('-', 50));
|
||||||
|
|
||||||
|
foreach (var name in entityNames)
|
||||||
|
{
|
||||||
|
if (name.StartsWith("wait ", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var seconds = int.Parse(name[5..]);
|
||||||
|
buildOrderService.AddWait(seconds);
|
||||||
|
economyService.Calculate(buildOrderService, timingService, buildOrderService.GetLastRequestInterval());
|
||||||
|
Console.WriteLine($" Wait {seconds}s -> now at interval {buildOrderService.GetLastRequestInterval()}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.StartsWith("waitto ", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var interval = int.Parse(name[7..]);
|
||||||
|
buildOrderService.AddWaitTo(interval);
|
||||||
|
economyService.Calculate(buildOrderService, timingService, buildOrderService.GetLastRequestInterval());
|
||||||
|
Console.WriteLine($" Wait to {interval}s -> now at interval {buildOrderService.GetLastRequestInterval()}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entity = FindEntity(name, faction, immortal);
|
||||||
|
if (entity == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" ERROR: '{name}' not found for this faction/immortal.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var beforeInterval = buildOrderService.GetLastRequestInterval();
|
||||||
|
var added = buildOrderService.Add(entity, economyService);
|
||||||
|
if (added)
|
||||||
|
{
|
||||||
|
economyService.Calculate(buildOrderService, timingService, buildOrderService.GetLastRequestInterval());
|
||||||
|
var startedAt = buildOrderService.GetLastRequestInterval();
|
||||||
|
var production = entity.Production();
|
||||||
|
var completedAt = production != null ? startedAt + production.BuildTime : startedAt;
|
||||||
|
var cost = production != null
|
||||||
|
? $" [{production.Alloy}a/{production.Ether}e/{production.Pyre}p, {production.BuildTime}s]"
|
||||||
|
: "";
|
||||||
|
Console.WriteLine($" {entity.GetName(),-25} start={startedAt,4}s done={completedAt,4}s{cost}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($" ERROR: Could not add '{name}'.");
|
||||||
|
var toasts = toastService.GetToasts();
|
||||||
|
if (toasts.Count > 0)
|
||||||
|
{
|
||||||
|
var lastToast = toasts[0];
|
||||||
|
Console.WriteLine($" Reason: {lastToast.Title} - {lastToast.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine(new string('-', 50));
|
||||||
|
|
||||||
|
var lastInterval = buildOrderService.GetLastRequestInterval();
|
||||||
|
var finalEconomy = economyService.GetEconomy(timingService.GetAttackTime());
|
||||||
|
var lastEconomy = economyService.GetEconomy(lastInterval);
|
||||||
|
|
||||||
|
Console.WriteLine($"Army Attacking At: {timingService.GetAttackTime()}s");
|
||||||
|
Console.WriteLine("");
|
||||||
|
Console.WriteLine($"At attack time ({timingService.GetAttackTime()}s):");
|
||||||
|
Console.WriteLine($" Alloy: {finalEconomy.Alloy,10:F1}");
|
||||||
|
Console.WriteLine($" Ether: {finalEconomy.Ether,10:F1}");
|
||||||
|
Console.WriteLine($" Pyre: {finalEconomy.Pyre,10:F1}");
|
||||||
|
Console.WriteLine("");
|
||||||
|
Console.WriteLine($"At last build action ({lastInterval}s):");
|
||||||
|
Console.WriteLine($" Alloy: {lastEconomy.Alloy,10:F1}");
|
||||||
|
Console.WriteLine($" Ether: {lastEconomy.Ether,10:F1}");
|
||||||
|
Console.WriteLine($" Pyre: {lastEconomy.Pyre,10:F1}");
|
||||||
|
|
||||||
|
static EntityModel? FindEntity(string name, string faction, string immortal)
|
||||||
|
{
|
||||||
|
var candidates = EntityModel.GetList()
|
||||||
|
.Where(e => e.Info()?.Name?.Equals(name, StringComparison.OrdinalIgnoreCase) == true)
|
||||||
|
.Where(e => e.Faction()?.Faction == faction)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (candidates.Count == 0)
|
||||||
|
candidates = EntityModel.GetList()
|
||||||
|
.Where(e => e.Info()?.Name?.Equals(name, StringComparison.OrdinalIgnoreCase) == true)
|
||||||
|
.Where(e => e.Faction() == null || e.Faction()!.Faction == DataType.FACTION_Neutral)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (candidates.Count == 0) return null;
|
||||||
|
if (candidates.Count == 1) return candidates[0];
|
||||||
|
|
||||||
|
var vanguardMatch = candidates.FirstOrDefault(e => e.VanguardAdded()?.ImmortalId == immortal);
|
||||||
|
if (vanguardMatch != null) return vanguardMatch;
|
||||||
|
|
||||||
|
return candidates.FirstOrDefault(e => e.VanguardAdded() == null);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using Services;
|
||||||
|
|
||||||
|
namespace IGP.Calculator.Cli.Services;
|
||||||
|
|
||||||
|
public class NullStorageService : IStorageService
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, object?> _store = new();
|
||||||
|
|
||||||
|
public void Subscribe(Action action)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unsubscribe(Action action)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public T GetValue<T>(string forKey)
|
||||||
|
{
|
||||||
|
if (_store.TryGetValue(forKey, out var value) && value is T typed)
|
||||||
|
return typed;
|
||||||
|
return default!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetValue<T>(string key, T value)
|
||||||
|
{
|
||||||
|
_store[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Load()
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<RootNamespace>Components</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
}
|
}
|
||||||
<div>
|
<div>
|
||||||
<input readonly="@MemoryQuestion.IsRevealed"
|
<input readonly="@MemoryQuestion.IsRevealed"
|
||||||
class="formTextInput @(MemoryQuestion.IsRevealed ? "revealed" : IsSubmitted == false ? "guess" : int.Parse(guess ?? string.Empty) == MemoryQuestion.Answer ? "correct" : "wrong")"
|
class="formTextInput @(MemoryQuestion.IsRevealed ? "revealed" : !IsSubmitted ? "guess" : int.Parse(guess ?? string.Empty) == MemoryQuestion.Answer ? "correct" : "wrong")"
|
||||||
placeholder="guess..."
|
placeholder="guess..."
|
||||||
type="number"
|
type="number"
|
||||||
value="@guess"
|
value="@guess"
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY . .
|
||||||
|
RUN dotnet publish Web/Web.csproj -c Release -o /app/publish
|
||||||
|
|
||||||
|
FROM nginx:alpine AS final
|
||||||
|
WORKDIR /usr/share/nginx/html
|
||||||
|
COPY --from=build /app/publish/wwwroot ./
|
||||||
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.32112.339
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Model", "Model\Model.csproj", "{F4D758C2-349F-49F2-86E2-7DAB2B53BB61}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services", "Services\Services.csproj", "{4F56F39D-D34C-4987-8C3F-3A8E03A2D7E6}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web", "Web\Web.csproj", "{4A54E237-4FF6-4459-91D9-9249AE3E72E0}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CLI", "CLI\CLI.csproj", "{F8A23FFE-7FCC-4C28-B5CF-DBE435284AFA}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Components", "Components\Components.csproj", "{C0ACFFEB-8098-4119-9C28-3A4D671C2514}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{1460CB4C-7E8E-41F0-8DF2-174C69A3E366}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{F4D758C2-349F-49F2-86E2-7DAB2B53BB61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F4D758C2-349F-49F2-86E2-7DAB2B53BB61}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F4D758C2-349F-49F2-86E2-7DAB2B53BB61}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{F4D758C2-349F-49F2-86E2-7DAB2B53BB61}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{F4D758C2-349F-49F2-86E2-7DAB2B53BB61}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{F4D758C2-349F-49F2-86E2-7DAB2B53BB61}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{F4D758C2-349F-49F2-86E2-7DAB2B53BB61}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F4D758C2-349F-49F2-86E2-7DAB2B53BB61}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{F4D758C2-349F-49F2-86E2-7DAB2B53BB61}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{F4D758C2-349F-49F2-86E2-7DAB2B53BB61}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{F4D758C2-349F-49F2-86E2-7DAB2B53BB61}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{F4D758C2-349F-49F2-86E2-7DAB2B53BB61}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{4F56F39D-D34C-4987-8C3F-3A8E03A2D7E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4F56F39D-D34C-4987-8C3F-3A8E03A2D7E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4F56F39D-D34C-4987-8C3F-3A8E03A2D7E6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{4F56F39D-D34C-4987-8C3F-3A8E03A2D7E6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{4F56F39D-D34C-4987-8C3F-3A8E03A2D7E6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{4F56F39D-D34C-4987-8C3F-3A8E03A2D7E6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{4F56F39D-D34C-4987-8C3F-3A8E03A2D7E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4F56F39D-D34C-4987-8C3F-3A8E03A2D7E6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4F56F39D-D34C-4987-8C3F-3A8E03A2D7E6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{4F56F39D-D34C-4987-8C3F-3A8E03A2D7E6}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{4F56F39D-D34C-4987-8C3F-3A8E03A2D7E6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{4F56F39D-D34C-4987-8C3F-3A8E03A2D7E6}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{4A54E237-4FF6-4459-91D9-9249AE3E72E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4A54E237-4FF6-4459-91D9-9249AE3E72E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4A54E237-4FF6-4459-91D9-9249AE3E72E0}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{4A54E237-4FF6-4459-91D9-9249AE3E72E0}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{4A54E237-4FF6-4459-91D9-9249AE3E72E0}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{4A54E237-4FF6-4459-91D9-9249AE3E72E0}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{4A54E237-4FF6-4459-91D9-9249AE3E72E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4A54E237-4FF6-4459-91D9-9249AE3E72E0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4A54E237-4FF6-4459-91D9-9249AE3E72E0}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{4A54E237-4FF6-4459-91D9-9249AE3E72E0}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{4A54E237-4FF6-4459-91D9-9249AE3E72E0}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{4A54E237-4FF6-4459-91D9-9249AE3E72E0}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{F8A23FFE-7FCC-4C28-B5CF-DBE435284AFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F8A23FFE-7FCC-4C28-B5CF-DBE435284AFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F8A23FFE-7FCC-4C28-B5CF-DBE435284AFA}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{F8A23FFE-7FCC-4C28-B5CF-DBE435284AFA}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{F8A23FFE-7FCC-4C28-B5CF-DBE435284AFA}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{F8A23FFE-7FCC-4C28-B5CF-DBE435284AFA}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{F8A23FFE-7FCC-4C28-B5CF-DBE435284AFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F8A23FFE-7FCC-4C28-B5CF-DBE435284AFA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{F8A23FFE-7FCC-4C28-B5CF-DBE435284AFA}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{F8A23FFE-7FCC-4C28-B5CF-DBE435284AFA}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{F8A23FFE-7FCC-4C28-B5CF-DBE435284AFA}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{F8A23FFE-7FCC-4C28-B5CF-DBE435284AFA}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{C0ACFFEB-8098-4119-9C28-3A4D671C2514}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C0ACFFEB-8098-4119-9C28-3A4D671C2514}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C0ACFFEB-8098-4119-9C28-3A4D671C2514}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{C0ACFFEB-8098-4119-9C28-3A4D671C2514}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{C0ACFFEB-8098-4119-9C28-3A4D671C2514}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{C0ACFFEB-8098-4119-9C28-3A4D671C2514}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{C0ACFFEB-8098-4119-9C28-3A4D671C2514}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C0ACFFEB-8098-4119-9C28-3A4D671C2514}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C0ACFFEB-8098-4119-9C28-3A4D671C2514}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{C0ACFFEB-8098-4119-9C28-3A4D671C2514}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{C0ACFFEB-8098-4119-9C28-3A4D671C2514}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{C0ACFFEB-8098-4119-9C28-3A4D671C2514}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{1460CB4C-7E8E-41F0-8DF2-174C69A3E366}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1460CB4C-7E8E-41F0-8DF2-174C69A3E366}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1460CB4C-7E8E-41F0-8DF2-174C69A3E366}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{1460CB4C-7E8E-41F0-8DF2-174C69A3E366}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{1460CB4C-7E8E-41F0-8DF2-174C69A3E366}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{1460CB4C-7E8E-41F0-8DF2-174C69A3E366}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{1460CB4C-7E8E-41F0-8DF2-174C69A3E366}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{1460CB4C-7E8E-41F0-8DF2-174C69A3E366}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1460CB4C-7E8E-41F0-8DF2-174C69A3E366}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{1460CB4C-7E8E-41F0-8DF2-174C69A3E366}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{1460CB4C-7E8E-41F0-8DF2-174C69A3E366}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{1460CB4C-7E8E-41F0-8DF2-174C69A3E366}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {19100811-B909-4D20-9AE0-2EB579A55B3A}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
-49
@@ -1,49 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 17
|
|
||||||
VisualStudioVersion = 17.0.32112.339
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IGP", "IGP.csproj", "{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Model", "..\Model\Model.csproj", "{77395F7A-BE93-470C-9F10-F48FFA445B63}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Components", "..\Components\Components.csproj", "{0419E7CD-0971-4A56-A61F-C090DF60FAF6}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Services", "..\Services\Services.csproj", "{621178C8-4E8B-478E-80E5-7478F0E7B67E}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestAutomation", "..\TestAutomation\TestAutomation.csproj", "{8B49D038-D013-460D-9C4F-817CAFFEB06F}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{172D35E4-8E7B-40D1-96D6-BE2A2043CFCA}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{77395F7A-BE93-470C-9F10-F48FFA445B63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{77395F7A-BE93-470C-9F10-F48FFA445B63}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{77395F7A-BE93-470C-9F10-F48FFA445B63}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{77395F7A-BE93-470C-9F10-F48FFA445B63}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{0419E7CD-0971-4A56-A61F-C090DF60FAF6}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{621178C8-4E8B-478E-80E5-7478F0E7B67E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{621178C8-4E8B-478E-80E5-7478F0E7B67E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{621178C8-4E8B-478E-80E5-7478F0E7B67E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{621178C8-4E8B-478E-80E5-7478F0E7B67E}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{8B49D038-D013-460D-9C4F-817CAFFEB06F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{8B49D038-D013-460D-9C4F-817CAFFEB06F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{8B49D038-D013-460D-9C4F-817CAFFEB06F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{8B49D038-D013-460D-9C4F-817CAFFEB06F}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {19100811-B909-4D20-9AE0-2EB579A55B3A}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
@@ -48,7 +48,7 @@ public class BuildOrderModel
|
|||||||
new List<EntityModel>
|
new List<EntityModel>
|
||||||
{
|
{
|
||||||
EntityModel.Get(DataType.STARTING_Bastion),
|
EntityModel.Get(DataType.STARTING_Bastion),
|
||||||
EntityModel.Get(DataType.STARTING_TownHall_Aru)
|
EntityModel.Get(factionStartingTownHall)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -59,7 +59,7 @@ public class BuildOrderModel
|
|||||||
new List<EntityModel>
|
new List<EntityModel>
|
||||||
{
|
{
|
||||||
EntityModel.Get(DataType.STARTING_Bastion),
|
EntityModel.Get(DataType.STARTING_Bastion),
|
||||||
EntityModel.Get(DataType.STARTING_TownHall_Aru)
|
EntityModel.Get(factionStartingTownHall)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -69,7 +69,7 @@ public class BuildOrderModel
|
|||||||
DataType.STARTING_Bastion, 0
|
DataType.STARTING_Bastion, 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DataType.STARTING_TownHall_Aru, 0
|
factionStartingTownHall, 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
UniqueCompletedCount = new Dictionary<string, int>
|
UniqueCompletedCount = new Dictionary<string, int>
|
||||||
@@ -78,7 +78,7 @@ public class BuildOrderModel
|
|||||||
DataType.STARTING_Bastion, 1
|
DataType.STARTING_Bastion, 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DataType.STARTING_TownHall_Aru, 1
|
factionStartingTownHall, 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
SupplyCountTimes = new Dictionary<int, int>
|
SupplyCountTimes = new Dictionary<int, int>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
namespace Model.Entity.Parts;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Model.Entity.Parts;
|
||||||
|
|
||||||
public class IEntityPartInterface
|
public class IEntityPartInterface
|
||||||
{
|
{
|
||||||
public EntityModel Parent { get; set; }
|
[JsonIgnore] public EntityModel Parent { get; set; }
|
||||||
}
|
}
|
||||||
+2
-1
@@ -1,7 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<RootNamespace>Model</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1"/>
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1"/>
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules/
|
||||||
|
test-results/
|
||||||
|
playwright-report/
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
const ScreenType = Object.freeze({ Desktop: 'desktop', Tablet: 'tablet', Mobile: 'mobile' });
|
||||||
|
|
||||||
|
class Website {
|
||||||
|
constructor(page, options = {}) {
|
||||||
|
this.page = page;
|
||||||
|
this.screenType = ScreenType.Desktop;
|
||||||
|
this.runAgainstProduction = options.production || process.env.RUN_AGAINST_PRODUCTION === 'true';
|
||||||
|
|
||||||
|
if (this.runAgainstProduction) {
|
||||||
|
this.baseUrl = 'https://igpfanreference.ca';
|
||||||
|
} else {
|
||||||
|
const hook = process.env.TEST_HOOK || '';
|
||||||
|
this.deploymentType = hook.includes('localhost') ? 'Local' : 'Dev';
|
||||||
|
this.baseUrl = 'https://localhost:7234';
|
||||||
|
}
|
||||||
|
|
||||||
|
const BuildCalculatorPage = require('../pages/buildCalculatorPage');
|
||||||
|
const HarassCalculatorPage = require('../pages/harassCalculator.page');
|
||||||
|
const DatabasePage = require('../pages/database.page');
|
||||||
|
const DatabaseSinglePage = require('../pages/databaseSingle.page');
|
||||||
|
const NavigationBar = require('../shared/navigationBar');
|
||||||
|
const WebsiteSearchDialog = require('../shared/websiteSearchDialog');
|
||||||
|
|
||||||
|
this.buildCalculatorPage = new BuildCalculatorPage(this);
|
||||||
|
this.harassCalculatorPage = new HarassCalculatorPage(this);
|
||||||
|
this.databasePage = new DatabasePage(this);
|
||||||
|
this.databaseSinglePage = new DatabaseSinglePage(this);
|
||||||
|
this.navigationBar = new NavigationBar(this);
|
||||||
|
this.websiteSearchDialog = new WebsiteSearchDialog(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
locator(selector) {
|
||||||
|
return this.page.locator(selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
find(byId) {
|
||||||
|
return this.page.locator(`#${byId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
findWithParent(byId, withParentId) {
|
||||||
|
return this.page.locator(`#${withParentId} #${byId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
findScreenSpecific(byId) {
|
||||||
|
return this.page.locator(`#${this.screenType}-${byId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
findAll(byId) {
|
||||||
|
return this.page.locator(`#${byId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
findAllWithTag(tag) {
|
||||||
|
return this.page.locator(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
findAllWithTagFromElement(element, tag) {
|
||||||
|
return element.locator(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
findButtonWithLabel(label) {
|
||||||
|
return this.page.locator(`button[label="${label}"]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
findChildren(ofId, tagname) {
|
||||||
|
return this.page.locator(`#${ofId} ${tagname}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findText(byId) {
|
||||||
|
return (await this.page.locator(`#${byId}`).textContent()) || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async findInt(byId) {
|
||||||
|
const text = await this.findText(byId);
|
||||||
|
return parseInt(text, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickSearchBackground() {
|
||||||
|
await this.page.locator('#searchBackground').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickElement(element) {
|
||||||
|
await element.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async enterInput(element, value) {
|
||||||
|
await element.fill(String(value));
|
||||||
|
await element.press('Enter');
|
||||||
|
}
|
||||||
|
|
||||||
|
async goto(path) {
|
||||||
|
if (path) {
|
||||||
|
await this.page.goto(`${this.baseUrl}/${path}`);
|
||||||
|
} else {
|
||||||
|
await this.page.goto(this.baseUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { Website, ScreenType };
|
||||||
Generated
+76
@@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"name": "playwright",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "playwright",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@playwright/test": "^1.60.0",
|
||||||
|
"playwright": "^1.60.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@playwright/test": {
|
||||||
|
"version": "1.60.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz",
|
||||||
|
"integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright": "1.60.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright": {
|
||||||
|
"version": "1.60.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz",
|
||||||
|
"integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright-core": "1.60.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright-core": {
|
||||||
|
"version": "1.60.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz",
|
||||||
|
"integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"playwright-core": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "playwright",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "npx playwright test",
|
||||||
|
"test:headed": "npx playwright test --headed",
|
||||||
|
"report": "npx playwright show-report"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "commonjs",
|
||||||
|
"dependencies": {
|
||||||
|
"@playwright/test": "^1.60.0",
|
||||||
|
"playwright": "^1.60.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
class BasePage {
|
||||||
|
constructor(website) {
|
||||||
|
this.website = website;
|
||||||
|
}
|
||||||
|
|
||||||
|
get url() {
|
||||||
|
throw new Error('Subclasses must implement url');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLinks() {
|
||||||
|
const content = this.website.find('content');
|
||||||
|
const links = content.locator('a');
|
||||||
|
return await links.evaluateAll(els => els.map(el => el.getAttribute('href')).filter(Boolean));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BasePage;
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
class ArmyComponent {
|
||||||
|
constructor(page) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
armyView() {
|
||||||
|
return this.page.locator('.armyView');
|
||||||
|
}
|
||||||
|
|
||||||
|
displayValue(label) {
|
||||||
|
return this.page.locator('.displayContainer').filter({ hasText: label }).locator('.displayContent');
|
||||||
|
}
|
||||||
|
|
||||||
|
armyCards() {
|
||||||
|
return this.armyView().locator('.armyCard');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getArmyCompletedAt() {
|
||||||
|
return await this.displayValue('Army Completed At').textContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getArmyAttackingAt() {
|
||||||
|
return await this.displayValue('Army Attacking At').textContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getArmyUnitNames() {
|
||||||
|
const cards = await this.armyCards().all();
|
||||||
|
const names = [];
|
||||||
|
for (const card of cards) {
|
||||||
|
const text = await card.innerText();
|
||||||
|
const match = text.match(/\d+x\s*(.+)/);
|
||||||
|
names.push(match ? match[1].trim() : text.trim());
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getArmyUnitCounts() {
|
||||||
|
const cards = await this.armyCards().all();
|
||||||
|
const counts = [];
|
||||||
|
for (const card of cards) {
|
||||||
|
const countEl = card.locator('.armyCount');
|
||||||
|
const nameEl = card.locator('div').last();
|
||||||
|
const count = await countEl.textContent();
|
||||||
|
const name = await nameEl.textContent();
|
||||||
|
const num = count ? parseInt(count.replace('x', ''), 10) : 0;
|
||||||
|
counts.push({ name: (name || '').trim(), count: num });
|
||||||
|
}
|
||||||
|
return counts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ArmyComponent;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
class BankComponent {
|
||||||
|
constructor(page) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
bankContainer() {
|
||||||
|
return this.page.locator('.bankContainer');
|
||||||
|
}
|
||||||
|
|
||||||
|
displayValue(label) {
|
||||||
|
return this.bankContainer().locator('.displayContainer').filter({ hasText: label }).locator('.displayContent');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTime() {
|
||||||
|
return await this.displayValue('Time').textContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAlloy() {
|
||||||
|
return await this.displayValue('Alloy').textContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEther() {
|
||||||
|
return await this.displayValue('Ether').textContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPyre() {
|
||||||
|
return await this.displayValue('Pyre').textContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSupply() {
|
||||||
|
return await this.displayValue('Supply').textContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWorkerCount() {
|
||||||
|
return await this.bankContainer().locator('.workerText').locator('.displayContent').nth(0).textContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBusyWorkerCount() {
|
||||||
|
return await this.bankContainer().locator('.workerText').locator('.displayContent').nth(1).textContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCreatingWorkerCount() {
|
||||||
|
return await this.bankContainer().locator('.workerText').locator('.displayContent').nth(2).textContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BankComponent;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
class BuildChartComponent {
|
||||||
|
constructor(page) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
chartsContainer() {
|
||||||
|
return this.page.locator('.chartsContainer');
|
||||||
|
}
|
||||||
|
|
||||||
|
displayValue(label) {
|
||||||
|
return this.page.locator('.displayContainer').filter({ hasText: label }).locator('.displayContent');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getHighestAlloy() {
|
||||||
|
return await this.displayValue('Highest Alloy').textContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getHighestEther() {
|
||||||
|
return await this.displayValue('Highest Ether').textContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getHighestPyre() {
|
||||||
|
return await this.displayValue('Highest Pyre').textContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getHighestArmy() {
|
||||||
|
return await this.displayValue('Highest Army').textContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getChartCount() {
|
||||||
|
return await this.chartsContainer().locator('> div').count();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BuildChartComponent;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
class BuildOrderComponent {
|
||||||
|
constructor(page) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonTextarea() {
|
||||||
|
return this.page.locator('textarea');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getJsonData() {
|
||||||
|
return await this.jsonTextarea().inputValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BuildOrderComponent;
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
class EntityClickViewComponent {
|
||||||
|
constructor(page) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
entityClickView() {
|
||||||
|
return this.page.locator('.entityClickView');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEntityName() {
|
||||||
|
const el = this.entityClickView().locator('#entityName');
|
||||||
|
if ((await el.count()) === 0) return null;
|
||||||
|
return (await el.textContent()) || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEntityHealth() {
|
||||||
|
const healthText = this.entityClickView().locator('div').filter({ hasText: /Health/i }).first();
|
||||||
|
if ((await healthText.count()) === 0) return null;
|
||||||
|
const text = (await healthText.textContent()) || '';
|
||||||
|
const match = text.match(/(\d+)/);
|
||||||
|
return match ? match[1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickDetailedView() {
|
||||||
|
await this.entityClickView().locator('button').filter({ hasText: 'Detailed' }).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickPlainView() {
|
||||||
|
await this.entityClickView().locator('button').filter({ hasText: 'Plain' }).click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = EntityClickViewComponent;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
class FilterComponent {
|
||||||
|
constructor(page) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
factionSelect() {
|
||||||
|
return this.page.locator('select').filter({ has: this.page.locator('option:has-text("Aru"), option:has-text("Q\'Rath")') });
|
||||||
|
}
|
||||||
|
|
||||||
|
immortalSelect() {
|
||||||
|
return this.page.locator('select').filter({ has: this.page.locator('option:has-text("Orzum"), option:has-text("Ajari"), option:has-text("Atzlan"), option:has-text("Mala"), option:has-text("Xol")') });
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectFaction(faction) {
|
||||||
|
await this.factionSelect().selectOption(faction);
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectImmortal(immortal) {
|
||||||
|
await this.immortalSelect().selectOption(immortal);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSelectedFaction() {
|
||||||
|
return await this.factionSelect().inputValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSelectedImmortal() {
|
||||||
|
return await this.immortalSelect().inputValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAvailableImmortals() {
|
||||||
|
return await this.immortalSelect().locator('option').allTextContents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = FilterComponent;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
class HighlightsComponent {
|
||||||
|
constructor(page) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
highlightsContainer() {
|
||||||
|
return this.page.locator('.highlightsContainer');
|
||||||
|
}
|
||||||
|
|
||||||
|
requestedColumn() {
|
||||||
|
return this.highlightsContainer().locator('div').filter({ hasText: 'Requested' }).locator('+ div');
|
||||||
|
}
|
||||||
|
|
||||||
|
finishedColumn() {
|
||||||
|
return this.highlightsContainer().locator('div').filter({ hasText: 'Finished' }).locator('+ div');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRequestedItems() {
|
||||||
|
const items = await this.highlightsContainer().locator('div').filter({ hasText: /^\d+\s*\|/ }).all();
|
||||||
|
const result = [];
|
||||||
|
for (const item of items) {
|
||||||
|
const text = (await item.textContent()) || '';
|
||||||
|
result.push(text.trim());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFinishedItems() {
|
||||||
|
const items = await this.highlightsContainer().locator('div').filter({ hasText: /^\d+\s*\|/ }).all();
|
||||||
|
const result = [];
|
||||||
|
for (const item of items) {
|
||||||
|
const text = (await item.textContent()) || '';
|
||||||
|
result.push(text.trim());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = HighlightsComponent;
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
class HotkeyViewerComponent {
|
||||||
|
constructor(page) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
keyContainer() {
|
||||||
|
return this.page.locator('.keyContainer');
|
||||||
|
}
|
||||||
|
|
||||||
|
async _findKeyButton(keyLabel) {
|
||||||
|
const upper = keyLabel.toUpperCase();
|
||||||
|
const buttons = this.keyContainer().locator('> div > div');
|
||||||
|
const count = await buttons.count();
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const btn = buttons.nth(i);
|
||||||
|
const text = (await btn.textContent()) || '';
|
||||||
|
if (text.trim().toUpperCase().startsWith(upper)) return btn;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickKey(keyText) {
|
||||||
|
const btn = await this._findKeyButton(keyText);
|
||||||
|
if (!btn) throw new Error(`Key "${keyText}" not found`);
|
||||||
|
await btn.click({ force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFirstEntityName(keyText) {
|
||||||
|
const btn = await this._findKeyButton(keyText);
|
||||||
|
if (!btn) return null;
|
||||||
|
const entities = btn.locator('> div');
|
||||||
|
if ((await entities.count()) === 0) return null;
|
||||||
|
return (await entities.first().textContent()) || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEntityNamesOnKey(keyText) {
|
||||||
|
const btn = await this._findKeyButton(keyText);
|
||||||
|
if (!btn) return [];
|
||||||
|
const entities = btn.locator('> div');
|
||||||
|
const count = await entities.count();
|
||||||
|
const names = [];
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const text = (await entities.nth(i).textContent()) || '';
|
||||||
|
names.push(text.trim());
|
||||||
|
}
|
||||||
|
return names.filter(Boolean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = HotkeyViewerComponent;
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
class OptionsComponent {
|
||||||
|
constructor(page) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildingInputDelayInput() {
|
||||||
|
return this.formNumberInput('Building Input Delay');
|
||||||
|
}
|
||||||
|
|
||||||
|
waitTimeInput() {
|
||||||
|
return this.formNumberInput('Wait Time');
|
||||||
|
}
|
||||||
|
|
||||||
|
waitToInput() {
|
||||||
|
return this.formNumberInput('Wait To');
|
||||||
|
}
|
||||||
|
|
||||||
|
addWaitButton() {
|
||||||
|
return this.buttonWithLabel('Add Wait').first();
|
||||||
|
}
|
||||||
|
|
||||||
|
addWaitToButton() {
|
||||||
|
return this.buttonWithLabel('Add Wait').last();
|
||||||
|
}
|
||||||
|
|
||||||
|
formNumberInput(label) {
|
||||||
|
return this.page.locator(`.formNumberContainer`).filter({ hasText: label }).locator('input[type="number"]');
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonWithLabel(label) {
|
||||||
|
return this.page.locator('button').filter({ hasText: label });
|
||||||
|
}
|
||||||
|
|
||||||
|
async setBuildingInputDelay(value) {
|
||||||
|
await this.buildingInputDelayInput().fill(String(value));
|
||||||
|
await this.buildingInputDelayInput().press('Enter');
|
||||||
|
}
|
||||||
|
|
||||||
|
async setWaitTime(value) {
|
||||||
|
await this.waitTimeInput().fill(String(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
async setWaitTo(value) {
|
||||||
|
await this.waitToInput().fill(String(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickAddWait() {
|
||||||
|
await this.addWaitButton().click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickAddWaitTo() {
|
||||||
|
await this.addWaitToButton().click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBuildingInputDelay() {
|
||||||
|
return await this.buildingInputDelayInput().inputValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWaitTime() {
|
||||||
|
return await this.waitTimeInput().inputValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWaitTo() {
|
||||||
|
return await this.waitToInput().inputValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = OptionsComponent;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
class TimelineComponent {
|
||||||
|
constructor(page) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
container() {
|
||||||
|
return this.page.locator('.calculatorGrid > div').filter({ hasText: 'Timeline highlights' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async containsEntity(name) {
|
||||||
|
const text = (await this.container().textContent()) || '';
|
||||||
|
return text.includes(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TimelineComponent;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
class TimingComponent {
|
||||||
|
constructor(website) {
|
||||||
|
this.website = website;
|
||||||
|
}
|
||||||
|
|
||||||
|
attackTimeInput() {
|
||||||
|
return this.formNumberInput('Attack Time');
|
||||||
|
}
|
||||||
|
|
||||||
|
travelTimeInput() {
|
||||||
|
return this.formNumberInput('Travel Time');
|
||||||
|
}
|
||||||
|
|
||||||
|
formNumberInput(label) {
|
||||||
|
return this.website.locator(`.formNumberContainer`).filter({ hasText: label }).locator('input[type="number"]');
|
||||||
|
}
|
||||||
|
|
||||||
|
async setAttackTime(value) {
|
||||||
|
await this.attackTimeInput().fill(String(value));
|
||||||
|
await this.attackTimeInput().press('Enter');
|
||||||
|
}
|
||||||
|
|
||||||
|
async setTravelTime(value) {
|
||||||
|
await this.travelTimeInput().fill(String(value));
|
||||||
|
await this.travelTimeInput().press('Enter');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAttackTime() {
|
||||||
|
return await this.attackTimeInput().inputValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTravelTime() {
|
||||||
|
return await this.travelTimeInput().inputValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TimingComponent;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
const TimingComponent = require('./buildCalculator/timingComponent');
|
||||||
|
const FilterComponent = require('./buildCalculator/filterComponent');
|
||||||
|
const OptionsComponent = require('./buildCalculator/optionsComponent');
|
||||||
|
const BankComponent = require('./buildCalculator/bankComponent');
|
||||||
|
const ArmyComponent = require('./buildCalculator/armyComponent');
|
||||||
|
const HighlightsComponent = require('./buildCalculator/highlightsComponent');
|
||||||
|
const BuildOrderComponent = require('./buildCalculator/buildOrderComponent');
|
||||||
|
const TimelineComponent = require('./buildCalculator/timelineComponent');
|
||||||
|
const HotkeyViewerComponent = require('./buildCalculator/hotkeyViewerComponent');
|
||||||
|
const EntityClickViewComponent = require('./buildCalculator/entityClickViewComponent');
|
||||||
|
const BuildChartComponent = require('./buildCalculator/buildChartComponent');
|
||||||
|
const ToastComponent = require('../shared/toastComponent');
|
||||||
|
|
||||||
|
const BasePage = require('./base.page');
|
||||||
|
|
||||||
|
|
||||||
|
class BuildCalculatorPage extends BasePage {
|
||||||
|
constructor(website) {
|
||||||
|
super(website);
|
||||||
|
this.timing = new TimingComponent(website);
|
||||||
|
this.filter = new FilterComponent(website);
|
||||||
|
this.options = new OptionsComponent(website);
|
||||||
|
this.bank = new BankComponent(website);
|
||||||
|
this.army = new ArmyComponent(website);
|
||||||
|
this.highlights = new HighlightsComponent(website);
|
||||||
|
this.buildOrder = new BuildOrderComponent(website);
|
||||||
|
this.timeline = new TimelineComponent(website);
|
||||||
|
this.hotkeys = new HotkeyViewerComponent(website);
|
||||||
|
this.entityView = new EntityClickViewComponent(website);
|
||||||
|
this.chart = new BuildChartComponent(website);
|
||||||
|
this.toast = new ToastComponent(website);
|
||||||
|
}
|
||||||
|
|
||||||
|
get url() {
|
||||||
|
return 'build-calculator';
|
||||||
|
}
|
||||||
|
|
||||||
|
calculatorGrid() {
|
||||||
|
return this.website.locator('.calculatorGrid');
|
||||||
|
}
|
||||||
|
|
||||||
|
clearBuildOrderButton() {
|
||||||
|
return this.website.locator('button').filter({ hasText: 'Clear Build Order' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickClearBuildOrder() {
|
||||||
|
await this.clearBuildOrderButton().click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async goto() {
|
||||||
|
await this.website.goto(this.url);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BuildCalculatorPage;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
const BasePage = require('./base.page');
|
||||||
|
|
||||||
|
class DatabasePage extends BasePage {
|
||||||
|
get url() { return 'database'; }
|
||||||
|
|
||||||
|
async filterName(name) {
|
||||||
|
await this.website.enterInput(this.website.findAll('filterName').first(), name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEntityName(entityType, entityName) {
|
||||||
|
return await this.website
|
||||||
|
.findWithParent('entityName', `${entityType.toLowerCase()}-${entityName.toLowerCase()}`)
|
||||||
|
.innerText();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEntityNameByIndex(index) {
|
||||||
|
return await this.website.findAll('entityName').nth(index).innerText();
|
||||||
|
}
|
||||||
|
|
||||||
|
async goto() {
|
||||||
|
await this.website.goto(this.url);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = DatabasePage;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
const BasePage = require('./base.page');
|
||||||
|
|
||||||
|
class DatabaseSinglePage extends BasePage {
|
||||||
|
get url() { return 'database'; }
|
||||||
|
|
||||||
|
async getEntityName() {
|
||||||
|
return await this.website.find('entityName').innerText();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEntityHealth() {
|
||||||
|
return await this.website.find('entityHealth').innerText();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInvalidSearch() {
|
||||||
|
return await this.website.find('invalidSearch').innerText();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getValidSearch() {
|
||||||
|
return await this.website.find('validSearch').innerText();
|
||||||
|
}
|
||||||
|
|
||||||
|
async goto(searchText) {
|
||||||
|
await this.website.goto(`${this.url}/${searchText}`);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = DatabaseSinglePage;
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
const BasePage = require('./base.page');
|
||||||
|
|
||||||
|
class HarassCalculatorPage extends BasePage {
|
||||||
|
get url() { return 'harass-calculator'; }
|
||||||
|
|
||||||
|
async setWorkersLostToHarass(number) {
|
||||||
|
await this.website.enterInput(this.website.find('numberOfWorkersLostToHarass'), number);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setNumberOfTownHallsExisting(number) {
|
||||||
|
await this.website.enterInput(this.website.find('numberOfTownHallsExisting'), number);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setTownHallTravelTime(forTownHall, number) {
|
||||||
|
const inputs = this.website.findChildren('numberOfTownHallTravelTimes', 'input');
|
||||||
|
await this.website.enterInput(inputs.nth(forTownHall), number);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTotalAlloyHarassment() {
|
||||||
|
return await this.website.findInt('totalAlloyHarassment');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWorkerReplacementCost() {
|
||||||
|
return await this.website.findInt('workerReplacementCost');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDelayedMiningCost() {
|
||||||
|
return await this.website.findInt('delayedMiningCost');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAverageTravelTime() {
|
||||||
|
return await this.website.findInt('getAverageTravelTime');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getExampleTotalAlloyLoss() {
|
||||||
|
return await this.website.findInt('exampleTotalAlloyLoss');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getExampleWorkerCost() {
|
||||||
|
return await this.website.findInt('exampleWorkerCost');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getExampleMiningTimeCost() {
|
||||||
|
return await this.website.findInt('exampleMiningTimeCost');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getExampleTotalAlloyLossAccurate() {
|
||||||
|
return await this.website.findInt('exampleTotalAlloyLossAccurate');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getExampleTotalAlloyLossDifference() {
|
||||||
|
return await this.website.findInt('exampleTotalAlloyLossDifference');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getExampleTotalAlloyLossAccurateDifference() {
|
||||||
|
return await this.website.findInt('exampleTotalAlloyLossAccurateDifference');
|
||||||
|
}
|
||||||
|
|
||||||
|
async goto() {
|
||||||
|
await this.website.goto(this.url);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = HarassCalculatorPage;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
const { defineConfig } = require('@playwright/test');
|
||||||
|
|
||||||
|
module.exports = defineConfig({
|
||||||
|
testDir: './tests',
|
||||||
|
fullyParallel: true,
|
||||||
|
retries: 1,
|
||||||
|
timeout: 30000,
|
||||||
|
use: {
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
screenshot: 'only-on-failure',
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{ name: 'chromium', use: { browserName: 'chromium' } },
|
||||||
|
],
|
||||||
|
});
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
class NavigationBar {
|
||||||
|
constructor(website) {
|
||||||
|
this.website = website;
|
||||||
|
}
|
||||||
|
|
||||||
|
get searchButton() { return this.website.findScreenSpecific('searchButton'); }
|
||||||
|
|
||||||
|
async clickHomeLink() {
|
||||||
|
await this.website.clickElement(this.website.locator('a:has-text("IGP Fan Reference")'));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickSearchButton() {
|
||||||
|
await this.website.clickElement(this.searchButton);
|
||||||
|
return this.website.websiteSearchDialog;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = NavigationBar;
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
class ToastComponent {
|
||||||
|
constructor(page) {
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
container() {
|
||||||
|
return this.page.locator('.toastsContainer');
|
||||||
|
}
|
||||||
|
|
||||||
|
toasts() {
|
||||||
|
return this.page.locator('.toastsContainer .toastContainer');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getToastTitles() {
|
||||||
|
const titles = await this.page.locator('.toastsContainer .toastTitle').allTextContents();
|
||||||
|
return titles.map(t => t.trim()).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
_page() {
|
||||||
|
return this.page.page || this.page;
|
||||||
|
}
|
||||||
|
|
||||||
|
async hasToastContaining(text) {
|
||||||
|
try {
|
||||||
|
await this._page().waitForFunction(
|
||||||
|
(expected) => {
|
||||||
|
const titles = document.querySelectorAll('.toastsContainer .toastTitle');
|
||||||
|
return Array.from(titles).some(t => t.textContent.trim().includes(expected));
|
||||||
|
},
|
||||||
|
text,
|
||||||
|
{ timeout: 3000 }
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ToastComponent;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
class WebsiteSearchDialog {
|
||||||
|
constructor(website) {
|
||||||
|
this.website = website;
|
||||||
|
}
|
||||||
|
|
||||||
|
get searchBackground() { return this.website.find('searchBackground'); }
|
||||||
|
get searchInput() { return this.website.find('searchInput'); }
|
||||||
|
|
||||||
|
async closeDialog() {
|
||||||
|
await this.website.clickSearchBackground();
|
||||||
|
return this.website.navigationBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(text) {
|
||||||
|
await this.website.enterInput(this.searchInput, text);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectSearchEntity(label) {
|
||||||
|
await this.website.clickElement(this.website.findButtonWithLabel(label));
|
||||||
|
return this.website.databaseSinglePage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = WebsiteSearchDialog;
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
const BuildCalculatorPage = require('../pages/buildCalculatorPage');
|
||||||
|
const { Website } = require('../helpers/website');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
test.describe('Build Calculator', () => {
|
||||||
|
|
||||||
|
let website;
|
||||||
|
|
||||||
|
test.beforeEach(({ page }) => {
|
||||||
|
website = new Website(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Add entities via keyboard Q, W, E with Q\'Rath/Orzum', async ({ page }) => {
|
||||||
|
const calc = website.buildCalculatorPage;
|
||||||
|
await calc.goto();
|
||||||
|
|
||||||
|
await calc.filter.selectFaction("Q'Rath");
|
||||||
|
await calc.filter.selectImmortal('Orzum');
|
||||||
|
|
||||||
|
await calc.hotkeys.clickKey('TAB');
|
||||||
|
|
||||||
|
const keyNames = { Q: 'q', W: 'w', E: 'e', TAB: 'Tab' };
|
||||||
|
|
||||||
|
for (const key of ['Q', 'W', 'E', 'TAB']) {
|
||||||
|
const entityNames = await calc.hotkeys.getEntityNamesOnKey(key);
|
||||||
|
if (entityNames.length === 0) continue;
|
||||||
|
|
||||||
|
await page.keyboard.press(keyNames[key]);
|
||||||
|
|
||||||
|
const viewName = await calc.entityView.getEntityName();
|
||||||
|
expect(viewName).toBeTruthy();
|
||||||
|
expect(entityNames).toContain(viewName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Add entities via hotkeys TAB, Q, W, E with Q\'Rath/Orzum', async ({ page }) => {
|
||||||
|
const calc = website.buildCalculatorPage;
|
||||||
|
await calc.goto();
|
||||||
|
|
||||||
|
await calc.filter.selectFaction("Q'Rath");
|
||||||
|
await calc.filter.selectImmortal('Orzum');
|
||||||
|
|
||||||
|
for (const key of ['TAB', 'Q', 'W', 'E']) {
|
||||||
|
const entityNames = await calc.hotkeys.getEntityNamesOnKey(key);
|
||||||
|
if (entityNames.length === 0) continue;
|
||||||
|
|
||||||
|
await calc.hotkeys.clickKey(key);
|
||||||
|
|
||||||
|
const viewName = await calc.entityView.getEntityName();
|
||||||
|
expect(viewName).toBeTruthy();
|
||||||
|
expect(entityNames).toContain(viewName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Add Acropolis via Q, verify entity view and timeline, then clear', async ({ page }) => {
|
||||||
|
const calc = website.buildCalculatorPage;
|
||||||
|
await calc.goto();
|
||||||
|
|
||||||
|
await calc.filter.selectFaction("Q'Rath");
|
||||||
|
await calc.filter.selectImmortal('Orzum');
|
||||||
|
|
||||||
|
expect(await calc.timeline.containsEntity('Acropolis')).toBe(false);
|
||||||
|
|
||||||
|
await calc.hotkeys.clickKey('Q');
|
||||||
|
|
||||||
|
expect(await calc.entityView.getEntityName()).toBe('Acropolis');
|
||||||
|
expect(await calc.timeline.containsEntity('Acropolis')).toBe(true);
|
||||||
|
|
||||||
|
await calc.clickClearBuildOrder();
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
expect(await calc.timeline.containsEntity('Acropolis')).toBe(false);
|
||||||
|
expect(await calc.entityView.getEntityName()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Missing Requirements toast when building Soul Foundry without Legion Hall', async ({ page }) => {
|
||||||
|
const calc = website.buildCalculatorPage;
|
||||||
|
await calc.goto();
|
||||||
|
|
||||||
|
await calc.filter.selectFaction("Q'Rath");
|
||||||
|
await calc.filter.selectImmortal('Orzum');
|
||||||
|
|
||||||
|
await calc.hotkeys.clickKey('E');
|
||||||
|
const hasToast = await calc.toast.hasToastContaining('Missing Requirements');
|
||||||
|
expect(hasToast).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Not Enough Ether toast when building Soul Foundry after Legion Hall', async ({ page }) => {
|
||||||
|
const calc = website.buildCalculatorPage;
|
||||||
|
|
||||||
|
await calc.goto();
|
||||||
|
|
||||||
|
await calc.filter.selectFaction("Q'Rath");
|
||||||
|
await calc.filter.selectImmortal('Orzum');
|
||||||
|
|
||||||
|
await calc.hotkeys.clickKey('W');
|
||||||
|
|
||||||
|
await calc.hotkeys.clickKey('E');
|
||||||
|
const hasToast = await calc.toast.hasToastContaining('Not Enough Ether');
|
||||||
|
expect(hasToast).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
const { Website } = require('../helpers/website');
|
||||||
|
|
||||||
|
test.describe('Harass Calculator', () => {
|
||||||
|
let website;
|
||||||
|
|
||||||
|
test.beforeEach(({ page }) => {
|
||||||
|
website = new Website(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('CalculatorInput', async () => {
|
||||||
|
const page = website.harassCalculatorPage;
|
||||||
|
await page.goto();
|
||||||
|
await page.setWorkersLostToHarass(3);
|
||||||
|
await page.setNumberOfTownHallsExisting(2);
|
||||||
|
await page.setTownHallTravelTime(0, 30);
|
||||||
|
const result = await page.getTotalAlloyHarassment();
|
||||||
|
expect(result).toBe(240);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('CalculatedExampleInformation', async () => {
|
||||||
|
const page = website.harassCalculatorPage;
|
||||||
|
await page.goto();
|
||||||
|
|
||||||
|
expect(await page.getExampleTotalAlloyLoss()).toBe(720);
|
||||||
|
expect(await page.getExampleWorkerCost()).toBe(300);
|
||||||
|
expect(await page.getExampleMiningTimeCost()).toBe(420);
|
||||||
|
expect(await page.getExampleTotalAlloyLossAccurate()).toBe(450);
|
||||||
|
expect(await page.getExampleTotalAlloyLossDifference()).toBe(300);
|
||||||
|
expect(await page.getExampleTotalAlloyLossAccurateDifference()).toBe(270);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
const { test } = require('@playwright/test');
|
||||||
|
const { Website } = require('../helpers/website');
|
||||||
|
const TestReport = require('../utils/testReport');
|
||||||
|
|
||||||
|
test.describe('Link Verification', () => {
|
||||||
|
let website;
|
||||||
|
let testReport;
|
||||||
|
|
||||||
|
test.beforeEach(() => {
|
||||||
|
testReport = new TestReport();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('VerifyPageLinks', async ({ page }) => {
|
||||||
|
website = new Website(page);
|
||||||
|
testReport.createTest(test.info().title);
|
||||||
|
|
||||||
|
await website.harassCalculatorPage.goto();
|
||||||
|
await testReport.verifyLinks(website.harassCalculatorPage);
|
||||||
|
|
||||||
|
await website.databasePage.goto();
|
||||||
|
await testReport.verifyLinks(website.databasePage);
|
||||||
|
|
||||||
|
await website.databaseSinglePage.goto('throne');
|
||||||
|
await testReport.verifyLinks(website.databaseSinglePage);
|
||||||
|
|
||||||
|
testReport.throwErrors();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
const { Website } = require('../helpers/website');
|
||||||
|
|
||||||
|
test.describe('Search Features', () => {
|
||||||
|
let website;
|
||||||
|
|
||||||
|
test.beforeEach(({ page }) => {
|
||||||
|
website = new Website(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('DesktopOpenCloseSearchDialog', async () => {
|
||||||
|
await website.goto();
|
||||||
|
await website.navigationBar.clickSearchButton();
|
||||||
|
await website.websiteSearchDialog.closeDialog();
|
||||||
|
await website.navigationBar.clickHomeLink();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('DesktopSearchForThrone', async () => {
|
||||||
|
await website.goto();
|
||||||
|
await website.navigationBar.clickSearchButton();
|
||||||
|
await website.websiteSearchDialog.search('Throne');
|
||||||
|
const page = await website.websiteSearchDialog.selectSearchEntity('Throne');
|
||||||
|
|
||||||
|
const name = await page.getEntityName();
|
||||||
|
const health = await page.getEntityHealth();
|
||||||
|
|
||||||
|
expect(name).toBe('Throne');
|
||||||
|
expect(health.trim()).not.toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('DesktopFilterForThrone', async () => {
|
||||||
|
const page = website.databasePage;
|
||||||
|
await page.goto();
|
||||||
|
await page.filterName('Throne');
|
||||||
|
const name = await page.getEntityNameByIndex(0);
|
||||||
|
expect(name).toBe('Throne');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SeeThroneByDefault', async () => {
|
||||||
|
const page = website.databasePage;
|
||||||
|
await page.goto();
|
||||||
|
const name = await page.getEntityName('army', 'throne');
|
||||||
|
expect(name).toBe('Throne');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('DirectLinkNotThroneFailure', async () => {
|
||||||
|
const page = website.databaseSinglePage;
|
||||||
|
await page.goto('not throne');
|
||||||
|
const invalidSearch = await page.getInvalidSearch();
|
||||||
|
const validSearch = await page.getValidSearch();
|
||||||
|
expect(invalidSearch).toBe('not throne');
|
||||||
|
expect(validSearch).toBe('Throne');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
class TestReport {
|
||||||
|
constructor() {
|
||||||
|
this.tests = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
createTest(name) {
|
||||||
|
const test = { name, result: true, messages: [] };
|
||||||
|
this.tests.push(test);
|
||||||
|
return test;
|
||||||
|
}
|
||||||
|
|
||||||
|
throwErrors() {
|
||||||
|
const latest = this.tests[this.tests.length - 1];
|
||||||
|
if (!latest.result) {
|
||||||
|
const msgs = latest.messages.map(m => m.description).join('\n');
|
||||||
|
throw new Error(`${latest.name} test failed with ${latest.messages.length} messages.\n\n${msgs}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkPassed(passed, message) {
|
||||||
|
if (!passed) {
|
||||||
|
const latest = this.tests[this.tests.length - 1];
|
||||||
|
latest.result = false;
|
||||||
|
latest.messages.push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyLinks(page) {
|
||||||
|
const links = await page.getLinks();
|
||||||
|
for (const link of links) {
|
||||||
|
if (link.startsWith('mailto')) continue;
|
||||||
|
try {
|
||||||
|
const response = await fetch(link);
|
||||||
|
if (!response.ok) {
|
||||||
|
this.checkPassed(false, {
|
||||||
|
color: 'red',
|
||||||
|
title: 'Bad Link',
|
||||||
|
description: `${link} failed on page ${page.url} with status code ${response.status}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.checkPassed(false, {
|
||||||
|
color: 'red',
|
||||||
|
title: 'Bad Link',
|
||||||
|
description: `${link} failed on page ${page.url} with error ${e.message}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
didTestsPass() {
|
||||||
|
return this.tests.every(t => t.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMessages() {
|
||||||
|
if (this.didTestsPass()) {
|
||||||
|
return [{
|
||||||
|
title: 'Passed',
|
||||||
|
color: 0x00FF00,
|
||||||
|
description: `All ${this.tests.length} tests passed.`
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
const messages = [];
|
||||||
|
for (const test of this.tests) {
|
||||||
|
for (const msg of test.messages) {
|
||||||
|
messages.push({
|
||||||
|
title: msg.title,
|
||||||
|
color: parseInt(msg.color, 16),
|
||||||
|
description: msg.description
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TestReport;
|
||||||
@@ -1 +1,26 @@
|
|||||||
|
|
||||||
|
|
||||||
|
# IGP Fan Reference
|
||||||
|
|
||||||
|
A fan-made reference site for *IMMORTAL: Gates of Pyre*.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Comprehensive documentation for developers is available in the [`/docs`](./docs) folder:
|
||||||
|
|
||||||
|
- [**Overview**](./docs/overview.md): High-level project description and tech stack.
|
||||||
|
- [**Architecture**](./docs/architecture.md): Solution structure and design patterns.
|
||||||
|
- [**Services**](./docs/services.md): Detailed explanation of core application services.
|
||||||
|
- [**Components**](./docs/components.md): UI component library and layout structure.
|
||||||
|
- [**Development Guide**](./docs/development.md): Build, test, and deployment instructions.
|
||||||
|
- [**Recommendations**](./docs/recommendations.md): Suggestions for code and design improvements.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
To run the project locally:
|
||||||
|
|
||||||
|
1. Navigate to the `IGP` directory.
|
||||||
|
2. Run `dotnet watch run`.
|
||||||
|
3. Open `https://localhost:5001`.
|
||||||
|
|
||||||
|
For more details, see the [Development Guide](./docs/development.md).
|
||||||
|
|||||||
@@ -114,7 +114,6 @@ public interface IEntityDialogService
|
|||||||
public bool HasHistory();
|
public bool HasHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public interface INoteService
|
public interface INoteService
|
||||||
{
|
{
|
||||||
public List<NoteContentModel> NoteContentModels { get; set; }
|
public List<NoteContentModel> NoteContentModels { get; set; }
|
||||||
@@ -291,6 +290,7 @@ public interface IBuildOrderService
|
|||||||
|
|
||||||
public void RemoveLast();
|
public void RemoveLast();
|
||||||
public void Reset();
|
public void Reset();
|
||||||
|
public void Reset(string faction);
|
||||||
|
|
||||||
public int GetLastRequestInterval();
|
public int GetLastRequestInterval();
|
||||||
public string BuildOrderAsYaml();
|
public string BuildOrderAsYaml();
|
||||||
|
|||||||
@@ -290,6 +290,7 @@ public class BuildOrderService : IBuildOrderService
|
|||||||
{
|
{
|
||||||
return (from ordersAtTime in _buildOrder.StartedOrders
|
return (from ordersAtTime in _buildOrder.StartedOrders
|
||||||
from orders in ordersAtTime.Value
|
from orders in ordersAtTime.Value
|
||||||
|
where orders.Harvest() != null
|
||||||
where ordersAtTime.Key + (orders.Production() == null
|
where ordersAtTime.Key + (orders.Production() == null
|
||||||
? 0
|
? 0
|
||||||
: orders.Production().BuildTime) <= interval
|
: orders.Production().BuildTime) <= interval
|
||||||
@@ -298,7 +299,6 @@ public class BuildOrderService : IBuildOrderService
|
|||||||
ordersAtTime.Key + (orders.Production() == null
|
ordersAtTime.Key + (orders.Production() == null
|
||||||
? 0
|
? 0
|
||||||
: orders.Production().BuildTime))
|
: orders.Production().BuildTime))
|
||||||
where orders.Harvest() != null
|
|
||||||
select orders).ToList();
|
select orders).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,6 +341,13 @@ public class BuildOrderService : IBuildOrderService
|
|||||||
NotifyDataChanged();
|
NotifyDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Reset(string faction)
|
||||||
|
{
|
||||||
|
_lastInterval = 0;
|
||||||
|
_buildOrder.Initialize(faction);
|
||||||
|
NotifyDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
public int? WillMeetTrainingQueue(EntityModel entity)
|
public int? WillMeetTrainingQueue(EntityModel entity)
|
||||||
{
|
{
|
||||||
var supply = entity.Supply();
|
var supply = entity.Supply();
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ public class EconomyComparisionService : IEconomyComparisonService
|
|||||||
if (usedWorkers < harvester.Slots) workersNeeded += 1;
|
if (usedWorkers < harvester.Slots) workersNeeded += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (harvester.RequiresWorker == false)
|
if (!harvester.RequiresWorker)
|
||||||
{
|
{
|
||||||
if (harvester.Resource == ResourceType.Ether)
|
if (harvester.Resource == ResourceType.Ether)
|
||||||
economyAtSecond.Ether += harvester.HarvestedPerInterval * harvester.Slots;
|
economyAtSecond.Ether += harvester.HarvestedPerInterval * harvester.Slots;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>Services</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
using TestAutomation.Utils;
|
|
||||||
|
|
||||||
namespace TestAutomation;
|
|
||||||
|
|
||||||
public enum DeploymentType
|
|
||||||
{
|
|
||||||
Dev,
|
|
||||||
Local
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BaseTest
|
|
||||||
{
|
|
||||||
protected static readonly TestReport TestReport = new();
|
|
||||||
|
|
||||||
|
|
||||||
protected static Website WebsiteInstance = default!;
|
|
||||||
protected readonly HttpClient HttpClient = new();
|
|
||||||
|
|
||||||
protected static Website Website
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (WebsiteInstance == null)
|
|
||||||
{
|
|
||||||
var options = new FirefoxOptions();
|
|
||||||
|
|
||||||
options.AcceptInsecureCertificates = true;
|
|
||||||
|
|
||||||
if (Website.DeploymentType.Equals(DeploymentType.Dev)) options.AddArgument("--headless");
|
|
||||||
options.AddArgument("--ignore-certificate-errors");
|
|
||||||
options.AddArgument("--start-maximized");
|
|
||||||
options.AddArgument("--test-type");
|
|
||||||
options.AddArgument("--allow-running-insecure-content");
|
|
||||||
|
|
||||||
IWebDriver webDriver = new FirefoxDriver(Environment.CurrentDirectory, options);
|
|
||||||
|
|
||||||
WebsiteInstance = new Website(webDriver, TestReport);
|
|
||||||
}
|
|
||||||
|
|
||||||
return WebsiteInstance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace TestAutomation.Enums;
|
|
||||||
|
|
||||||
public enum ScreenType
|
|
||||||
{
|
|
||||||
Desktop,
|
|
||||||
Tablet,
|
|
||||||
Mobile
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
using TestAutomation.Shared;
|
|
||||||
using TestAutomation.Utils;
|
|
||||||
|
|
||||||
namespace TestAutomation.Pages;
|
|
||||||
|
|
||||||
public abstract class BasePage : BaseElement
|
|
||||||
{
|
|
||||||
protected BasePage(Website website) : base(website)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<string> Links =>
|
|
||||||
Website.FindAllWithTag(Website.Find("content"), "a")
|
|
||||||
.Select(x => x.GetAttribute("href"));
|
|
||||||
|
|
||||||
public abstract string Url { get; set; }
|
|
||||||
|
|
||||||
public IEnumerable<string> GetLinks()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return Links;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
throw new Exception($"Couldn't get links on page {Url}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
using System.Collections.ObjectModel;
|
|
||||||
using TestAutomation.Utils;
|
|
||||||
|
|
||||||
namespace TestAutomation.Pages;
|
|
||||||
|
|
||||||
public class DatabasePage : BasePage
|
|
||||||
{
|
|
||||||
public DatabasePage(Website website) : base(website)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private IWebElement FilterNameInput => Website.Find("filterName");
|
|
||||||
|
|
||||||
public override string Url { get; set; } = "database";
|
|
||||||
|
|
||||||
|
|
||||||
private ReadOnlyCollection<IWebElement> EntityNames()
|
|
||||||
{
|
|
||||||
return Website.FindAll("entityName");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private IWebElement EntityName(string entityType, string entityName)
|
|
||||||
{
|
|
||||||
return Website.Find("entityName",
|
|
||||||
$"{entityType.ToLower()}-{entityName.ToLower()}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public DatabasePage FilterName(string name)
|
|
||||||
{
|
|
||||||
Website.EnterInput(FilterNameInput, name);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DatabasePage GetEntityName(string entityType, string entityName, out string result)
|
|
||||||
{
|
|
||||||
result = EntityName(entityType, entityName).Text;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DatabasePage GetEntityName(int index, out string result)
|
|
||||||
{
|
|
||||||
result = EntityNames()[index].Text;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DatabasePage Goto()
|
|
||||||
{
|
|
||||||
Website.Goto(Url);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
using TestAutomation.Utils;
|
|
||||||
|
|
||||||
namespace TestAutomation.Pages;
|
|
||||||
|
|
||||||
public class DatabaseSinglePage : BasePage
|
|
||||||
{
|
|
||||||
public DatabaseSinglePage(Website website) : base(website)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private IWebElement EntityName => Website.Find("entityName");
|
|
||||||
private IWebElement EntityHealth => Website.Find("entityHealth");
|
|
||||||
|
|
||||||
private IWebElement InvalidSearch => Website.Find("invalidSearch");
|
|
||||||
private IWebElement ValidSearch => Website.Find("validSearch");
|
|
||||||
|
|
||||||
public override string Url { get; set; } = "database";
|
|
||||||
|
|
||||||
|
|
||||||
public DatabaseSinglePage GetEntityName(out string result)
|
|
||||||
{
|
|
||||||
result = EntityName.Text;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DatabaseSinglePage GetEntityHealth(out string result)
|
|
||||||
{
|
|
||||||
result = EntityHealth.Text;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DatabaseSinglePage GetInvalidSearch(out string result)
|
|
||||||
{
|
|
||||||
result = InvalidSearch.Text;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DatabaseSinglePage GetValidSearch(out string result)
|
|
||||||
{
|
|
||||||
result = ValidSearch.Text;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DatabaseSinglePage Goto(string searchText)
|
|
||||||
{
|
|
||||||
Website.Goto($"{Url}/{searchText}");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
using TestAutomation.Utils;
|
|
||||||
|
|
||||||
namespace TestAutomation.Pages;
|
|
||||||
|
|
||||||
public class HarassCalculatorPage : BasePage
|
|
||||||
{
|
|
||||||
public HarassCalculatorPage(Website website) : base(website)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private IWebElement NumberOfWorkersLostToHarass => Website.Find("numberOfWorkersLostToHarass");
|
|
||||||
private IWebElement NumberOfTownHallsExisting => Website.Find("numberOfTownHallsExisting");
|
|
||||||
private IList<IWebElement> OnTownHallTravelTimes => Website.FindChildren("numberOfTownHallTravelTimes", "input");
|
|
||||||
private int TotalAlloyHarassment => Website.FindInt("totalAlloyHarassment");
|
|
||||||
private int WorkerReplacementCost => Website.FindInt("workerReplacementCost");
|
|
||||||
private int DelayedMiningCost => Website.FindInt("delayedMiningCost");
|
|
||||||
private int AverageTravelTime => Website.FindInt("getAverageTravelTime");
|
|
||||||
|
|
||||||
private int ExampleTotalAlloyLoss => Website.FindInt("exampleTotalAlloyLoss");
|
|
||||||
private int ExampleWorkerCost => Website.FindInt("exampleWorkerCost");
|
|
||||||
private int ExampleMiningTimeCost => Website.FindInt("exampleMiningTimeCost");
|
|
||||||
private int ExampleTotalAlloyLossDifference => Website.FindInt("exampleTotalAlloyLossDifference");
|
|
||||||
private int ExampleTotalAlloyLossAccurate => Website.FindInt("exampleTotalAlloyLossAccurate");
|
|
||||||
private int ExampleTotalAlloyLossAccurateDifference => Website.FindInt("exampleTotalAlloyLossAccurateDifference");
|
|
||||||
|
|
||||||
public override string Url { get; set; } = "harass-calculator";
|
|
||||||
|
|
||||||
public HarassCalculatorPage SetWorkersLostToHarass(int number)
|
|
||||||
{
|
|
||||||
Website.EnterInput(NumberOfWorkersLostToHarass, number);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HarassCalculatorPage SetNumberOfTownHallsExisting(int number)
|
|
||||||
{
|
|
||||||
Website.EnterInput(NumberOfTownHallsExisting, number);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HarassCalculatorPage SetTownHallTravelTime(int forTownHall, int number)
|
|
||||||
{
|
|
||||||
Website.EnterInput(OnTownHallTravelTimes[forTownHall], number);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HarassCalculatorPage GetTotalAlloyHarassment(out int result)
|
|
||||||
{
|
|
||||||
result = TotalAlloyHarassment;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public HarassCalculatorPage GetExampleTotalAlloyLoss(out int result)
|
|
||||||
{
|
|
||||||
result = ExampleTotalAlloyLoss;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HarassCalculatorPage GetExampleWorkerCost(out int result)
|
|
||||||
{
|
|
||||||
result = ExampleWorkerCost;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HarassCalculatorPage GetExampleMiningTimeCost(out int result)
|
|
||||||
{
|
|
||||||
result = ExampleMiningTimeCost;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HarassCalculatorPage GetExampleTotalAlloyLossAccurate(out int result)
|
|
||||||
{
|
|
||||||
result = ExampleTotalAlloyLossAccurate;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HarassCalculatorPage GetExampleTotalAlloyLossDifference(out int result)
|
|
||||||
{
|
|
||||||
result = ExampleTotalAlloyLossDifference;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HarassCalculatorPage GetExampleTotalAlloyLossAccurateDifference(out int result)
|
|
||||||
{
|
|
||||||
result = ExampleTotalAlloyLossAccurateDifference;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected HarassCalculatorPage NavigateTo()
|
|
||||||
{
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HarassCalculatorPage Goto()
|
|
||||||
{
|
|
||||||
Website.Goto(Url);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using TestAutomation.Utils;
|
|
||||||
|
|
||||||
namespace TestAutomation.Shared;
|
|
||||||
|
|
||||||
public abstract class BaseElement
|
|
||||||
{
|
|
||||||
protected readonly Website Website;
|
|
||||||
|
|
||||||
protected BaseElement(Website website)
|
|
||||||
{
|
|
||||||
Website = website;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
using TestAutomation.Utils;
|
|
||||||
|
|
||||||
namespace TestAutomation.Shared;
|
|
||||||
|
|
||||||
public class NavigationBar : BaseElement
|
|
||||||
{
|
|
||||||
public NavigationBar(Website website) : base(website)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private IWebElement HomeLink => Website.FindScreenSpecific("homeLink");
|
|
||||||
private IWebElement SearchButton => Website.FindScreenSpecific("searchButton");
|
|
||||||
|
|
||||||
public NavigationBar ClickHomeLink()
|
|
||||||
{
|
|
||||||
Website.Click(HomeLink);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebsiteSearchDialog ClickSearchButton()
|
|
||||||
{
|
|
||||||
Website.Click(SearchButton);
|
|
||||||
return Website.WebsiteSearchDialog;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using TestAutomation.Utils;
|
|
||||||
|
|
||||||
namespace TestAutomation.Shared;
|
|
||||||
|
|
||||||
public class WebsiteSearchDialog : BaseElement
|
|
||||||
{
|
|
||||||
public WebsiteSearchDialog(Website website) : base(website)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public IWebElement SearchBackground => Website.Find("searchBackground");
|
|
||||||
|
|
||||||
public IWebElement SearchInput => Website.Find("searchInput");
|
|
||||||
|
|
||||||
public NavigationBar CloseDialog()
|
|
||||||
{
|
|
||||||
Website.ClickTopLeft();
|
|
||||||
return Website.NavigationBar;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebsiteSearchDialog Search(string throne)
|
|
||||||
{
|
|
||||||
Website.EnterInput(SearchInput, throne);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DatabaseSinglePage SelectSearchEntity(string throne)
|
|
||||||
{
|
|
||||||
Website.Click(Website.FindButtonWithLabel(throne));
|
|
||||||
return Website.DatabaseSinglePage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Discord.Net.Webhook" Version="3.6.0"/>
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.14"/>
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0"/>
|
|
||||||
<PackageReference Include="NUnit" Version="3.13.2"/>
|
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.0"/>
|
|
||||||
<PackageReference Include="NUnit.Analyzers" Version="3.2.0"/>
|
|
||||||
<PackageReference Include="coverlet.collector" Version="3.1.0"/>
|
|
||||||
<PackageReference Include="Selenium.WebDriver" Version="4.1.0"/>
|
|
||||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="101.0.4951.4100"/>
|
|
||||||
<PackageReference Include="Selenium.WebDriver.GeckoDriver" Version="0.31.0"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Pages\"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
using TestAutomation.Utils;
|
|
||||||
|
|
||||||
namespace TestAutomation;
|
|
||||||
|
|
||||||
[TestFixture]
|
|
||||||
public class TestHarassCalculator : BaseTest
|
|
||||||
{
|
|
||||||
[SetUp]
|
|
||||||
public void SetUp()
|
|
||||||
{
|
|
||||||
TestReport.CreateTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
[TearDown]
|
|
||||||
public void TearDown()
|
|
||||||
{
|
|
||||||
TestReport.ThrowErrors();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void CalculatorInput()
|
|
||||||
{
|
|
||||||
var expectedTotalAlloyHarassment = 240;
|
|
||||||
|
|
||||||
Website.HarassCalculatorPage
|
|
||||||
.Goto()
|
|
||||||
.SetWorkersLostToHarass(3)
|
|
||||||
.SetNumberOfTownHallsExisting(2)
|
|
||||||
.SetTownHallTravelTime(0, 30)
|
|
||||||
.GetTotalAlloyHarassment(out var foundTotalAlloyHarassment);
|
|
||||||
|
|
||||||
TestReport.CheckPassed(expectedTotalAlloyHarassment.Equals(foundTotalAlloyHarassment),
|
|
||||||
TestMessage.CreateFailedMessage($"expectTotalAlloyHarassment of {expectedTotalAlloyHarassment} " +
|
|
||||||
"does not equal " +
|
|
||||||
$"foundTotalAlloyHarassment of {foundTotalAlloyHarassment} "));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void CalculatedExampleInformation()
|
|
||||||
{
|
|
||||||
var expectedExampleTotalAlloyLoss = 720;
|
|
||||||
var expectedExampleWorkerCost = 300;
|
|
||||||
var expectedExampleMiningTimeCost = 420;
|
|
||||||
var expectedExampleTotalAlloyLossDifference = 300;
|
|
||||||
var expectedExampleTotalAlloyLossAccurate = 450;
|
|
||||||
var expectedExampleTotalAlloyLossAccurateDifference = 270;
|
|
||||||
|
|
||||||
Website.HarassCalculatorPage
|
|
||||||
.Goto()
|
|
||||||
.GetExampleTotalAlloyLoss(out var foundTotalAlloyLoss)
|
|
||||||
.GetExampleWorkerCost(out var foundExampleWorkerCost)
|
|
||||||
.GetExampleMiningTimeCost(out var foundExampleMiningTimeCost)
|
|
||||||
.GetExampleTotalAlloyLossAccurate(out var foundExampleTotalAlloyLossAccurate)
|
|
||||||
.GetExampleTotalAlloyLossDifference(out var foundGetExampleTotalAlloyLossDifference)
|
|
||||||
.GetExampleTotalAlloyLossAccurateDifference(out var foundExampleTotalAlloyLossAccurateDifference);
|
|
||||||
|
|
||||||
TestReport.CheckPassed(expectedExampleTotalAlloyLoss.Equals(foundTotalAlloyLoss),
|
|
||||||
TestMessage.CreateFailedMessage($"expectedExampleTotalAlloyLoss of {expectedExampleTotalAlloyLoss} " +
|
|
||||||
"does not equal " +
|
|
||||||
$"foundTotalAlloyLoss of {foundTotalAlloyLoss} "));
|
|
||||||
|
|
||||||
TestReport.CheckPassed(expectedExampleWorkerCost.Equals(foundExampleWorkerCost),
|
|
||||||
TestMessage.CreateFailedMessage($"expectedExampleWorkerCost of {expectedExampleWorkerCost} " +
|
|
||||||
"does not equal " +
|
|
||||||
$"foundExampleWorkerCost of {foundExampleWorkerCost} "));
|
|
||||||
|
|
||||||
|
|
||||||
TestReport.CheckPassed(expectedExampleMiningTimeCost.Equals(foundExampleMiningTimeCost),
|
|
||||||
TestMessage.CreateFailedMessage($"expectedExampleMiningTimeCost of {expectedExampleMiningTimeCost} " +
|
|
||||||
"does not equal " +
|
|
||||||
$"foundExampleMiningTimeCost of {foundExampleMiningTimeCost} "));
|
|
||||||
|
|
||||||
|
|
||||||
TestReport.CheckPassed(expectedExampleTotalAlloyLossAccurate.Equals(foundExampleTotalAlloyLossAccurate),
|
|
||||||
TestMessage.CreateFailedMessage(
|
|
||||||
$"expectedExampleTotalAlloyLossAccurate of {expectedExampleTotalAlloyLossAccurate} " +
|
|
||||||
"does not equal " +
|
|
||||||
$"foundExampleTotalAlloyLossAccurate of {foundExampleTotalAlloyLossAccurate} "));
|
|
||||||
|
|
||||||
|
|
||||||
TestReport.CheckPassed(expectedExampleTotalAlloyLossDifference.Equals(foundGetExampleTotalAlloyLossDifference),
|
|
||||||
TestMessage.CreateFailedMessage(
|
|
||||||
$"expectedExampleTotalAlloyLossDifference of {expectedExampleTotalAlloyLossDifference} " +
|
|
||||||
"does not equal " +
|
|
||||||
$"foundGetExampleTotalAlloyLossDifference of {foundGetExampleTotalAlloyLossDifference} "));
|
|
||||||
|
|
||||||
|
|
||||||
TestReport.CheckPassed(
|
|
||||||
expectedExampleTotalAlloyLossAccurateDifference.Equals(foundExampleTotalAlloyLossAccurateDifference),
|
|
||||||
TestMessage.CreateFailedMessage(
|
|
||||||
$"expectedExampleTotalAlloyLossAccurateDifference of {expectedExampleTotalAlloyLossAccurateDifference} " +
|
|
||||||
"does not equal " +
|
|
||||||
$"foundExampleTotalAlloyLossAccurateDifference of {foundExampleTotalAlloyLossAccurateDifference} "));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
namespace TestAutomation;
|
|
||||||
|
|
||||||
[TestFixture]
|
|
||||||
public class TestLinks : BaseTest
|
|
||||||
{
|
|
||||||
[SetUp]
|
|
||||||
public void SetUp()
|
|
||||||
{
|
|
||||||
TestReport.CreateTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
[TearDown]
|
|
||||||
public void TearDown()
|
|
||||||
{
|
|
||||||
TestReport.ThrowErrors();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void VerifyPageLinks()
|
|
||||||
{
|
|
||||||
Website.HarassCalculatorPage.Goto();
|
|
||||||
TestReport.VerifyLinks(Website.HarassCalculatorPage).Wait();
|
|
||||||
|
|
||||||
Website.DatabasePage.Goto();
|
|
||||||
TestReport.VerifyLinks(Website.DatabasePage).Wait();
|
|
||||||
|
|
||||||
Website.DatabaseSinglePage.Goto("throne");
|
|
||||||
TestReport.VerifyLinks(Website.DatabaseSinglePage).Wait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
using TestAutomation.Utils;
|
|
||||||
|
|
||||||
namespace TestAutomation;
|
|
||||||
|
|
||||||
[TestFixture]
|
|
||||||
public class TestSearchFeatures : BaseTest
|
|
||||||
{
|
|
||||||
[SetUp]
|
|
||||||
public void SetUp()
|
|
||||||
{
|
|
||||||
TestReport.CreateTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
[TearDown]
|
|
||||||
public void TearDown()
|
|
||||||
{
|
|
||||||
TestReport.ThrowErrors();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void DesktopOpenCloseSearchDialog()
|
|
||||||
{
|
|
||||||
Website
|
|
||||||
.Goto()
|
|
||||||
.NavigationBar
|
|
||||||
.ClickSearchButton()
|
|
||||||
.CloseDialog()
|
|
||||||
.ClickHomeLink();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void DesktopSearchForThrone()
|
|
||||||
{
|
|
||||||
Website
|
|
||||||
.Goto()
|
|
||||||
.NavigationBar.ClickSearchButton()
|
|
||||||
.Search("Throne")
|
|
||||||
.SelectSearchEntity("Throne")
|
|
||||||
.GetEntityName(out var name)
|
|
||||||
.GetEntityHealth(out var health);
|
|
||||||
|
|
||||||
TestReport.CheckPassed(name.Equals("Throne"),
|
|
||||||
new TestMessage { Description = "Couldn't find Throne via search." });
|
|
||||||
TestReport.CheckPassed(!health.Trim().Equals(""),
|
|
||||||
new TestMessage { Description = "Throne has no visible health!" });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void DesktopFilterForThrone()
|
|
||||||
{
|
|
||||||
Website.DatabasePage
|
|
||||||
.Goto()
|
|
||||||
.FilterName("Throne")
|
|
||||||
.GetEntityName(0, out var name);
|
|
||||||
|
|
||||||
TestReport.CheckPassed(name.Equals("Throne"),
|
|
||||||
new TestMessage { Description = "Couldn't find Throne via filter." });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void SeeThroneByDefault()
|
|
||||||
{
|
|
||||||
Website.DatabasePage
|
|
||||||
.Goto()
|
|
||||||
.GetEntityName("army", "throne", out var name);
|
|
||||||
|
|
||||||
TestReport.CheckPassed(name.Equals("Throne"),
|
|
||||||
new TestMessage { Description = "Couldn't find Throne on the page by default." });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void DirectLinkNotThroneFailure()
|
|
||||||
{
|
|
||||||
Website.DatabaseSinglePage
|
|
||||||
.Goto("not throne")
|
|
||||||
.GetInvalidSearch(out var invalidSearch)
|
|
||||||
.GetValidSearch(out var validSearch);
|
|
||||||
|
|
||||||
TestReport.CheckPassed(invalidSearch.Equals("not throne"),
|
|
||||||
new TestMessage { Description = "Couldn't find invalid search text on the page." });
|
|
||||||
TestReport.CheckPassed(validSearch.Equals("Throne"),
|
|
||||||
new TestMessage { Description = "Couldn't find valid search text on the page." });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace TestAutomation.Utils;
|
|
||||||
|
|
||||||
public class Test
|
|
||||||
{
|
|
||||||
public string Name { get; set; } = "Name...";
|
|
||||||
public bool Result { get; set; } = true;
|
|
||||||
public IList<TestMessage> Messages { get; set; } = new List<TestMessage>();
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace TestAutomation.Utils;
|
|
||||||
|
|
||||||
public class TestMessage
|
|
||||||
{
|
|
||||||
public string Title { get; set; } = "Name...";
|
|
||||||
public string Description { get; set; } = "";
|
|
||||||
public string Color { get; set; } = "FFFFFF";
|
|
||||||
|
|
||||||
public static TestMessage CreateFailedMessage(string description)
|
|
||||||
{
|
|
||||||
return new TestMessage { Title = "Check Failed", Description = description, Color = "FF0000" };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
using System.Globalization;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace TestAutomation.Utils;
|
|
||||||
|
|
||||||
public class TestReport
|
|
||||||
{
|
|
||||||
private List<Test> Tests { get; } = new();
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
public Test CreateTest()
|
|
||||||
{
|
|
||||||
Tests.Add(new Test
|
|
||||||
{
|
|
||||||
Name = TestContext.CurrentContext.Test.Name
|
|
||||||
});
|
|
||||||
return Tests.Last();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ThrowErrors()
|
|
||||||
{
|
|
||||||
if (!Tests.Last().Result)
|
|
||||||
{
|
|
||||||
var messages = string.Join("\n", Tests.Last().Messages.Select(x => x.Description).ToList());
|
|
||||||
|
|
||||||
throw new Exception(
|
|
||||||
$"{Tests.Last().Name} test failed with {Tests.Last().Messages.Count} messages.\n\n{messages}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task VerifyLinks(BasePage page)
|
|
||||||
{
|
|
||||||
foreach (var link in page.GetLinks())
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (link.StartsWith("mailto")) continue;
|
|
||||||
|
|
||||||
using var client = new HttpClient();
|
|
||||||
var response = await client.GetAsync(link);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
CheckPassed(false,
|
|
||||||
new TestMessage
|
|
||||||
{
|
|
||||||
Color = "red", Title = "Bad Link",
|
|
||||||
Description = $"{link} failed on page {page.Url} with status code {response.StatusCode}"
|
|
||||||
});
|
|
||||||
Console.WriteLine(response.StatusCode.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
CheckPassed(false,
|
|
||||||
new TestMessage
|
|
||||||
{
|
|
||||||
Color = "red", Title = "Bad Link",
|
|
||||||
Description = $"{link} failed on page {page.Url} with stacktrace {e.StackTrace}"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void CheckPassed(bool passed, TestMessage message)
|
|
||||||
{
|
|
||||||
if (passed) return;
|
|
||||||
Tests.Last().Result = false;
|
|
||||||
Tests.Last().Messages.Add(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool DidTestsPass()
|
|
||||||
{
|
|
||||||
foreach (var test in Tests)
|
|
||||||
{
|
|
||||||
if (test.Result) continue;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<object> GetMessages()
|
|
||||||
{
|
|
||||||
if (DidTestsPass())
|
|
||||||
return new List<object>
|
|
||||||
{
|
|
||||||
new
|
|
||||||
{
|
|
||||||
title = "Passed",
|
|
||||||
color = int.Parse("00FF00", NumberStyles.HexNumber),
|
|
||||||
description = $"All {Tests.Count} tests passed."
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var messageList = new List<object>();
|
|
||||||
foreach (var test in Tests)
|
|
||||||
foreach (var message in test.Messages)
|
|
||||||
messageList.Add(
|
|
||||||
new
|
|
||||||
{
|
|
||||||
title = message.Title,
|
|
||||||
color = int.Parse(message.Color, NumberStyles.HexNumber),
|
|
||||||
description = message.Description
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
return messageList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
global using NUnit.Framework;
|
|
||||||
global using OpenQA.Selenium;
|
|
||||||
global using OpenQA.Selenium.Firefox;
|
|
||||||
global using OpenQA.Selenium.Chrome;
|
|
||||||
global using TestAutomation.Pages;
|
|
||||||
global using OpenQA.Selenium.Support.UI;
|
|
||||||
global using OpenQA.Selenium.Support;
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
using System.Collections.ObjectModel;
|
|
||||||
using OpenQA.Selenium.Interactions;
|
|
||||||
using TestAutomation.Enums;
|
|
||||||
using TestAutomation.Shared;
|
|
||||||
|
|
||||||
namespace TestAutomation.Utils;
|
|
||||||
|
|
||||||
public class Website
|
|
||||||
{
|
|
||||||
public static readonly DeploymentType DeploymentType =
|
|
||||||
Environment.GetEnvironmentVariable("TEST_HOOK")!.Contains("localhost")
|
|
||||||
? DeploymentType.Local
|
|
||||||
: DeploymentType.Dev;
|
|
||||||
|
|
||||||
public static readonly string Url =
|
|
||||||
DeploymentType.Equals(DeploymentType.Dev)
|
|
||||||
? "https://calm-mud-04916b210.1.azurestaticapps.net"
|
|
||||||
: "https://localhost:7234";
|
|
||||||
|
|
||||||
public readonly ScreenType ScreenType = ScreenType.Desktop;
|
|
||||||
|
|
||||||
public Website(IWebDriver webDriver, TestReport testReport)
|
|
||||||
{
|
|
||||||
WebDriver = webDriver;
|
|
||||||
TestReport = testReport;
|
|
||||||
|
|
||||||
// Pages
|
|
||||||
HarassCalculatorPage = new HarassCalculatorPage(this);
|
|
||||||
DatabasePage = new DatabasePage(this);
|
|
||||||
DatabaseSinglePage = new DatabaseSinglePage(this);
|
|
||||||
|
|
||||||
// Navigation
|
|
||||||
NavigationBar = new NavigationBar(this);
|
|
||||||
|
|
||||||
// Dialogs
|
|
||||||
WebsiteSearchDialog = new WebsiteSearchDialog(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TestReport TestReport { get; set; }
|
|
||||||
|
|
||||||
public IWebDriver WebDriver { get; }
|
|
||||||
|
|
||||||
public HarassCalculatorPage HarassCalculatorPage { get; }
|
|
||||||
public DatabaseSinglePage DatabaseSinglePage { get; }
|
|
||||||
public DatabasePage DatabasePage { get; }
|
|
||||||
public NavigationBar NavigationBar { get; }
|
|
||||||
public WebsiteSearchDialog WebsiteSearchDialog { get; }
|
|
||||||
|
|
||||||
public IWebElement FindScreenSpecific(string byId)
|
|
||||||
{
|
|
||||||
var screenSpecificId = $"{ScreenType.ToString().ToLower()}-{byId}";
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return WebDriver.FindElement(By.Id(screenSpecificId));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
throw new Exception($"Couldn't find {screenSpecificId}. Element does not exist on current page. " +
|
|
||||||
"\n\nPerhaps an Id is missing.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public IWebElement Find(string byId, string withParentId)
|
|
||||||
{
|
|
||||||
IWebElement parent;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
parent = WebDriver.FindElement(By.Id(withParentId));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
throw new Exception($"Couldn't find parent {withParentId}. Element does not exist on current page. " +
|
|
||||||
"\n\nPerhaps an Id is missing.");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return parent.FindElement(By.Id(byId));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
throw new Exception($"Couldn't find {byId}. Element does not exist on current page. " +
|
|
||||||
"\n\nPerhaps an Id is missing.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IWebElement Find(string byId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return WebDriver.FindElement(By.Id(byId));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
throw new Exception($"Couldn't find {byId}. Element does not exist on current page. " +
|
|
||||||
"\n\nPerhaps an Id is missing.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyCollection<IWebElement> FindAll(string byId)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return WebDriver.FindElements(By.Id(byId));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
throw new Exception($"Couldn't find {byId}. Element does not exist on current page. " +
|
|
||||||
"\n\nPerhaps an Id is missing.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyCollection<IWebElement> FindAllWithTag(string tag)
|
|
||||||
{
|
|
||||||
return WebDriver.FindElements(By.TagName(tag));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyCollection<IWebElement> FindAllWithTag(IWebElement parent, string tag)
|
|
||||||
{
|
|
||||||
return parent.FindElements(By.TagName(tag));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public IWebElement FindButtonWithLabel(string label)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return WebDriver.FindElement(By.XPath($"//button[@label='{label}']"));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
throw new Exception($"Couldn't find with label: {label}. Element does not exist on current page. ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//@FindBy(xpath = "//div[@label='First Name']")
|
|
||||||
|
|
||||||
public IList<IWebElement> FindChildren(string ofId, string tagname)
|
|
||||||
{
|
|
||||||
return WebDriver.FindElements(By.CssSelector($"#{ofId} {tagname}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public string FindText(string byId)
|
|
||||||
{
|
|
||||||
return WebDriver.FindElement(By.Id(byId)).Text;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int FindInt(string byId)
|
|
||||||
{
|
|
||||||
return int.Parse(WebDriver.FindElement(By.Id(byId)).Text);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void ClickTopLeft()
|
|
||||||
{
|
|
||||||
new Actions(WebDriver)
|
|
||||||
.MoveByOffset(32, 32)
|
|
||||||
.Click()
|
|
||||||
.Perform();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IWebElement Click(IWebElement element)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
element.Click();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
throw new Exception($"Couldn't click on {element.GetDomProperty("id")}. ");
|
|
||||||
}
|
|
||||||
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public IWebElement EnterInput<T>(IWebElement element, T input)
|
|
||||||
{
|
|
||||||
element.Clear();
|
|
||||||
element.SendKeys(input!.ToString());
|
|
||||||
element.SendKeys(Keys.Enter);
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public IWebElement EnterInput<T>(string byId, T input)
|
|
||||||
{
|
|
||||||
var element = Find(byId);
|
|
||||||
element.Clear();
|
|
||||||
element.SendKeys(input!.ToString());
|
|
||||||
element.SendKeys(Keys.Enter);
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string GetLabel(string byId)
|
|
||||||
{
|
|
||||||
return Find(byId).Text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Website Goto()
|
|
||||||
{
|
|
||||||
WebDriver.Navigate().GoToUrl($"{Url}");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Goto(string path)
|
|
||||||
{
|
|
||||||
var url = $"{Url}/{path}";
|
|
||||||
|
|
||||||
WebDriver.Navigate().GoToUrl($"{url}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace TestAutomation;
|
|
||||||
|
|
||||||
[SetUpFixture]
|
|
||||||
public class Tests : BaseTest
|
|
||||||
{
|
|
||||||
[OneTimeSetUp]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
Website.Goto();
|
|
||||||
Website.WebDriver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(15);
|
|
||||||
}
|
|
||||||
|
|
||||||
[OneTimeTearDown]
|
|
||||||
public void TearDown()
|
|
||||||
{
|
|
||||||
Website.WebDriver.Quit();
|
|
||||||
|
|
||||||
var message = new
|
|
||||||
{
|
|
||||||
content = "Test Report " + DateTime.Now.ToString("dd/MM/yyyy"),
|
|
||||||
embeds = TestReport.GetMessages()
|
|
||||||
};
|
|
||||||
|
|
||||||
var content = new StringContent(JsonConvert.SerializeObject(message), Encoding.UTF8, "application/json");
|
|
||||||
|
|
||||||
if (Environment.GetEnvironmentVariable("TEST_HOOK") == null) return;
|
|
||||||
|
|
||||||
HttpClient.PostAsync(Environment.GetEnvironmentVariable("TEST_HOOK"), content).Wait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using NUnit.Framework;
|
||||||
|
using Tests.Helpers;
|
||||||
|
|
||||||
|
namespace Tests;
|
||||||
|
|
||||||
|
[SetUpFixture]
|
||||||
|
public class GlobalSetup
|
||||||
|
{
|
||||||
|
[OneTimeSetUp]
|
||||||
|
public async Task GlobalStart()
|
||||||
|
{
|
||||||
|
if (Environment.GetEnvironmentVariable("RUN_AGAINST_PRODUCTION") != "true")
|
||||||
|
{
|
||||||
|
await LocalServer.StartAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[OneTimeTearDown]
|
||||||
|
public void GlobalStop()
|
||||||
|
{
|
||||||
|
LocalServer.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace Tests.Helpers;
|
||||||
|
|
||||||
|
public static class LocalServer
|
||||||
|
{
|
||||||
|
private static Process? _process;
|
||||||
|
public static string? BaseUrl { get; private set; }
|
||||||
|
|
||||||
|
public static async Task StartAsync()
|
||||||
|
{
|
||||||
|
if (_process != null) return;
|
||||||
|
|
||||||
|
var root = FindProjectRoot();
|
||||||
|
var webProject = Path.Combine(root, "Web");
|
||||||
|
|
||||||
|
Console.WriteLine($"[DEBUG_LOG] Starting local server for project: {webProject}");
|
||||||
|
|
||||||
|
_process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "dotnet",
|
||||||
|
Arguments = $"run --project \"{webProject}\" --urls http://127.0.0.1:0",
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
WorkingDirectory = root
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var tcs = new TaskCompletionSource<string>();
|
||||||
|
|
||||||
|
_process.OutputDataReceived += (s, e) =>
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(e.Data)) return;
|
||||||
|
Console.WriteLine($"[SERVER] {e.Data}");
|
||||||
|
var match = Regex.Match(e.Data, @"Now listening on:\s+(http://127.0.0.1:\d+)");
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
tcs.TrySetResult(match.Groups[1].Value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_process.ErrorDataReceived += (s, e) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(e.Data))
|
||||||
|
Console.Error.WriteLine($"[SERVER ERROR] {e.Data}");
|
||||||
|
};
|
||||||
|
|
||||||
|
_process.Start();
|
||||||
|
_process.BeginOutputReadLine();
|
||||||
|
_process.BeginErrorReadLine();
|
||||||
|
|
||||||
|
// Wait for the URL to be parsed from output
|
||||||
|
BaseUrl = await Task.WhenAny(tcs.Task, Task.Delay(30000)) == tcs.Task
|
||||||
|
? tcs.Task.Result
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (BaseUrl == null)
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
throw new Exception("Timeout waiting for local server to start and provide a URL.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"[DEBUG_LOG] Local server started at: {BaseUrl}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Stop()
|
||||||
|
{
|
||||||
|
if (_process != null && !_process.HasExited)
|
||||||
|
{
|
||||||
|
Console.WriteLine("[DEBUG_LOG] Stopping local server...");
|
||||||
|
_process.Kill(true);
|
||||||
|
_process.Dispose();
|
||||||
|
_process = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FindProjectRoot()
|
||||||
|
{
|
||||||
|
var current = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
|
while (!string.IsNullOrEmpty(current) && !File.Exists(Path.Combine(current, "IGP.sln")))
|
||||||
|
{
|
||||||
|
var parent = Path.GetDirectoryName(current);
|
||||||
|
if (parent == current) break;
|
||||||
|
current = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(current) || !File.Exists(Path.Combine(current, "IGP.sln")))
|
||||||
|
{
|
||||||
|
// Fallback to searching up from current directory if BaseDirectory fails
|
||||||
|
current = Directory.GetCurrentDirectory();
|
||||||
|
while (!string.IsNullOrEmpty(current) && !File.Exists(Path.Combine(current, "IGP.sln")))
|
||||||
|
{
|
||||||
|
var parent = Path.GetDirectoryName(current);
|
||||||
|
if (parent == current) break;
|
||||||
|
current = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return current ?? throw new Exception("Could not find project root containing IGP.sln");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using Microsoft.Playwright;
|
||||||
|
using Tests.Pages;
|
||||||
|
using Tests.Shared;
|
||||||
|
|
||||||
|
namespace Tests.Helpers;
|
||||||
|
|
||||||
|
public class Website
|
||||||
|
{
|
||||||
|
public IPage Page { get; }
|
||||||
|
public bool RunAgainstProduction { get; }
|
||||||
|
public string BaseUrl { get; }
|
||||||
|
|
||||||
|
public Website(IPage page)
|
||||||
|
{
|
||||||
|
Page = page;
|
||||||
|
RunAgainstProduction = Environment.GetEnvironmentVariable("RUN_AGAINST_PRODUCTION") == "true";
|
||||||
|
|
||||||
|
BaseUrl = RunAgainstProduction ? "https://igpfanreference.ca" : (LocalServer.BaseUrl ?? "http://localhost:5234");
|
||||||
|
|
||||||
|
NavigationBar = new NavigationBar(this);
|
||||||
|
SearchDialog = new SearchDialog(this);
|
||||||
|
BuildCalculatorPage = new BuildCalculatorPage(this);
|
||||||
|
HarassCalculatorPage = new HarassCalculatorPage(this);
|
||||||
|
DatabasePage = new DatabasePage(this);
|
||||||
|
DatabaseSinglePage = new DatabaseSinglePage(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILocator Locator(string selector) => Page.Locator(selector);
|
||||||
|
public ILocator FindById(string id) => Page.Locator($"#{id}");
|
||||||
|
public NavigationBar NavigationBar { get; }
|
||||||
|
public SearchDialog SearchDialog { get; }
|
||||||
|
public BuildCalculatorPage BuildCalculatorPage { get; }
|
||||||
|
public HarassCalculatorPage HarassCalculatorPage { get; }
|
||||||
|
public DatabasePage DatabasePage { get; }
|
||||||
|
public DatabaseSinglePage DatabaseSinglePage { get; }
|
||||||
|
|
||||||
|
public async Task GotoAsync(string? path = null)
|
||||||
|
{
|
||||||
|
var url = path is null ? BaseUrl : $"{BaseUrl}/{path}";
|
||||||
|
await Page.GotoAsync(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ClickElementAsync(ILocator locator) => await locator.ClickAsync();
|
||||||
|
|
||||||
|
public async Task EnterInputAsync(ILocator locator, string value)
|
||||||
|
{
|
||||||
|
await locator.FillAsync(value);
|
||||||
|
await locator.PressAsync("Enter");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using Tests.Helpers;
|
||||||
|
|
||||||
|
namespace Tests.Pages;
|
||||||
|
|
||||||
|
public abstract class BasePage
|
||||||
|
{
|
||||||
|
protected Website Website { get; }
|
||||||
|
|
||||||
|
protected BasePage(Website website)
|
||||||
|
{
|
||||||
|
Website = website;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract string Url { get; }
|
||||||
|
|
||||||
|
public virtual async Task GotoAsync()
|
||||||
|
{
|
||||||
|
await Website.GotoAsync(Url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<string>> GetLinksAsync()
|
||||||
|
{
|
||||||
|
var content = Website.FindById("content");
|
||||||
|
var links = content.Locator("a");
|
||||||
|
var hrefs = await links.EvaluateAllAsync<string[]>("els => els.map(el => el.getAttribute('href')).filter(Boolean)");
|
||||||
|
return hrefs.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
namespace Tests.Pages.BuildCalculator;
|
||||||
|
|
||||||
|
public class ArmyComponent
|
||||||
|
{
|
||||||
|
private readonly Website _website;
|
||||||
|
public ArmyComponent(Website website) => _website = website;
|
||||||
|
|
||||||
|
public ILocator ArmyView => _website.Locator(".armyView");
|
||||||
|
|
||||||
|
public ILocator DisplayValue(string label) =>
|
||||||
|
_website.Locator(".displayContainer").Filter(new() { HasText = label }).Locator(".displayContent");
|
||||||
|
|
||||||
|
public ILocator ArmyCards => ArmyView.Locator(".armyCard");
|
||||||
|
|
||||||
|
public async Task<string> GetArmyCompletedAtAsync() =>
|
||||||
|
(await DisplayValue("Army Completed At").TextContentAsync())?.Trim() ?? "";
|
||||||
|
|
||||||
|
public async Task<string> GetArmyAttackingAtAsync() =>
|
||||||
|
(await DisplayValue("Army Attacking At").TextContentAsync())?.Trim() ?? "";
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<string>> GetArmyUnitNamesAsync()
|
||||||
|
{
|
||||||
|
var cards = await ArmyCards.AllAsync();
|
||||||
|
var names = new List<string>();
|
||||||
|
foreach (var card in cards)
|
||||||
|
{
|
||||||
|
var text = (await card.InnerTextAsync()).Trim();
|
||||||
|
var match = System.Text.RegularExpressions.Regex.Match(text, @"\d+x\s*(.+)");
|
||||||
|
names.Add(match.Success ? match.Groups[1].Value.Trim() : text);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<(string Name, int Count)>> GetArmyUnitCountsAsync()
|
||||||
|
{
|
||||||
|
var cards = await ArmyCards.AllAsync();
|
||||||
|
var counts = new List<(string, int)>();
|
||||||
|
foreach (var card in cards)
|
||||||
|
{
|
||||||
|
var countEl = card.Locator(".armyCount");
|
||||||
|
var nameEl = card.Locator("div").Last;
|
||||||
|
var count = (await countEl.TextContentAsync())?.Replace("x", "").Trim() ?? "0";
|
||||||
|
var name = (await nameEl.TextContentAsync())?.Trim() ?? "";
|
||||||
|
counts.Add((name, int.Parse(count)));
|
||||||
|
}
|
||||||
|
return counts;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
namespace Tests.Pages.BuildCalculator;
|
||||||
|
|
||||||
|
public class BankComponent
|
||||||
|
{
|
||||||
|
private readonly Website _website;
|
||||||
|
public BankComponent(Website website) => _website = website;
|
||||||
|
|
||||||
|
public ILocator BankContainer => _website.Locator(".bankContainer");
|
||||||
|
|
||||||
|
public ILocator DisplayValue(string label) =>
|
||||||
|
BankContainer.Locator(".displayContainer").Filter(new() { HasText = label }).Locator(".displayContent");
|
||||||
|
|
||||||
|
public async Task<string> GetTimeAsync() => (await DisplayValue("Time").TextContentAsync())?.Trim() ?? "";
|
||||||
|
public async Task<string> GetAlloyAsync() => (await DisplayValue("Alloy").TextContentAsync())?.Trim() ?? "";
|
||||||
|
public async Task<string> GetEtherAsync() => (await DisplayValue("Ether").TextContentAsync())?.Trim() ?? "";
|
||||||
|
public async Task<string> GetPyreAsync() => (await DisplayValue("Pyre").TextContentAsync())?.Trim() ?? "";
|
||||||
|
public async Task<string> GetSupplyAsync() => (await DisplayValue("Supply").TextContentAsync())?.Trim() ?? "";
|
||||||
|
|
||||||
|
public async Task<string> GetWorkerCountAsync() =>
|
||||||
|
(await BankContainer.Locator(".workerText").Locator(".displayContent").Nth(0).TextContentAsync())?.Trim() ?? "";
|
||||||
|
|
||||||
|
public async Task<string> GetBusyWorkerCountAsync() =>
|
||||||
|
(await BankContainer.Locator(".workerText").Locator(".displayContent").Nth(1).TextContentAsync())?.Trim() ?? "";
|
||||||
|
|
||||||
|
public async Task<string> GetCreatingWorkerCountAsync() =>
|
||||||
|
(await BankContainer.Locator(".workerText").Locator(".displayContent").Nth(2).TextContentAsync())?.Trim() ?? "";
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
namespace Tests.Pages.BuildCalculator;
|
||||||
|
|
||||||
|
public class BuildChartComponent
|
||||||
|
{
|
||||||
|
private readonly Website _website;
|
||||||
|
public BuildChartComponent(Website website) => _website = website;
|
||||||
|
|
||||||
|
public ILocator ChartsContainer => _website.Locator(".chartsContainer");
|
||||||
|
|
||||||
|
public ILocator DisplayValue(string label) =>
|
||||||
|
_website.Locator(".displayContainer").Filter(new() { HasText = label }).Locator(".displayContent");
|
||||||
|
|
||||||
|
public async Task<string> GetHighestAlloyAsync() => (await DisplayValue("Highest Alloy").TextContentAsync())?.Trim() ?? "";
|
||||||
|
public async Task<string> GetHighestEtherAsync() => (await DisplayValue("Highest Ether").TextContentAsync())?.Trim() ?? "";
|
||||||
|
public async Task<string> GetHighestPyreAsync() => (await DisplayValue("Highest Pyre").TextContentAsync())?.Trim() ?? "";
|
||||||
|
public async Task<string> GetHighestArmyAsync() => (await DisplayValue("Highest Army").TextContentAsync())?.Trim() ?? "";
|
||||||
|
|
||||||
|
public async Task<int> GetChartCountAsync() =>
|
||||||
|
await ChartsContainer.Locator("> div").CountAsync();
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Tests.Pages.BuildCalculator;
|
||||||
|
|
||||||
|
public class BuildOrderComponent
|
||||||
|
{
|
||||||
|
private readonly Website _website;
|
||||||
|
public BuildOrderComponent(Website website) => _website = website;
|
||||||
|
|
||||||
|
public ILocator JsonTextarea => _website.Locator("textarea");
|
||||||
|
|
||||||
|
public async Task<string> GetJsonDataAsync() =>
|
||||||
|
await JsonTextarea.InputValueAsync();
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
namespace Tests.Pages.BuildCalculator;
|
||||||
|
|
||||||
|
public class EntityClickViewComponent
|
||||||
|
{
|
||||||
|
private readonly Website _website;
|
||||||
|
public EntityClickViewComponent(Website website) => _website = website;
|
||||||
|
|
||||||
|
public ILocator EntityClickView => _website.Locator(".entityClickView");
|
||||||
|
|
||||||
|
public async Task<string?> GetEntityNameAsync()
|
||||||
|
{
|
||||||
|
var el = EntityClickView.Locator("#entityName");
|
||||||
|
if (await el.CountAsync() == 0) return null;
|
||||||
|
return (await el.TextContentAsync())?.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string?> GetEntityHealthAsync()
|
||||||
|
{
|
||||||
|
var healthText = EntityClickView.Locator("div").Filter(new() { HasTextRegex = new System.Text.RegularExpressions.Regex("Health", System.Text.RegularExpressions.RegexOptions.IgnoreCase) }).First;
|
||||||
|
if (await healthText.CountAsync() == 0) return null;
|
||||||
|
var text = (await healthText.TextContentAsync()) ?? "";
|
||||||
|
var match = System.Text.RegularExpressions.Regex.Match(text, @"(\d+)");
|
||||||
|
return match.Success ? match.Groups[1].Value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ClickDetailedViewAsync() =>
|
||||||
|
await EntityClickView.Locator("button").Filter(new() { HasText = "Detailed" }).ClickAsync();
|
||||||
|
|
||||||
|
public async Task ClickPlainViewAsync() =>
|
||||||
|
await EntityClickView.Locator("button").Filter(new() { HasText = "Plain" }).ClickAsync();
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
namespace Tests.Pages.BuildCalculator;
|
||||||
|
|
||||||
|
public class FilterComponent
|
||||||
|
{
|
||||||
|
private readonly Website _website;
|
||||||
|
public FilterComponent(Website website) => _website = website;
|
||||||
|
|
||||||
|
private ILocator FactionSelect =>
|
||||||
|
_website.Locator("select").Filter(new() { Has = _website.Locator("option:has-text('Aru'), option:has-text(\"Q'Rath\")") });
|
||||||
|
|
||||||
|
private ILocator ImmortalSelect =>
|
||||||
|
_website.Locator("select").Filter(new() { Has = _website.Locator("option:has-text('Orzum'), option:has-text('Ajari'), option:has-text('Atzlan'), option:has-text('Mala'), option:has-text('Xol')") });
|
||||||
|
|
||||||
|
public async Task SelectFactionAsync(string faction) =>
|
||||||
|
await FactionSelect.SelectOptionAsync(faction);
|
||||||
|
|
||||||
|
public async Task SelectImmortalAsync(string immortal) =>
|
||||||
|
await ImmortalSelect.SelectOptionAsync(immortal);
|
||||||
|
|
||||||
|
public async Task<string> GetSelectedFactionAsync() =>
|
||||||
|
await FactionSelect.InputValueAsync();
|
||||||
|
|
||||||
|
public async Task<string> GetSelectedImmortalAsync() =>
|
||||||
|
await ImmortalSelect.InputValueAsync();
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<string>> GetAvailableImmortalsAsync() =>
|
||||||
|
await ImmortalSelect.Locator("option").AllTextContentsAsync();
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
namespace Tests.Pages.BuildCalculator;
|
||||||
|
|
||||||
|
public class HighlightsComponent
|
||||||
|
{
|
||||||
|
private readonly Website _website;
|
||||||
|
public HighlightsComponent(Website website) => _website = website;
|
||||||
|
|
||||||
|
public ILocator HighlightsContainer => _website.Locator(".highlightsContainer");
|
||||||
|
public ILocator RequestedColumn => HighlightsContainer.Locator("div").Filter(new() { HasText = "Requested" }).Locator("+ div");
|
||||||
|
public ILocator FinishedColumn => HighlightsContainer.Locator("div").Filter(new() { HasText = "Finished" }).Locator("+ div");
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<string>> GetRequestedItemsAsync() =>
|
||||||
|
await GetHighlightItemsAsync();
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<string>> GetFinishedItemsAsync() =>
|
||||||
|
await GetHighlightItemsAsync();
|
||||||
|
|
||||||
|
private async Task<IReadOnlyList<string>> GetHighlightItemsAsync()
|
||||||
|
{
|
||||||
|
var items = await _website.Locator(".highlightsContainer").Locator("div").Filter(new() { HasTextRegex = new System.Text.RegularExpressions.Regex(@"^\d+\s*\|") }).AllAsync();
|
||||||
|
var result = new List<string>();
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
var text = (await item.TextContentAsync())?.Trim();
|
||||||
|
if (text is not null) result.Add(text);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
namespace Tests.Pages.BuildCalculator;
|
||||||
|
|
||||||
|
public class HotkeyViewerComponent
|
||||||
|
{
|
||||||
|
private readonly Website _website;
|
||||||
|
public HotkeyViewerComponent(Website website) => _website = website;
|
||||||
|
|
||||||
|
public ILocator KeyContainer => _website.Locator(".keyContainer");
|
||||||
|
|
||||||
|
public async Task<ILocator?> FindKeyButtonAsync(string keyLabel)
|
||||||
|
{
|
||||||
|
var upper = keyLabel.ToUpperInvariant();
|
||||||
|
var buttons = KeyContainer.Locator("> div > div");
|
||||||
|
var count = await buttons.CountAsync();
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var btn = buttons.Nth(i);
|
||||||
|
var text = (await btn.TextContentAsync())?.Trim().ToUpperInvariant() ?? "";
|
||||||
|
if (text.StartsWith(upper)) return btn;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ClickKeyAsync(string keyText)
|
||||||
|
{
|
||||||
|
var btn = await FindKeyButtonAsync(keyText);
|
||||||
|
if (btn is null) throw new InvalidOperationException($"Key \"{keyText}\" not found");
|
||||||
|
await btn.ClickAsync(new() { Force = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string?> GetFirstEntityNameAsync(string keyText)
|
||||||
|
{
|
||||||
|
var btn = await FindKeyButtonAsync(keyText);
|
||||||
|
if (btn is null) return null;
|
||||||
|
var entities = btn.Locator("> div");
|
||||||
|
if (await entities.CountAsync() == 0) return null;
|
||||||
|
return (await entities.First.TextContentAsync())?.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<string>> GetEntityNamesOnKeyAsync(string keyText)
|
||||||
|
{
|
||||||
|
var btn = await FindKeyButtonAsync(keyText);
|
||||||
|
if (btn is null) return Array.Empty<string>();
|
||||||
|
var entities = btn.Locator("> div");
|
||||||
|
var count = await entities.CountAsync();
|
||||||
|
var names = new List<string>();
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var text = (await entities.Nth(i).TextContentAsync())?.Trim();
|
||||||
|
if (!string.IsNullOrEmpty(text)) names.Add(text);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
namespace Tests.Pages.BuildCalculator;
|
||||||
|
|
||||||
|
public class OptionsComponent
|
||||||
|
{
|
||||||
|
private readonly Website _website;
|
||||||
|
public OptionsComponent(Website website) => _website = website;
|
||||||
|
|
||||||
|
private ILocator FormNumberInput(string label) =>
|
||||||
|
_website.Locator(".formNumberContainer").Filter(new() { HasText = label }).Locator("input[type='number']");
|
||||||
|
|
||||||
|
private ILocator ButtonWithLabel(string label) =>
|
||||||
|
_website.Locator("button").Filter(new() { HasText = label });
|
||||||
|
|
||||||
|
public ILocator BuildingInputDelayInput => FormNumberInput("Building Input Delay");
|
||||||
|
public ILocator WaitTimeInput => FormNumberInput("Wait Time");
|
||||||
|
public ILocator WaitToInput => FormNumberInput("Wait To");
|
||||||
|
public ILocator AddWaitButton => ButtonWithLabel("Add Wait").First;
|
||||||
|
public ILocator AddWaitToButton => ButtonWithLabel("Add Wait").Last;
|
||||||
|
|
||||||
|
public async Task SetBuildingInputDelayAsync(int value)
|
||||||
|
{
|
||||||
|
await BuildingInputDelayInput.FillAsync(value.ToString());
|
||||||
|
await BuildingInputDelayInput.PressAsync("Enter");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetWaitTimeAsync(int value) =>
|
||||||
|
await WaitTimeInput.FillAsync(value.ToString());
|
||||||
|
|
||||||
|
public async Task SetWaitToAsync(int value) =>
|
||||||
|
await WaitToInput.FillAsync(value.ToString());
|
||||||
|
|
||||||
|
public async Task ClickAddWaitAsync() => await AddWaitButton.ClickAsync();
|
||||||
|
public async Task ClickAddWaitToAsync() => await AddWaitToButton.ClickAsync();
|
||||||
|
|
||||||
|
public async Task<string> GetBuildingInputDelayAsync() => await BuildingInputDelayInput.InputValueAsync();
|
||||||
|
public async Task<string> GetWaitTimeAsync() => await WaitTimeInput.InputValueAsync();
|
||||||
|
public async Task<string> GetWaitToAsync() => await WaitToInput.InputValueAsync();
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
namespace Tests.Pages.BuildCalculator;
|
||||||
|
|
||||||
|
public class TimelineComponent
|
||||||
|
{
|
||||||
|
private readonly Website _website;
|
||||||
|
public TimelineComponent(Website website) => _website = website;
|
||||||
|
|
||||||
|
public ILocator Container =>
|
||||||
|
_website.Locator(".calculatorGrid > div").Filter(new() { HasText = "Timeline highlights" });
|
||||||
|
|
||||||
|
public async Task<bool> ContainsEntityAsync(string name)
|
||||||
|
{
|
||||||
|
var text = (await Container.TextContentAsync()) ?? "";
|
||||||
|
return text.Contains(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
namespace Tests.Pages.BuildCalculator;
|
||||||
|
|
||||||
|
public class TimingComponent
|
||||||
|
{
|
||||||
|
private readonly Website _website;
|
||||||
|
public TimingComponent(Website website) => _website = website;
|
||||||
|
|
||||||
|
private ILocator FormNumberInput(string label) =>
|
||||||
|
_website.Locator(".formNumberContainer").Filter(new() { HasText = label }).Locator("input[type='number']");
|
||||||
|
|
||||||
|
public ILocator AttackTimeInput => FormNumberInput("Attack Time");
|
||||||
|
public ILocator TravelTimeInput => FormNumberInput("Travel Time");
|
||||||
|
|
||||||
|
public async Task SetAttackTimeAsync(int value)
|
||||||
|
{
|
||||||
|
await AttackTimeInput.FillAsync(value.ToString());
|
||||||
|
await AttackTimeInput.PressAsync("Enter");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetTravelTimeAsync(int value)
|
||||||
|
{
|
||||||
|
await TravelTimeInput.FillAsync(value.ToString());
|
||||||
|
await TravelTimeInput.PressAsync("Enter");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetAttackTimeAsync() => await AttackTimeInput.InputValueAsync();
|
||||||
|
public async Task<string> GetTravelTimeAsync() => await TravelTimeInput.InputValueAsync();
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using Tests.Pages.BuildCalculator;
|
||||||
|
using Tests.Shared;
|
||||||
|
|
||||||
|
namespace Tests.Pages;
|
||||||
|
|
||||||
|
public class BuildCalculatorPage : BasePage
|
||||||
|
{
|
||||||
|
public BuildCalculatorPage(Website website) : base(website)
|
||||||
|
{
|
||||||
|
Timing = new TimingComponent(website);
|
||||||
|
Filter = new FilterComponent(website);
|
||||||
|
Options = new OptionsComponent(website);
|
||||||
|
Bank = new BankComponent(website);
|
||||||
|
Army = new ArmyComponent(website);
|
||||||
|
Highlights = new HighlightsComponent(website);
|
||||||
|
BuildOrder = new BuildOrderComponent(website);
|
||||||
|
Timeline = new TimelineComponent(website);
|
||||||
|
Hotkeys = new HotkeyViewerComponent(website);
|
||||||
|
EntityView = new EntityClickViewComponent(website);
|
||||||
|
Chart = new BuildChartComponent(website);
|
||||||
|
Toast = new ToastComponent(website);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Url => "build-calculator";
|
||||||
|
|
||||||
|
public TimingComponent Timing { get; }
|
||||||
|
public FilterComponent Filter { get; }
|
||||||
|
public OptionsComponent Options { get; }
|
||||||
|
public BankComponent Bank { get; }
|
||||||
|
public ArmyComponent Army { get; }
|
||||||
|
public HighlightsComponent Highlights { get; }
|
||||||
|
public BuildOrderComponent BuildOrder { get; }
|
||||||
|
public TimelineComponent Timeline { get; }
|
||||||
|
public HotkeyViewerComponent Hotkeys { get; }
|
||||||
|
public EntityClickViewComponent EntityView { get; }
|
||||||
|
public BuildChartComponent Chart { get; }
|
||||||
|
public ToastComponent Toast { get; }
|
||||||
|
|
||||||
|
public ILocator CalculatorGrid => Website.Locator(".calculatorGrid");
|
||||||
|
public ILocator ClearBuildOrderButton => Website.Locator("button").Filter(new() { HasText = "Clear Build Order" });
|
||||||
|
|
||||||
|
public async Task ClickClearBuildOrderAsync() => await ClearBuildOrderButton.ClickAsync();
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
namespace Tests.Pages;
|
||||||
|
|
||||||
|
public class DatabasePage : BasePage
|
||||||
|
{
|
||||||
|
public DatabasePage(Website website) : base(website) { }
|
||||||
|
|
||||||
|
public override string Url => "database";
|
||||||
|
|
||||||
|
public async Task FilterNameAsync(string name)
|
||||||
|
{
|
||||||
|
var input = Website.FindById("filterName").First;
|
||||||
|
await Website.EnterInputAsync(input, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetEntityNameAsync(string entityType, string entityName)
|
||||||
|
{
|
||||||
|
var el = Website.Locator($"#{entityType.ToLower()}-{entityName.ToLower()}").Locator("#entityName");
|
||||||
|
return (await el.InnerTextAsync()).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetEntityNameByIndexAsync(int index)
|
||||||
|
{
|
||||||
|
var el = Website.FindById("entityName").Nth(index);
|
||||||
|
return (await el.InnerTextAsync()).Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
namespace Tests.Pages;
|
||||||
|
|
||||||
|
public class DatabaseSinglePage : BasePage
|
||||||
|
{
|
||||||
|
public DatabaseSinglePage(Website website) : base(website) { }
|
||||||
|
|
||||||
|
public override string Url => "database";
|
||||||
|
|
||||||
|
public async Task GotoWithSearchAsync(string searchText)
|
||||||
|
{
|
||||||
|
await Website.GotoAsync($"{Url}/{searchText}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetEntityNameAsync() =>
|
||||||
|
(await Website.FindById("entityName").InnerTextAsync()).Trim();
|
||||||
|
|
||||||
|
public async Task<string> GetEntityHealthAsync() =>
|
||||||
|
(await Website.FindById("entityHealth").InnerTextAsync()).Trim();
|
||||||
|
|
||||||
|
public async Task<string> GetInvalidSearchAsync() =>
|
||||||
|
(await Website.FindById("invalidSearch").InnerTextAsync()).Trim();
|
||||||
|
|
||||||
|
public async Task<string> GetValidSearchAsync() =>
|
||||||
|
(await Website.FindById("validSearch").InnerTextAsync()).Trim();
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
namespace Tests.Pages;
|
||||||
|
|
||||||
|
public class HarassCalculatorPage : BasePage
|
||||||
|
{
|
||||||
|
public HarassCalculatorPage(Website website) : base(website) { }
|
||||||
|
|
||||||
|
public override string Url => "harass-calculator";
|
||||||
|
|
||||||
|
public async Task SetWorkersLostToHarassAsync(int number) =>
|
||||||
|
await EnterAndPressAsync("numberOfWorkersLostToHarass", number);
|
||||||
|
|
||||||
|
public async Task SetNumberOfTownHallsExistingAsync(int number) =>
|
||||||
|
await EnterAndPressAsync("numberOfTownHallsExisting", number);
|
||||||
|
|
||||||
|
public async Task SetTownHallTravelTimeAsync(int index, int seconds) =>
|
||||||
|
await EnterInputAtIndexAsync("numberOfTownHallTravelTimes", index, seconds);
|
||||||
|
|
||||||
|
public async Task<int> GetTotalAlloyHarassmentAsync() => await ReadIntAsync("totalAlloyHarassment");
|
||||||
|
public async Task<int> GetWorkerReplacementCostAsync() => await ReadIntAsync("workerReplacementCost");
|
||||||
|
public async Task<int> GetDelayedMiningCostAsync() => await ReadIntAsync("delayedMiningCost");
|
||||||
|
public async Task<int> GetAverageTravelTimeAsync() => await ReadIntAsync("getAverageTravelTime");
|
||||||
|
public async Task<int> GetExampleTotalAlloyLossAsync() => await ReadIntAsync("exampleTotalAlloyLoss");
|
||||||
|
public async Task<int> GetExampleWorkerCostAsync() => await ReadIntAsync("exampleWorkerCost");
|
||||||
|
public async Task<int> GetExampleMiningTimeCostAsync() => await ReadIntAsync("exampleMiningTimeCost");
|
||||||
|
public async Task<int> GetExampleTotalAlloyLossAccurateAsync() => await ReadIntAsync("exampleTotalAlloyLossAccurate");
|
||||||
|
public async Task<int> GetExampleTotalAlloyLossDifferenceAsync() => await ReadIntAsync("exampleTotalAlloyLossDifference");
|
||||||
|
public async Task<int> GetExampleTotalAlloyLossAccurateDifferenceAsync() => await ReadIntAsync("exampleTotalAlloyLossAccurateDifference");
|
||||||
|
|
||||||
|
private async Task EnterAndPressAsync(string id, int value)
|
||||||
|
{
|
||||||
|
var locator = Website.FindById(id);
|
||||||
|
await Website.EnterInputAsync(locator, value.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EnterInputAtIndexAsync(string parentId, int index, int value)
|
||||||
|
{
|
||||||
|
var inputs = Website.FindById(parentId).Locator("input");
|
||||||
|
var input = inputs.Nth(index);
|
||||||
|
await input.FillAsync(value.ToString());
|
||||||
|
await input.PressAsync("Enter");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> ReadIntAsync(string id)
|
||||||
|
{
|
||||||
|
var text = await Website.FindById(id).TextContentAsync() ?? "";
|
||||||
|
return int.Parse(text.Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
namespace Tests.Shared;
|
||||||
|
|
||||||
|
public class NavigationBar
|
||||||
|
{
|
||||||
|
private readonly Website _website;
|
||||||
|
public NavigationBar(Website website) => _website = website;
|
||||||
|
|
||||||
|
public ILocator SearchButton => _website.Locator("#desktop-searchButton");
|
||||||
|
|
||||||
|
public async Task ClickHomeLinkAsync()
|
||||||
|
{
|
||||||
|
await _website.Locator("a:has-text(\"IGP Fan Reference\")").ClickAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SearchDialog> ClickSearchButtonAsync()
|
||||||
|
{
|
||||||
|
await SearchButton.ClickAsync();
|
||||||
|
return _website.SearchDialog;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
namespace Tests.Shared;
|
||||||
|
|
||||||
|
public class SearchDialog
|
||||||
|
{
|
||||||
|
private readonly Website _website;
|
||||||
|
public SearchDialog(Website website) => _website = website;
|
||||||
|
|
||||||
|
public ILocator SearchBackground => _website.FindById("searchBackground");
|
||||||
|
public ILocator SearchInput => _website.FindById("searchInput");
|
||||||
|
|
||||||
|
public async Task CloseDialogAsync()
|
||||||
|
{
|
||||||
|
await _website.ClickElementAsync(SearchBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<SearchDialog> SearchAsync(string text)
|
||||||
|
{
|
||||||
|
await _website.EnterInputAsync(SearchInput, text);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SelectSearchEntityAsync(string label)
|
||||||
|
{
|
||||||
|
await _website.ClickElementAsync(_website.Locator($"button[label=\"{label}\"]"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
namespace Tests.Shared;
|
||||||
|
|
||||||
|
public class ToastComponent
|
||||||
|
{
|
||||||
|
private readonly Website _website;
|
||||||
|
public ToastComponent(Website website) => _website = website;
|
||||||
|
|
||||||
|
public ILocator Container => _website.Locator(".toastsContainer");
|
||||||
|
public ILocator Toasts => _website.Locator(".toastsContainer .toastContainer");
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<string>> GetToastTitlesAsync()
|
||||||
|
{
|
||||||
|
var titles = await _website.Locator(".toastsContainer .toastTitle").AllTextContentsAsync();
|
||||||
|
return titles.Select(t => t.Trim()).Where(t => !string.IsNullOrEmpty(t)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> HasToastContainingAsync(string text)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _website.Page.WaitForFunctionAsync(
|
||||||
|
@"(expected) => {
|
||||||
|
const titles = document.querySelectorAll('.toastsContainer .toastTitle');
|
||||||
|
return Array.from(titles).some(t => t.textContent.trim().includes(expected));
|
||||||
|
}",
|
||||||
|
text,
|
||||||
|
new() { Timeout = 3000 }
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
using Microsoft.Playwright.NUnit;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace Tests.Specs;
|
||||||
|
|
||||||
|
[Parallelizable(ParallelScope.Self)]
|
||||||
|
[FixtureLifeCycle(LifeCycle.SingleInstance)]
|
||||||
|
public class BuildCalculatorTests : PageTest
|
||||||
|
{
|
||||||
|
private Helpers.Website _website = null!;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void CreateWebsite() => _website = new Helpers.Website(Page);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task AddEntitiesViaKeyboardQWE()
|
||||||
|
{
|
||||||
|
var calc = _website.BuildCalculatorPage;
|
||||||
|
await calc.GotoAsync();
|
||||||
|
|
||||||
|
await calc.Filter.SelectFactionAsync("Q'Rath");
|
||||||
|
await calc.Filter.SelectImmortalAsync("Orzum");
|
||||||
|
|
||||||
|
await calc.Hotkeys.ClickKeyAsync("TAB");
|
||||||
|
|
||||||
|
var keyMap = new Dictionary<string, string> { ["Q"] = "q", ["W"] = "w", ["E"] = "e", ["TAB"] = "Tab" };
|
||||||
|
|
||||||
|
foreach (var key in new[] { "Q", "W", "E", "TAB" })
|
||||||
|
{
|
||||||
|
var entityNames = await calc.Hotkeys.GetEntityNamesOnKeyAsync(key);
|
||||||
|
if (entityNames.Count == 0) continue;
|
||||||
|
|
||||||
|
await Page.Keyboard.PressAsync(keyMap[key]);
|
||||||
|
|
||||||
|
var viewName = await calc.EntityView.GetEntityNameAsync();
|
||||||
|
Assert.That(viewName, Is.Not.Null.And.Not.Empty);
|
||||||
|
Assert.That(entityNames, Does.Contain(viewName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task AddEntitiesViaHotkeysClickTABQWE()
|
||||||
|
{
|
||||||
|
var calc = _website.BuildCalculatorPage;
|
||||||
|
await calc.GotoAsync();
|
||||||
|
|
||||||
|
await calc.Filter.SelectFactionAsync("Q'Rath");
|
||||||
|
await calc.Filter.SelectImmortalAsync("Orzum");
|
||||||
|
|
||||||
|
foreach (var key in new[] { "TAB", "Q", "W", "E" })
|
||||||
|
{
|
||||||
|
var entityNames = await calc.Hotkeys.GetEntityNamesOnKeyAsync(key);
|
||||||
|
if (entityNames.Count == 0) continue;
|
||||||
|
|
||||||
|
await calc.Hotkeys.ClickKeyAsync(key);
|
||||||
|
|
||||||
|
var viewName = await calc.EntityView.GetEntityNameAsync();
|
||||||
|
Assert.That(viewName, Is.Not.Null.And.Not.Empty);
|
||||||
|
Assert.That(entityNames, Does.Contain(viewName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task AddAcropolisViaQVerifyEntityViewAndTimelineThenClear()
|
||||||
|
{
|
||||||
|
var calc = _website.BuildCalculatorPage;
|
||||||
|
await calc.GotoAsync();
|
||||||
|
|
||||||
|
await calc.Filter.SelectFactionAsync("Q'Rath");
|
||||||
|
await calc.Filter.SelectImmortalAsync("Orzum");
|
||||||
|
|
||||||
|
Assert.That(await calc.Timeline.ContainsEntityAsync("Acropolis"), Is.False);
|
||||||
|
|
||||||
|
await calc.Hotkeys.ClickKeyAsync("Q");
|
||||||
|
|
||||||
|
Assert.That(await calc.EntityView.GetEntityNameAsync(), Is.EqualTo("Acropolis"));
|
||||||
|
Assert.That(await calc.Timeline.ContainsEntityAsync("Acropolis"), Is.True);
|
||||||
|
|
||||||
|
await calc.ClickClearBuildOrderAsync();
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
Assert.That(await calc.Timeline.ContainsEntityAsync("Acropolis"), Is.False);
|
||||||
|
Assert.That(await calc.EntityView.GetEntityNameAsync(), Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task MissingRequirementsToastWhenBuildingSoulFoundryWithoutLegionHall()
|
||||||
|
{
|
||||||
|
var calc = _website.BuildCalculatorPage;
|
||||||
|
await calc.GotoAsync();
|
||||||
|
|
||||||
|
await calc.Filter.SelectFactionAsync("Q'Rath");
|
||||||
|
await calc.Filter.SelectImmortalAsync("Orzum");
|
||||||
|
|
||||||
|
await calc.Hotkeys.ClickKeyAsync("E");
|
||||||
|
var hasToast = await calc.Toast.HasToastContainingAsync("Missing Requirements");
|
||||||
|
Assert.That(hasToast, Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task NotEnoughEtherToastWhenBuildingSoulFoundryAfterLegionHall()
|
||||||
|
{
|
||||||
|
var calc = _website.BuildCalculatorPage;
|
||||||
|
await calc.GotoAsync();
|
||||||
|
|
||||||
|
await calc.Filter.SelectFactionAsync("Q'Rath");
|
||||||
|
await calc.Filter.SelectImmortalAsync("Orzum");
|
||||||
|
|
||||||
|
await calc.Hotkeys.ClickKeyAsync("W");
|
||||||
|
await calc.Hotkeys.ClickKeyAsync("E");
|
||||||
|
|
||||||
|
var hasToast = await calc.Toast.HasToastContainingAsync("Not Enough Ether");
|
||||||
|
Assert.That(hasToast, Is.True);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using Microsoft.Playwright.NUnit;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace Tests.Specs;
|
||||||
|
|
||||||
|
[Parallelizable(ParallelScope.Self)]
|
||||||
|
[FixtureLifeCycle(LifeCycle.SingleInstance)]
|
||||||
|
public class HarassCalculatorTests : PageTest
|
||||||
|
{
|
||||||
|
private Helpers.Website _website = null!;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void CreateWebsite() => _website = new Helpers.Website(Page);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task CalculatorInput()
|
||||||
|
{
|
||||||
|
var page = _website.HarassCalculatorPage;
|
||||||
|
await page.GotoAsync();
|
||||||
|
await page.SetWorkersLostToHarassAsync(3);
|
||||||
|
await page.SetNumberOfTownHallsExistingAsync(2);
|
||||||
|
await page.SetTownHallTravelTimeAsync(0, 30);
|
||||||
|
var result = await page.GetTotalAlloyHarassmentAsync();
|
||||||
|
Assert.That(result, Is.EqualTo(240));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task CalculatedExampleInformation()
|
||||||
|
{
|
||||||
|
var page = _website.HarassCalculatorPage;
|
||||||
|
await page.GotoAsync();
|
||||||
|
|
||||||
|
Assert.Multiple(async () =>
|
||||||
|
{
|
||||||
|
Assert.That(await page.GetExampleTotalAlloyLossAsync(), Is.EqualTo(720));
|
||||||
|
Assert.That(await page.GetExampleWorkerCostAsync(), Is.EqualTo(300));
|
||||||
|
Assert.That(await page.GetExampleMiningTimeCostAsync(), Is.EqualTo(420));
|
||||||
|
Assert.That(await page.GetExampleTotalAlloyLossAccurateAsync(), Is.EqualTo(450));
|
||||||
|
Assert.That(await page.GetExampleTotalAlloyLossDifferenceAsync(), Is.EqualTo(300));
|
||||||
|
Assert.That(await page.GetExampleTotalAlloyLossAccurateDifferenceAsync(), Is.EqualTo(270));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using Microsoft.Playwright.NUnit;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace Tests.Specs;
|
||||||
|
|
||||||
|
[Parallelizable(ParallelScope.Self)]
|
||||||
|
[FixtureLifeCycle(LifeCycle.SingleInstance)]
|
||||||
|
public class LinksTests : PageTest
|
||||||
|
{
|
||||||
|
private Helpers.Website _website = null!;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void CreateWebsite() => _website = new Helpers.Website(Page);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task VerifyPageLinks()
|
||||||
|
{
|
||||||
|
_website = new Helpers.Website(Page);
|
||||||
|
|
||||||
|
await _website.HarassCalculatorPage.GotoAsync();
|
||||||
|
var harassLinks = await _website.HarassCalculatorPage.GetLinksAsync();
|
||||||
|
foreach (var link in harassLinks) await VerifyLinkAsync(link);
|
||||||
|
|
||||||
|
await _website.DatabasePage.GotoAsync();
|
||||||
|
var dbLinks = await _website.DatabasePage.GetLinksAsync();
|
||||||
|
foreach (var link in dbLinks) await VerifyLinkAsync(link);
|
||||||
|
|
||||||
|
await _website.DatabaseSinglePage.GotoWithSearchAsync("throne");
|
||||||
|
var singleLinks = await _website.DatabaseSinglePage.GetLinksAsync();
|
||||||
|
foreach (var link in singleLinks) await VerifyLinkAsync(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task VerifyLinkAsync(string link)
|
||||||
|
{
|
||||||
|
if (link.StartsWith("mailto")) return;
|
||||||
|
|
||||||
|
using var client = new System.Net.Http.HttpClient();
|
||||||
|
var response = await client.GetAsync(link);
|
||||||
|
Assert.That(response.IsSuccessStatusCode, Is.True, $"Link '{link}' returned {response.StatusCode}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using Microsoft.Playwright.NUnit;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace Tests.Specs;
|
||||||
|
|
||||||
|
[Parallelizable(ParallelScope.Self)]
|
||||||
|
[FixtureLifeCycle(LifeCycle.SingleInstance)]
|
||||||
|
public class SearchFeaturesTests : PageTest
|
||||||
|
{
|
||||||
|
private Helpers.Website _website = null!;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void CreateWebsite() => _website = new Helpers.Website(Page);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task DesktopOpenCloseSearchDialog()
|
||||||
|
{
|
||||||
|
await _website.GotoAsync();
|
||||||
|
await _website.NavigationBar.ClickSearchButtonAsync();
|
||||||
|
await _website.SearchDialog.CloseDialogAsync();
|
||||||
|
await _website.NavigationBar.ClickHomeLinkAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task DesktopSearchForThrone()
|
||||||
|
{
|
||||||
|
await _website.GotoAsync();
|
||||||
|
await _website.NavigationBar.ClickSearchButtonAsync();
|
||||||
|
await _website.SearchDialog.SearchAsync("Throne");
|
||||||
|
await _website.SearchDialog.SelectSearchEntityAsync("Throne");
|
||||||
|
|
||||||
|
var name = await _website.DatabaseSinglePage.GetEntityNameAsync();
|
||||||
|
var health = await _website.DatabaseSinglePage.GetEntityHealthAsync();
|
||||||
|
|
||||||
|
Assert.That(name, Is.EqualTo("Throne"));
|
||||||
|
Assert.That(health.Trim(), Is.Not.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task DesktopFilterForThrone()
|
||||||
|
{
|
||||||
|
var page = _website.DatabasePage;
|
||||||
|
await page.GotoAsync();
|
||||||
|
await page.FilterNameAsync("Throne");
|
||||||
|
var name = await page.GetEntityNameByIndexAsync(0);
|
||||||
|
Assert.That(name, Is.EqualTo("Throne"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task SeeThroneByDefault()
|
||||||
|
{
|
||||||
|
var page = _website.DatabasePage;
|
||||||
|
await page.GotoAsync();
|
||||||
|
var name = await page.GetEntityNameAsync("army", "throne");
|
||||||
|
Assert.That(name, Is.EqualTo("Throne"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task DirectLinkNotThroneFailure()
|
||||||
|
{
|
||||||
|
var page = _website.DatabaseSinglePage;
|
||||||
|
await page.GotoWithSearchAsync("not throne");
|
||||||
|
var invalidSearch = await page.GetInvalidSearchAsync();
|
||||||
|
var validSearch = await page.GetValidSearchAsync();
|
||||||
|
Assert.That(invalidSearch, Is.EqualTo("not throne"));
|
||||||
|
Assert.That(validSearch, Is.EqualTo("Throne"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
|
<PackageReference Include="NUnit" Version="4.3.0" />
|
||||||
|
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Playwright.NUnit" Version="1.52.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Microsoft.Playwright" />
|
||||||
|
<Using Include="Tests.Helpers" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user