diff --git a/.gitignore b/.gitignore index a0fac7f0..19faf07c 100644 --- a/.gitignore +++ b/.gitignore @@ -261,3 +261,6 @@ __pycache__/ *.pyc *.feature.cs + +.DS_Store +*mono_crash* diff --git a/Allure.Commons.NetCore.Nuget.Tests/Allure.Commons.NetCore.Nuget.Tests.csproj b/Allure.Commons.NetCore.Nuget.Tests/Allure.Commons.NetCore.Nuget.Tests.csproj index fdd980bf..0ab035f6 100644 --- a/Allure.Commons.NetCore.Nuget.Tests/Allure.Commons.NetCore.Nuget.Tests.csproj +++ b/Allure.Commons.NetCore.Nuget.Tests/Allure.Commons.NetCore.Nuget.Tests.csproj @@ -1,16 +1,16 @@  - netcoreapp2.1 + netcoreapp3.1 Allure.Commons.NetCore.Nuget.Tests Allure.Commons.NetCore.Nuget.Tests - - + + - + diff --git a/Allure.Commons.Tests/Allure.Commons.Tests.csproj b/Allure.Commons.Tests/Allure.Commons.Tests.csproj index 719c4458..e9857996 100644 --- a/Allure.Commons.Tests/Allure.Commons.Tests.csproj +++ b/Allure.Commons.Tests/Allure.Commons.Tests.csproj @@ -1,14 +1,16 @@  - net461 + netcoreapp3.1 false + default - + + - + diff --git a/Allure.Commons/Allure.Commons.csproj b/Allure.Commons/Allure.Commons.csproj index faaec837..13124405 100644 --- a/Allure.Commons/Allure.Commons.csproj +++ b/Allure.Commons/Allure.Commons.csproj @@ -2,9 +2,7 @@ net45;netstandard2.0 - 2.4.2.4 - 2.4.2.4 - 2.4.2.4 + 3.0.0 True Alexander Bakanov @@ -12,17 +10,18 @@ .net implementation of Allure java-commons https://github.com/allure-framework/allure-csharp/wiki/Allure.Commons allure - https://raw.githubusercontent.com/allure-framework/allure-csharp/master/img/allure-net.png + allure-net.png False False key.snk - + default - + + - - + + diff --git a/Allure.Commons/AllureConstants.cs b/Allure.Commons/AllureConstants.cs index e21f636a..6b681a24 100644 --- a/Allure.Commons/AllureConstants.cs +++ b/Allure.Commons/AllureConstants.cs @@ -10,8 +10,5 @@ public sealed class AllureConstants public static string TEST_RESULT_CONTAINER_FILE_SUFFIX = "-container.json"; public static string TEST_RUN_FILE_SUFFIX = "-testrun.json"; public static string ATTACHMENT_FILE_SUFFIX = "-attachment"; - - - } -} +} \ No newline at end of file diff --git a/Allure.Commons/AllureLifecycle.cs b/Allure.Commons/AllureLifecycle.cs index 54e6dd5f..66d146fe 100644 --- a/Allure.Commons/AllureLifecycle.cs +++ b/Allure.Commons/AllureLifecycle.cs @@ -1,54 +1,53 @@ -using Allure.Commons.Configuration; +using System; +using System.IO; +using Allure.Commons.Configuration; using Allure.Commons.Helpers; using Allure.Commons.Storage; using Allure.Commons.Writer; using HeyRed.Mime; -using System; -using System.IO; namespace Allure.Commons { public class AllureLifecycle { private static readonly object lockobj = new object(); - private AllureStorage storage; - private IAllureResultsWriter writer; private static AllureLifecycle instance; + private readonly AllureStorage storage; + private readonly IAllureResultsWriter writer; + + public AllureLifecycle(string jsonConfigurationFile = null) + { + if (jsonConfigurationFile != null) + JsonConfiguration = File.ReadAllText(jsonConfigurationFile); + else + JsonConfiguration = File.ReadAllText(GetDefaultJsonConfiguration()); + + AllureConfiguration = AllureConfiguration.ReadFromJson(JsonConfiguration); + writer = new FileSystemResultsWriter(AllureConfiguration); + storage = new AllureStorage(); + } - public string JsonConfiguration { get; private set; } = string.Empty; - public AllureConfiguration AllureConfiguration { get; private set; } + public string JsonConfiguration { get; } = string.Empty; + public AllureConfiguration AllureConfiguration { get; } public string ResultsDirectory => writer.ToString(); + public static AllureLifecycle Instance { get { if (instance == null) - { lock (lockobj) { instance = instance ?? new AllureLifecycle(); } - } return instance; } } - public AllureLifecycle(string jsonConfigurationFile = null) - { - if (jsonConfigurationFile != null) - JsonConfiguration = File.ReadAllText(jsonConfigurationFile); - else - JsonConfiguration = File.ReadAllText(GetDefaultJsonConfiguration()); - - AllureConfiguration = AllureConfiguration.ReadFromJson(JsonConfiguration); - writer = new FileSystemResultsWriter(AllureConfiguration); - storage = new AllureStorage(); - - } - #region TestContainer + public virtual AllureLifecycle StartTestContainer(TestResultContainer container) { container.start = DateTimeOffset.Now.ToUnixTimeMilliseconds(); @@ -80,9 +79,11 @@ public virtual AllureLifecycle WriteTestContainer(string uuid) writer.Write(storage.Remove(uuid)); return this; } + #endregion #region Fixture + public virtual AllureLifecycle StartBeforeFixture(string parentUuid, string uuid, FixtureResult result) { UpdateTestContainer(parentUuid, container => container.befores.Add(result)); @@ -102,6 +103,7 @@ public virtual AllureLifecycle UpdateFixture(Action update) UpdateFixture(storage.GetRootStep(), update); return this; } + public virtual AllureLifecycle UpdateFixture(string uuid, Action update) { update.Invoke(storage.Get(uuid)); @@ -113,6 +115,7 @@ public virtual AllureLifecycle StopFixture(Action beforeStop) UpdateFixture(beforeStop); return StopFixture(storage.GetRootStep()); } + public virtual AllureLifecycle StopFixture(string uuid) { var fixture = storage.Remove(uuid); @@ -121,6 +124,7 @@ public virtual AllureLifecycle StopFixture(string uuid) fixture.stop = DateTimeOffset.Now.ToUnixTimeMilliseconds(); return this; } + #endregion #region TestCase @@ -176,6 +180,7 @@ public virtual AllureLifecycle WriteTestCase(string uuid) #endregion #region Step + public virtual AllureLifecycle StartStep(string uuid, StepResult result) { StartStep(storage.GetCurrentStep(), uuid, result); @@ -202,6 +207,7 @@ public virtual AllureLifecycle UpdateStep(string uuid, Action update update.Invoke(storage.Get(uuid)); return this; } + public virtual AllureLifecycle StopStep(Action beforeStop) { UpdateStep(beforeStop); @@ -232,10 +238,12 @@ public virtual AllureLifecycle AddAttachment(string name, string type, string pa var fileExtension = new FileInfo(path).Extension; return AddAttachment(name, type, File.ReadAllBytes(path), fileExtension); } - public virtual AllureLifecycle AddAttachment(string name, string type, byte[] content, string fileExtension = "") + + public virtual AllureLifecycle AddAttachment(string name, string type, byte[] content, + string fileExtension = "") { var source = $"{Guid.NewGuid().ToString("N")}{AllureConstants.ATTACHMENT_FILE_SUFFIX}{fileExtension}"; - var attachment = new Attachment() + var attachment = new Attachment { name = name, type = type, @@ -256,18 +264,19 @@ public virtual AllureLifecycle AddAttachment(string path, string name = null) #endregion #region Extensions + public virtual void CleanupResultDirectory() { writer.CleanUp(); } - public virtual AllureLifecycle AddScreenDiff(string testCaseUuid, string expectedPng, string actualPng, string diffPng) + public virtual AllureLifecycle AddScreenDiff(string testCaseUuid, string expectedPng, string actualPng, + string diffPng) { - AddAttachment(expectedPng, "expected") - .AddAttachment(actualPng, "actual") - .AddAttachment(diffPng, "diff") - .UpdateTestCase(testCaseUuid, x => x.labels.Add(Label.TestType("screenshotDiff"))); + .AddAttachment(actualPng, "actual") + .AddAttachment(diffPng, "diff") + .UpdateTestCase(testCaseUuid, x => x.labels.Add(Label.TestType("screenshotDiff"))); return this; } @@ -276,12 +285,14 @@ public virtual AllureLifecycle AddScreenDiff(string testCaseUuid, string expecte #region Privates + private string GetDefaultJsonConfiguration() { var envConfig = Environment.GetEnvironmentVariable(AllureConstants.ALLURE_CONFIG_ENV_VARIABLE); if (envConfig != null && !File.Exists(envConfig)) - throw new FileNotFoundException($"Couldn't find '{envConfig}' specified in {AllureConstants.ALLURE_CONFIG_ENV_VARIABLE} environment variable"); + throw new FileNotFoundException( + $"Couldn't find '{envConfig}' specified in {AllureConstants.ALLURE_CONFIG_ENV_VARIABLE} environment variable"); if (File.Exists(envConfig)) return envConfig; @@ -290,11 +301,12 @@ private string GetDefaultJsonConfiguration() var binaryConfig = Path.Combine(binaryFolder, AllureConstants.CONFIG_FILENAME); if (!File.Exists(binaryConfig)) - throw new FileNotFoundException($"Couldn't find Allure configuration file. Please either specify full path to {AllureConstants.CONFIG_FILENAME} in the {AllureConstants.ALLURE_CONFIG_ENV_VARIABLE} environment variable or place {AllureConstants.CONFIG_FILENAME} to the '{binaryFolder}' folder"); + throw new FileNotFoundException( + $"Couldn't find Allure configuration file. Please either specify full path to {AllureConstants.CONFIG_FILENAME} in the {AllureConstants.ALLURE_CONFIG_ENV_VARIABLE} environment variable or place {AllureConstants.CONFIG_FILENAME} to the '{binaryFolder}' folder"); return binaryConfig; - } + private void StartFixture(string uuid, FixtureResult fixtureResult) { storage.Put(uuid, fixtureResult); @@ -305,6 +317,5 @@ private void StartFixture(string uuid, FixtureResult fixtureResult) } #endregion - } -} +} \ No newline at end of file diff --git a/Allure.Commons/Configuration/AllureConfiguration.cs b/Allure.Commons/Configuration/AllureConfiguration.cs index 763d4ddd..a7f40607 100644 --- a/Allure.Commons/Configuration/AllureConfiguration.cs +++ b/Allure.Commons/Configuration/AllureConfiguration.cs @@ -1,16 +1,14 @@ -using Newtonsoft.Json; +using System.Collections.Generic; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using System.Collections.Generic; namespace Allure.Commons.Configuration { public class AllureConfiguration { - public string Title { get; } - public string Directory { get; } = "allure-results"; - public HashSet Links { get; } = new HashSet(); - - private AllureConfiguration() { } + private AllureConfiguration() + { + } [JsonConstructor] protected AllureConfiguration(string title, string directory, HashSet links) @@ -20,6 +18,10 @@ protected AllureConfiguration(string title, string directory, HashSet li Links = links ?? Links; } + public string Title { get; } + public string Directory { get; } = "allure-results"; + public HashSet Links { get; } = new HashSet(); + public static AllureConfiguration ReadFromJson(string json) { var config = new AllureConfiguration(); @@ -32,4 +34,4 @@ public static AllureConfiguration ReadFromJson(string json) return config; } } -} +} \ No newline at end of file diff --git a/Allure.Commons/Helpers/LinkHelper.cs b/Allure.Commons/Helpers/LinkHelper.cs index 546f333e..c5c12059 100644 --- a/Allure.Commons/Helpers/LinkHelper.cs +++ b/Allure.Commons/Helpers/LinkHelper.cs @@ -14,17 +14,19 @@ public static void UpdateLinks(IEnumerable links, HashSet patterns .GroupBy(l => l.type)) { var typePattern = $"{{{linkTypeGroup.Key}}}"; - var linkPattern = patterns.FirstOrDefault(x => x.IndexOf(typePattern, StringComparison.CurrentCultureIgnoreCase) >= 0); + var linkPattern = patterns.FirstOrDefault(x => + x.IndexOf(typePattern, StringComparison.CurrentCultureIgnoreCase) >= 0); if (linkPattern != null) { var linkArray = linkTypeGroup.ToArray(); for (var i = 0; i < linkArray.Length; i++) { - var replacedLink = Regex.Replace(linkPattern, typePattern, linkArray[i].url ?? string.Empty, RegexOptions.IgnoreCase); + var replacedLink = Regex.Replace(linkPattern, typePattern, linkArray[i].url ?? string.Empty, + RegexOptions.IgnoreCase); linkArray[i].url = Uri.EscapeUriString(replacedLink); } } } } } -} +} \ No newline at end of file diff --git a/Allure.Commons/Model/allure2.Extensions.cs b/Allure.Commons/Model/allure2.Extensions.cs index 9f38fe2c..e42acc03 100644 --- a/Allure.Commons/Model/allure2.Extensions.cs +++ b/Allure.Commons/Model/allure2.Extensions.cs @@ -2,7 +2,14 @@ namespace Allure.Commons { - public enum SeverityLevel { normal, blocker, critical, minor, trivial } + public enum SeverityLevel + { + normal, + blocker, + critical, + minor, + trivial + } public partial class TestResultContainer { @@ -32,81 +39,82 @@ public partial class Label { public static Label TestType(string value) { - return new Label() { name = "testType", value = value }; + return new Label {name = "testType", value = value}; } public static Label ParentSuite(string value) { - return new Label() { name = "parentSuite", value = value }; + return new Label {name = "parentSuite", value = value}; } public static Label Suite(string value) { - return new Label() { name = "suite", value = value }; + return new Label {name = "suite", value = value}; } public static Label SubSuite(string value) { - return new Label() { name = "subSuite", value = value }; + return new Label {name = "subSuite", value = value}; } public static Label Owner(string value) { - return new Label() { name = "owner", value = value }; + return new Label {name = "owner", value = value}; } public static Label Severity(SeverityLevel value) { - return new Label() { name = "severity", value = value.ToString() }; + return new Label {name = "severity", value = value.ToString()}; } public static Label Tag(string value) { - return new Label() { name = "tag", value = value }; + return new Label {name = "tag", value = value}; } public static Label Epic(string value) { - return new Label() { name = "epic", value = value }; + return new Label {name = "epic", value = value}; } public static Label Feature(string value) { - return new Label() { name = "feature", value = value }; + return new Label {name = "feature", value = value}; } public static Label Story(string value) { - return new Label() { name = "story", value = value }; + return new Label {name = "story", value = value}; } public static Label Package(string value) { - return new Label() { name = "package", value = value }; + return new Label {name = "package", value = value}; } public static Label TestClass(string value) { - return new Label() { name = "testClass", value = value }; + return new Label {name = "testClass", value = value}; } public static Label TestMethod(string value) { - return new Label() { name = "testMethod", value = value }; + return new Label {name = "testMethod", value = value}; } public static Label Thread() { - return new Label() + return new Label { name = "thread", - value = System.Threading.Thread.CurrentThread.Name ?? System.Threading.Thread.CurrentThread.ManagedThreadId.ToString() + value = System.Threading.Thread.CurrentThread.Name ?? + System.Threading.Thread.CurrentThread.ManagedThreadId.ToString() }; } public static Label Host() { - return new Label() + return new Label { name = "host", value = Environment.MachineName ?? "Unknown host" @@ -115,7 +123,7 @@ public static Label Host() public static Label Host(string value) { - return new Label() + return new Label { name = "host", value = value @@ -127,7 +135,7 @@ public partial class Link { public static Link Issue(string name, string url) { - return new Link() { name = name, type = "issue", url = url }; + return new Link {name = name, type = "issue", url = url}; } public static Link Issue(string name) @@ -137,7 +145,7 @@ public static Link Issue(string name) public static Link Tms(string name, string url) { - return new Link() { name = name, type = "tms", url = url }; + return new Link {name = name, type = "tms", url = url}; } public static Link Tms(string name) @@ -145,4 +153,4 @@ public static Link Tms(string name) return Tms(name, null); } } -} +} \ No newline at end of file diff --git a/Allure.Commons/Storage/AllureStorage.cs b/Allure.Commons/Storage/AllureStorage.cs index 81fa4e7d..6673f1c7 100644 --- a/Allure.Commons/Storage/AllureStorage.cs +++ b/Allure.Commons/Storage/AllureStorage.cs @@ -1,59 +1,63 @@ -using Allure.Commons; -using System; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading; namespace Allure.Commons.Storage { - class AllureStorage + internal class AllureStorage { - private ConcurrentDictionary storage = new ConcurrentDictionary(); - private ThreadLocal> stepContext = new ThreadLocal>(() => + private readonly ThreadLocal> stepContext = new ThreadLocal>(() => { return new LinkedList(); }); + private readonly ConcurrentDictionary storage = new ConcurrentDictionary(); + public T Get(string uuid) { - return (T)storage[uuid]; + return (T) storage[uuid]; } + public T Put(string uuid, T item) { - return (T)storage.GetOrAdd(uuid, item); + return (T) storage.GetOrAdd(uuid, item); } + public T Remove(string uuid) { - storage.TryRemove(uuid, out object value); - return (T)value; + storage.TryRemove(uuid, out var value); + return (T) value; } + public void ClearStepContext() { stepContext.Value.Clear(); } + public void StartStep(string uuid) { stepContext.Value.AddFirst(uuid); } + public void StopStep() { stepContext.Value.RemoveFirst(); } + public string GetRootStep() { return stepContext.Value.Last?.Value; } + public string GetCurrentStep() { return stepContext.Value.First?.Value; } + public void AddStep(string parentUuid, string uuid, StepResult stepResult) { Put(uuid, stepResult); Get(parentUuid).steps.Add(stepResult); } - } -} +} \ No newline at end of file diff --git a/Allure.Commons/Writer/FileSystemResultsWriter.cs b/Allure.Commons/Writer/FileSystemResultsWriter.cs index d0bcef05..6cba9f89 100644 --- a/Allure.Commons/Writer/FileSystemResultsWriter.cs +++ b/Allure.Commons/Writer/FileSystemResultsWriter.cs @@ -1,72 +1,77 @@ -using Allure.Commons.Configuration; -using Allure.Commons.Helpers; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using System; +using System; using System.IO; using System.Runtime.CompilerServices; using System.Threading; +using Allure.Commons.Configuration; +using Allure.Commons.Helpers; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; [assembly: InternalsVisibleTo("Allure.Commons.Tests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] + namespace Allure.Commons.Writer { - class FileSystemResultsWriter : IAllureResultsWriter + internal class FileSystemResultsWriter : IAllureResultsWriter { + private readonly AllureConfiguration configuration; //private Logger logger = LogManager.GetCurrentClassLogger(); private readonly string outputDirectory; - private readonly AllureConfiguration configuration; - private JsonSerializer serializer = new JsonSerializer(); + private readonly JsonSerializer serializer = new JsonSerializer(); internal FileSystemResultsWriter(AllureConfiguration configuration) { this.configuration = configuration; - this.outputDirectory = GetResultsDirectory(configuration.Directory); + outputDirectory = GetResultsDirectory(configuration.Directory); serializer.NullValueHandling = NullValueHandling.Ignore; serializer.Formatting = Formatting.Indented; serializer.Converters.Add(new StringEnumConverter()); } - public override string ToString() => outputDirectory; - public void Write(TestResult testResult) { LinkHelper.UpdateLinks(testResult.links, configuration.Links); - this.Write(testResult, AllureConstants.TEST_RESULT_FILE_SUFFIX); + Write(testResult, AllureConstants.TEST_RESULT_FILE_SUFFIX); } + public void Write(TestResultContainer testResult) { LinkHelper.UpdateLinks(testResult.links, configuration.Links); - this.Write(testResult, AllureConstants.TEST_RESULT_CONTAINER_FILE_SUFFIX); + Write(testResult, AllureConstants.TEST_RESULT_CONTAINER_FILE_SUFFIX); } + public void Write(string source, byte[] content) { var filePath = Path.Combine(outputDirectory, source); File.WriteAllBytes(filePath, content); } + public void CleanUp() { using (var mutex = new Mutex(false, "729dc988-0e9c-49d0-9e50-17e0df3cd82b")) { mutex.WaitOne(); var directory = new DirectoryInfo(outputDirectory); - foreach (var file in directory.GetFiles()) - { - file.Delete(); - } + foreach (var file in directory.GetFiles()) file.Delete(); mutex.ReleaseMutex(); } } + public override string ToString() + { + return outputDirectory; + } + protected string Write(object allureObject, string fileSuffix) { var filePath = Path.Combine(outputDirectory, $"{Guid.NewGuid().ToString("N")}{fileSuffix}"); - using (StreamWriter fileStream = File.CreateText(filePath)) + using (var fileStream = File.CreateText(filePath)) { serializer.Serialize(fileStream, allureObject); } + return filePath; } @@ -88,15 +93,14 @@ internal virtual bool HasDirectoryAccess(string directory) private string GetResultsDirectory(string outputDirectory) { var parentDir = new DirectoryInfo(outputDirectory).Parent.FullName; - outputDirectory = HasDirectoryAccess(parentDir) ? outputDirectory : - Path.Combine( - Path.GetTempPath(), AllureConstants.DEFAULT_RESULTS_FOLDER); + outputDirectory = HasDirectoryAccess(parentDir) + ? outputDirectory + : Path.Combine( + Path.GetTempPath(), AllureConstants.DEFAULT_RESULTS_FOLDER); Directory.CreateDirectory(outputDirectory); return new DirectoryInfo(outputDirectory).FullName; } - - } -} +} \ No newline at end of file diff --git a/Allure.Commons/Writer/IAllureResultsWriter.cs b/Allure.Commons/Writer/IAllureResultsWriter.cs index c0257df3..11126c7e 100644 --- a/Allure.Commons/Writer/IAllureResultsWriter.cs +++ b/Allure.Commons/Writer/IAllureResultsWriter.cs @@ -1,15 +1,10 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace Allure.Commons.Writer +namespace Allure.Commons.Writer { - interface IAllureResultsWriter + internal interface IAllureResultsWriter { void Write(TestResult testResult); void Write(TestResultContainer testResult); void Write(string source, byte[] attachment); void CleanUp(); } -} +} \ No newline at end of file diff --git a/Allure.Features/Allure.Features.csproj b/Allure.Features/Allure.Features.csproj new file mode 100644 index 00000000..b4a260c3 --- /dev/null +++ b/Allure.Features/Allure.Features.csproj @@ -0,0 +1,47 @@ + + + netcoreapp3.1 + false + + + + + + + + + + + + + + + + + + + Always + + + Always + + + + + PreserveNewest + + + + + + + + + + + + + + + + diff --git a/Allure.Features/AssemblyInfo.cs b/Allure.Features/AssemblyInfo.cs new file mode 100644 index 00000000..365a663e --- /dev/null +++ b/Allure.Features/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using NUnit.Framework; + +[assembly: Parallelizable(ParallelScope.Fixtures)] +[assembly: LevelOfParallelism(10)] \ No newline at end of file diff --git a/Allure.Features/IgnoreException.cs b/Allure.Features/IgnoreException.cs new file mode 100644 index 00000000..8df881b5 --- /dev/null +++ b/Allure.Features/IgnoreException.cs @@ -0,0 +1,8 @@ +using System; + +namespace Allure.Features +{ + internal class IgnoreException : Exception + { + } +} \ No newline at end of file diff --git a/Tests.SpecRun/Steps.cs b/Allure.Features/Steps.cs similarity index 86% rename from Tests.SpecRun/Steps.cs rename to Allure.Features/Steps.cs index df192793..a8623c50 100644 --- a/Tests.SpecRun/Steps.cs +++ b/Allure.Features/Steps.cs @@ -1,27 +1,30 @@ -using Allure.Commons; -using Allure.SpecFlowPlugin; -using System; +using System; using System.IO; using System.Linq; +using Allure.Commons; +using Allure.SpecFlowPlugin; using TechTalk.SpecFlow; -namespace Tests.SpecRun +namespace Allure.Features { - public enum TestOutcome { passed, failed } + public enum TestOutcome + { + passed, + failed + } [Binding] public class Steps { - static AllureLifecycle allure = AllureLifecycle.Instance; + private static readonly AllureLifecycle allure = AllureLifecycle.Instance; - FeatureContext featureContext; - ScenarioContext scenarioContext; + private FeatureContext featureContext; + private readonly ScenarioContext scenarioContext; public Steps(FeatureContext featureContext, ScenarioContext scenarioContext) { this.featureContext = featureContext; this.scenarioContext = scenarioContext; - } [StepDefinition(@"Step is '(.*)'")] @@ -98,12 +101,12 @@ private static void Handle(string[] tags) allure.AddAttachment(path); allure.AddAttachment(path, "text file"); } + if (tags != null) - if (tags.Any(x => x.EndsWith("failed"))) + if (tags.Any(x => x.EndsWith(Status.failed.ToString()) || x.EndsWith(Status.broken.ToString()))) throw new Exception("Wasted"); else if (tags.Any(x => x == PluginHelper.IGNORE_EXCEPTION)) throw new IgnoreException(); } - } -} +} \ No newline at end of file diff --git a/Allure.Features/TestData/After Feature Failure.feature b/Allure.Features/TestData/After Feature Failure.feature new file mode 100644 index 00000000..e2ff4fdf --- /dev/null +++ b/Allure.Features/TestData/After Feature Failure.feature @@ -0,0 +1,13 @@ +@afterfeaturefailed +Feature: After Feature Failure + + @passed + Scenario: After Feature Failure 1 + Given Step is 'passed' + + @broken + Scenario: After Feature Failure 2 + Given Step is 'failed' + + + diff --git a/Allure.Features/TestData/Attachments.feature b/Allure.Features/TestData/Attachments.feature new file mode 100644 index 00000000..b76e2b73 --- /dev/null +++ b/Allure.Features/TestData/Attachments.feature @@ -0,0 +1,26 @@ +Feature: Attachments + + @passed @attachment @beforescenario @afterscenario + Scenario: With attachments + Given Step with attachment + Given Step is 'passed' + + @broken @attachment @beforescenario + Scenario: Failed BeforeScenario with attachment + Given Step with attachment + Given Step is 'passed' + + @broken @attachment @afterscenario + Scenario: Failed AfterScenario with attachment + Given Step with attachment + Given Step is 'passed' + + @broken @attachment @beforestep + Scenario: Failed BeforeStep with attachment + Given Step with attachment + Given Step is 'passed' + + @broken @attachment @afterstep + Scenario: Failed AfterStep with attachment + Given Step with attachment + Given Step is 'passed' diff --git a/Allure.Features/TestData/Before Feature Failure.feature b/Allure.Features/TestData/Before Feature Failure.feature new file mode 100644 index 00000000..e1159e4a --- /dev/null +++ b/Allure.Features/TestData/Before Feature Failure.feature @@ -0,0 +1,5 @@ +@beforefeaturefailed +Feature: Before Feature Failure + + @broken + Scenario: Unknown \ No newline at end of file diff --git a/Allure.Features/TestData/Ignored.feature b/Allure.Features/TestData/Ignored.feature new file mode 100644 index 00000000..0da07a7c --- /dev/null +++ b/Allure.Features/TestData/Ignored.feature @@ -0,0 +1,7 @@ +Feature: Ignored + Handle any exceptions with IgnoreException text + and mark related scenario as Skipped + https://github.com/allure-framework/allure-csharp/issues/14 + + @beforescenario @IgnoreException @skipped + Scenario: Should handle IgnoreException in BeforeScenario \ No newline at end of file diff --git a/Allure.Features/TestData/Invalid Steps.feature b/Allure.Features/TestData/Invalid Steps.feature new file mode 100644 index 00000000..9ad1dfa0 --- /dev/null +++ b/Allure.Features/TestData/Invalid Steps.feature @@ -0,0 +1,20 @@ +Feature: Invalid Steps + + @broken + Scenario: All steps are invalid + Given I don't have such step + And I don't have such step too + + @broken + Scenario: Some steps are invalid + Given Step is 'passed' + Given I don't have such step + Given Step is 'passed' + And I don't have such step too + + @broken + Scenario: Failed step followed by invalid step + Given Step is 'failed' + Given I don't have such step + Given Step is 'passed' + And I don't have such step too diff --git a/Allure.Features/TestData/Labels.feature b/Allure.Features/TestData/Labels.feature new file mode 100644 index 00000000..e31beac8 --- /dev/null +++ b/Allure.Features/TestData/Labels.feature @@ -0,0 +1,20 @@ +@labels @core @epic:v1.2 @owner:Vasya +Feature: Labels + + @passed @ui @story:accounting @123 @tms:234 @package:com.company.accounting @class:main @method:getLedger @tag1 + Scenario: [v1.2 accounting] [ui.core] Selenium test 1 + + @passed @api @blocker @567 @999999 + Scenario: [v1.2] [api.core] Api test 1 + + @passed @api @create @link:http://example.org + Scenario: [v1.2] [api.core.create] Api test 2 + + @passed @api @update + Scenario: [v1.2] [api.core.update] Api test 3 + + @passed @update @story:accounting + Scenario: [v1.2 accounting] [core.update] Update test + + @passed @epic:v.2.0 @story:security @package:com.company.security @class:main @method:getACL + Scenario: [v1.2 / v.2.0 security] [core.update] [com.company.security.main.getACL] Get ACL test \ No newline at end of file diff --git a/Allure.Features/TestData/Scenario Hooks.feature b/Allure.Features/TestData/Scenario Hooks.feature new file mode 100644 index 00000000..7bfeba5a --- /dev/null +++ b/Allure.Features/TestData/Scenario Hooks.feature @@ -0,0 +1,27 @@ +Feature: Scenario and Step Bindings + + @broken @beforescenario @beforestep + Scenario: Should handle BeforeScenario and BeforeStep failure + Given Step is 'passed' + Given Step is 'passed' + + @broken @beforescenario + Scenario: Should handle BeforeScenario failure + Given Step is 'passed' + Given Step is 'passed' + + @broken @beforestep + Scenario: Should handle BeforeStep failure + Given Step is 'passed' + Given Step is 'passed' + Given Step is 'passed' + + @broken @afterstep + Scenario: Should handle AfterStep failure + Given Step is 'passed' + Given Step is 'passed' + + @broken @afterscenario + Scenario: Should handle AfterScenario failure + Given Step is 'passed' + Given Step is 'passed' \ No newline at end of file diff --git a/Allure.Features/TestData/Scenarios and Steps.feature b/Allure.Features/TestData/Scenarios and Steps.feature new file mode 100644 index 00000000..a0d8c71d --- /dev/null +++ b/Allure.Features/TestData/Scenarios and Steps.feature @@ -0,0 +1,22 @@ +Feature: Scenarios and Steps + + @passed + Scenario: Empty scenario + + @passed + Scenario: Passed scenario + Given Step is 'passed' + + @failed + Scenario: Failed scenario + Given Step is 'passed' + Given Step is 'failed' + Given Step is 'passed' + + @passed + Scenario: Shared Steps + Given I execute the steps of: + | feature | scenario | + | Scenarios and Steps | Passed scenario | + + And Step is 'passed' \ No newline at end of file diff --git a/Tests.SpecRun/TestData/Step arguments.feature b/Allure.Features/TestData/Step arguments.feature similarity index 52% rename from Tests.SpecRun/TestData/Step arguments.feature rename to Allure.Features/TestData/Step arguments.feature index ee4cffe1..af9021ac 100644 --- a/Tests.SpecRun/TestData/Step arguments.feature +++ b/Allure.Features/TestData/Step arguments.feature @@ -1,51 +1,52 @@ -@data -Feature: Step arguments - -Scenario: Table arguments +Feature: Step arguments + + @passed + Scenario: Table arguments # no params - Given Step with table - |1|2| + Given Step with table + | 1 | 2 | # no params - Given Step with table - |1|2|3| + Given Step with table + | 1 | 2 | 3 | # 2 columns with header match, 1 row: process as param value wihout header (width = 10) - Given Step with table - | attribute | value | - | width | 10 | + Given Step with table + | attribute | value | + | width | 10 | # 1 row: process as param value with header (name = John, surname = Smith, ...) - Given Step with table - | name | surname | gender | age | - | John | Smith | male | | + Given Step with table + | name | surname | gender | age | + | John | Smith | male | | # 2 column with header match, N rows: process as param value wihout header (width = 10, length = 20, height = 5) - Given Step with table - | attribute | value | - | width | 10 | - | length | 20 | - | height | 5 | + Given Step with table + | attribute | value | + | width | 10 | + | length | 20 | + | height | 5 | # 2 column without header match: process as csv attachment - Given Step with table - | param | value | - | width | 10 | - | length | 20 | - | height | 5 | + Given Step with table + | param | value | + | width | 10 | + | length | 20 | + | height | 5 | # matrix: process as csv attachment - Given Step with table - | name | surname | gender | age | - | John | Smith | male | 30 | - | "Mary","Ann" | Jane; | female | 25 | - | | | | | - | Eric | Cartman | , | ,, | + Given Step with table + | name | surname | gender | age | + | John | Smith | male | 30 | + | "Mary","Ann" | Jane; | female | 25 | + | | | | | + | Eric | Cartman | , | ,, | -Scenario: Multiline arguments + @passed + Scenario: Multiline arguments - Given Step with params: 123 + Given Step with params: 123 """ diff --git a/Allure.Features/TestData/Tags.feature b/Allure.Features/TestData/Tags.feature new file mode 100644 index 00000000..9d9bec3e --- /dev/null +++ b/Allure.Features/TestData/Tags.feature @@ -0,0 +1,14 @@ +@foo +Feature: Tags + + @passed @bar + Scenario: Foo and Bar + Given Step is 'passed' + + @passed + Scenario: Foo 1 + Given Step is 'passed' + + @passed @foo + Scenario: Foo 2 + Given Step is 'passed' \ No newline at end of file diff --git "a/Allure.Features/TestData/\320\244\320\270\321\207\320\260 \321\201 \320\272\320\270\321\200\320\270\320\273\320\273\320\270\321\206\320\265\320\271.feature" "b/Allure.Features/TestData/\320\244\320\270\321\207\320\260 \321\201 \320\272\320\270\321\200\320\270\320\273\320\273\320\270\321\206\320\265\320\271.feature" new file mode 100644 index 00000000..d84d010f --- /dev/null +++ "b/Allure.Features/TestData/\320\244\320\270\321\207\320\260 \321\201 \320\272\320\270\321\200\320\270\320\273\320\273\320\270\321\206\320\265\320\271.feature" @@ -0,0 +1,10 @@ +#language: ru +Функция: В одном царстве, в одном государстве + + @passed + Сценарий: Жили-были дед да баба + Дано Step is 'passed' + Но Step is 'passed' + И Step is 'passed' + Когда Step is 'passed' + Тогда Step is 'passed' diff --git a/Tests.SpecRun/allureConfig.json b/Allure.Features/allureConfig.json similarity index 100% rename from Tests.SpecRun/allureConfig.json rename to Allure.Features/allureConfig.json diff --git a/Tests.SpecRun/runtests.cmd b/Allure.Features/runtests.cmd similarity index 100% rename from Tests.SpecRun/runtests.cmd rename to Allure.Features/runtests.cmd diff --git a/Allure.Features/specflow.json b/Allure.Features/specflow.json new file mode 100644 index 00000000..eaab3380 --- /dev/null +++ b/Allure.Features/specflow.json @@ -0,0 +1,13 @@ +{ + "stepAssemblies": [ + { + "assembly": "Allure.Features" + }, + { + "assembly": "Allure.SpecFlowPlugin" + }, + { + "assembly": "SpecFlowSharedSteps" + } + ] +} \ No newline at end of file diff --git a/Allure.SpecFlowPlugin.Tests/Allure.SpecFlowPlugin.Tests.csproj b/Allure.SpecFlowPlugin.Tests/Allure.SpecFlowPlugin.Tests.csproj index 84ae8804..ff42898d 100644 --- a/Allure.SpecFlowPlugin.Tests/Allure.SpecFlowPlugin.Tests.csproj +++ b/Allure.SpecFlowPlugin.Tests/Allure.SpecFlowPlugin.Tests.csproj @@ -1,12 +1,14 @@  - net461 + netcoreapp3.1 + false - + + - + diff --git a/Allure.SpecFlowPlugin.Tests/App.config b/Allure.SpecFlowPlugin.Tests/App.config deleted file mode 100644 index aee5c919..00000000 --- a/Allure.SpecFlowPlugin.Tests/App.config +++ /dev/null @@ -1,16 +0,0 @@ - - - -
- - - - - - - - - - - - \ No newline at end of file diff --git a/Allure.SpecFlowPlugin.Tests/ConfigurationTests.cs b/Allure.SpecFlowPlugin.Tests/ConfigurationTests.cs index 482553c2..a8a4ca93 100644 --- a/Allure.SpecFlowPlugin.Tests/ConfigurationTests.cs +++ b/Allure.SpecFlowPlugin.Tests/ConfigurationTests.cs @@ -1,5 +1,5 @@ -using NUnit.Framework; -using System.IO; +using System.IO; +using NUnit.Framework; namespace Allure.SpecFlowPlugin.Tests { @@ -23,4 +23,4 @@ public void ShouldNotHaveNullParents(string json) }); } } -} +} \ No newline at end of file diff --git a/Allure.SpecFlowPlugin.Tests/IntegrationTests.cs b/Allure.SpecFlowPlugin.Tests/IntegrationTests.cs index 52a98973..7d2bea01 100644 --- a/Allure.SpecFlowPlugin.Tests/IntegrationTests.cs +++ b/Allure.SpecFlowPlugin.Tests/IntegrationTests.cs @@ -1,73 +1,103 @@ -using Allure.Commons; -using Newtonsoft.Json; -using NUnit.Framework; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; +using Allure.Commons; +using Gherkin; +using Gherkin.Ast; +using Gherkin.Stream; +using Newtonsoft.Json; +using NUnit.Framework; namespace Allure.SpecFlowPlugin.Tests { [TestFixture] public class IntegrationFixture { - private const string SCENARIO_PATTERN = "Scenario:"; - private const string FEATURE_PATTERN = "Feature:"; - private readonly HashSet scenarioTitles = new HashSet(); - private HashSet allureContainers = new HashSet(); - private HashSet allureTestResults = new HashSet(); - private readonly HashSet specflowSuites = new HashSet(); + private readonly HashSet allureContainers = new HashSet(); + private readonly HashSet allureTestResults = new HashSet(); + private IEnumerable> scenariosByStatus; [OneTimeSetUp] public void Init() { - var testDirectory = @"..\..\..\..\Tests.SpecRun\bin\debug"; - var allureDirectory = $@"{testDirectory}\TestResults\allure-results"; - if (Directory.Exists(allureDirectory)) - Directory.Delete(allureDirectory, true); + var featuresDirectory = @"./../../../../Allure.Features/TestData"; + var testDirectory = @"./../../../../Allure.Features/bin/Debug/netcoreapp3.1"; + var allureDirectory = $@"{testDirectory}/allure-results"; + // if (Directory.Exists(allureDirectory)) + // Directory.Delete(allureDirectory, true); // run SpecFlow scenarios using SpecRun runner - var process = Process.Start($@"{testDirectory}\\runtests.cmd"); - process.WaitForExit(); + // var process = Process.Start($@"{testDirectory}\\runtests.cmd"); + // process.WaitForExit(); // parse allure suites ParseAllureSuites(allureDirectory); + ParseFeatures(featuresDirectory); } - [TestCase(Status.passed, 16)] - [TestCase(Status.failed, 1 * 2)] - [TestCase(Status.broken, 8 * 2 + 7)] - [TestCase(Status.skipped, 2)] - public void TestStatus(Status status, int count) + [TestCase(Status.passed)] + [TestCase(Status.failed)] + [TestCase(Status.broken)] + [TestCase(Status.skipped)] + public void TestStatus(Status status) { - var scenariosByStatus = allureTestResults.Where(x => x.status == status); - Assert.That(scenariosByStatus, Has.Exactly(count).Items, scenariosByStatus.Count().ToString()); + var expected = scenariosByStatus.FirstOrDefault(x => x.Key == status.ToString()).ToList(); + var actual = allureTestResults.Where(x => x.status == status).Select(x => x.name).ToList(); + Assert.That(actual, Is.EquivalentTo(expected)); } - [Test] - public void ShouldNotDuplicateBeforeFixtures() + + private void ParseFeatures(string featuresDir) { - var befores = allureContainers.Select(x => x.befores.Select(y => y.name)); - Assert.That(befores, Is.All.Unique); + var parser = new Parser(); + var scenarios = new List(); + var features = new DirectoryInfo(featuresDir).GetFiles("*.feature"); + scenarios.AddRange(features.SelectMany(f => parser.Parse(f.FullName).Feature.Children) + .Select(x => x as Scenario)); + + scenariosByStatus = + scenarios.GroupBy(x => x.Tags.FirstOrDefault(x => + Enum.GetNames(typeof(Status)).Contains(x.Name.Replace("@", "")))?.Name + .Replace("@", "") ?? + "_notag_", x => x.Name); } - [Test] - public void ShouldNotDuplicateAfterFixtures() + private void ParseAllureSuites(string allureResultsDir) { - var afters = allureContainers.Select(x => x.afters.Select(y => y.name)); - Assert.That(afters, Is.All.Unique); + var allureTestResultFiles = new DirectoryInfo(allureResultsDir).GetFiles("*-result.json"); + var allureContainerFiles = new DirectoryInfo(allureResultsDir).GetFiles("*-container.json"); + var serializer = new JsonSerializer(); + + foreach (var fileInfo in allureContainerFiles) + { + using var file = File.OpenText(fileInfo.FullName); + var container = (TestResultContainer) serializer.Deserialize(file, typeof(TestResultContainer)); + allureContainers.Add(container); + } + + foreach (var fileInfo in allureTestResultFiles) + { + using var file = File.OpenText(fileInfo.FullName); + var testResult = (TestResult) serializer.Deserialize(file, typeof(TestResult)); + allureTestResults.Add(testResult); + } } + [Test] - public void AllScenariosWithFailureTagShouldBeBroken() + public void ShouldConvertTableToStepParams() { - var withFailureTags = allureTestResults - .Where(x => x.labels - .Any(l => l.name == Label.Tag("").name && l.value.EndsWith("failed") && l.value != "afterfeaturefailed")) - .Select(x => x.status); - Assert.That(withFailureTags, Is.All.EqualTo(Status.broken)); + var parameters = allureTestResults + .First(x => x.name == "Table arguments").steps.SelectMany(s => s.parameters); + + Assert.That(parameters.Select(x => x.name), Has.Exactly(1).EqualTo("name")); + Assert.That(parameters.Select(x => x.name), Has.Exactly(1).EqualTo("surname")); + Assert.That(parameters.Select(x => x.name), Has.Exactly(2).EqualTo("width")); + Assert.That(parameters.Select(x => x.name), Has.Exactly(0).EqualTo("attribute")); } [Test] @@ -81,87 +111,59 @@ public void ShouldGroupNestedSteps() } [Test] - public void ShouldConvertTableToStepParams() + public void ShouldNotDuplicateAfterFixtures() { - var parameters = allureTestResults - .First(x => x.name == "Table arguments").steps. - SelectMany(s => s.parameters); - - Assert.That(parameters.Select(x => x.name), Has.Exactly(1).EqualTo("name")); - Assert.That(parameters.Select(x => x.name), Has.Exactly(1).EqualTo("surname")); - Assert.That(parameters.Select(x => x.name), Has.Exactly(2).EqualTo("width")); - Assert.That(parameters.Select(x => x.name), Has.Exactly(0).EqualTo("attribute")); - - + var afters = allureContainers.Select(x => x.afters.Select(y => y.name)); + Assert.That(afters, Is.All.Unique); + } + [Test] + public void ShouldNotDuplicateBeforeFixtures() + { + var befores = allureContainers.Select(x => x.befores.Select(y => y.name)); + Assert.That(befores, Is.All.Unique); } + [Test] - public void ShouldParseTags() + public void ShouldParseLinks() { var scenarios = allureTestResults .Where(x => x.labels.Any(l => l.value == "labels")); - var labels = scenarios.SelectMany(x => x.labels); + var links = scenarios.SelectMany(x => x.links); Assert.Multiple(() => { - // ummatched tags - Assert.That(labels.Where(x => x.name == "tag"), Has.Exactly(scenarios.Count() + 1).Items); - // owner - Assert.That(labels.Where(x => x.value == "Vasya").Select(l => l.name), - Has.Exactly(scenarios.Count()).Items.And.All.EqualTo("owner")); - + Assert.That(links.Select(x => x.url), Has.One.EqualTo("http://example.org")); + Assert.That(links.Where(x => x.type == "tms").Select(x => x.url), + Has.One.EqualTo("https://example.org/234")); + Assert.That(links.Where(x => x.type == "issue").Select(x => x.url), + Has.One.EqualTo("https://example.org/999999").And.One.EqualTo("https://example.org/123")); }); - } [Test] - public void ShouldParseLinks() + public void ShouldParseTags() { var scenarios = allureTestResults .Where(x => x.labels.Any(l => l.value == "labels")); - var links = scenarios.SelectMany(x => x.links); + var labels = scenarios.SelectMany(x => x.labels); Assert.Multiple(() => { - Assert.That(links.Select(x => x.url), Has.One.EqualTo("http://example.org")); - Assert.That(links.Where(x => x.type == "tms").Select(x => x.url), Has.One.EqualTo("https://example.org/234")); - Assert.That(links.Where(x => x.type == "issue").Select(x => x.url), Has.One.EqualTo("https://example.org/999999").And.One.EqualTo("https://example.org/123")); - - - + // all selected scenarios should have only 2 unmatched tags - "labels" and "passed". One scenario also has "tag1" as unmatched. + Assert.That(labels.Where(x => x.name == "tag"), Has.Exactly(scenarios.Count() * 2 + 1).Items); + // owner + Assert.That(labels.Where(x => x.value == "Vasya").Select(l => l.name), + Has.Exactly(scenarios.Count()).Items.And.All.EqualTo("owner")); }); - } + [Test] public void ShouldReadHostNameFromConfigTitle() { - var hostNames = allureTestResults.SelectMany(x => x.labels.Where(l => l.name == "host").Select(l => l.value)).Distinct(); + var hostNames = allureTestResults + .SelectMany(x => x.labels.Where(l => l.name == "host").Select(l => l.value)).Distinct(); Assert.That(hostNames, Has.One.Items.And.All.EqualTo("5994A3F7-AF84-46AD-9393-000BB45553CC")); } - private void ParseAllureSuites(string allureResultsDir) - { - var allureTestResultFiles = new DirectoryInfo(allureResultsDir).GetFiles("*-result.json"); - var allureContainerFiles = new DirectoryInfo(allureResultsDir).GetFiles("*-container.json"); - var serializer = new JsonSerializer(); - - foreach (var fileInfo in allureContainerFiles) - { - using (var file = File.OpenText(fileInfo.FullName)) - { - var container = (TestResultContainer)serializer.Deserialize(file, typeof(TestResultContainer)); - allureContainers.Add(container); - } - } - - foreach (var fileInfo in allureTestResultFiles) - { - using (var file = File.OpenText(fileInfo.FullName)) - { - var testResult = (TestResult)serializer.Deserialize(file, typeof(TestResult)); - allureTestResults.Add(testResult); - } - } - - } } -} +} \ No newline at end of file diff --git a/Allure.SpecFlowPlugin.Tests/TestSetup.cs b/Allure.SpecFlowPlugin.Tests/TestSetup.cs index 500e3ba0..8f46e4be 100644 --- a/Allure.SpecFlowPlugin.Tests/TestSetup.cs +++ b/Allure.SpecFlowPlugin.Tests/TestSetup.cs @@ -1,10 +1,6 @@ -using NUnit.Framework; -using System; -using System.Collections.Generic; +using System; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using NUnit.Framework; namespace Allure.SpecFlowPlugin.Tests { @@ -18,4 +14,4 @@ public void Setup() Environment.CurrentDirectory = Path.GetDirectoryName(typeof(TestSetup).Assembly.Location); } } -} +} \ No newline at end of file diff --git a/Allure.SpecFlowPlugin/Allure.SpecFlowPlugin.csproj b/Allure.SpecFlowPlugin/Allure.SpecFlowPlugin.csproj index 3aca1f0b..6afcf064 100644 --- a/Allure.SpecFlowPlugin/Allure.SpecFlowPlugin.csproj +++ b/Allure.SpecFlowPlugin/Allure.SpecFlowPlugin.csproj @@ -1,35 +1,33 @@  - net45 + net45;netstandard2.0 SpecFlow.Allure - 2.4.2.4 + 3.0.0 Alexander Bakanov Generates Allure report result files for SpecFlow test run https://github.com/allure-framework/allure-csharp/wiki/SpecFlow-Adapter - https://raw.githubusercontent.com/allure-framework/allure-csharp/master/img/allure-specflow.png + allure-specflow.png https://github.com/allure-framework/allure-csharp specflow allure true - - 2.4.2.4 - 2.4.2.4 + 8 - + - - - + + + diff --git a/Allure.SpecFlowPlugin/AllureBindingInvoker.cs b/Allure.SpecFlowPlugin/AllureBindingInvoker.cs index c5ea86d3..bd475c12 100644 --- a/Allure.SpecFlowPlugin/AllureBindingInvoker.cs +++ b/Allure.SpecFlowPlugin/AllureBindingInvoker.cs @@ -1,9 +1,10 @@ -using Allure.Commons; -using CsvHelper; -using System; +using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; +using Allure.Commons; +using CsvHelper; using TechTalk.SpecFlow; using TechTalk.SpecFlow.Bindings; using TechTalk.SpecFlow.Configuration; @@ -13,22 +14,21 @@ namespace Allure.SpecFlowPlugin { - class AllureBindingInvoker : BindingInvoker + internal class AllureBindingInvoker : BindingInvoker { - static AllureLifecycle allure = AllureLifecycle.Instance; + private static readonly AllureLifecycle allure = AllureLifecycle.Instance; - public AllureBindingInvoker(SpecFlowConfiguration specFlowConfiguration, IErrorProvider errorProvider) : base( - specFlowConfiguration, errorProvider) + public AllureBindingInvoker(SpecFlowConfiguration specFlowConfiguration, IErrorProvider errorProvider, + ISynchronousBindingDelegateInvoker synchronousBindingDelegateInvoker) : base( + specFlowConfiguration, errorProvider, synchronousBindingDelegateInvoker) { } public override object InvokeBinding(IBinding binding, IContextManager contextManager, object[] arguments, ITestTracer testTracer, out TimeSpan duration) { - var hook = binding as HookBinding; - // process hook - if (hook != null) + if (binding is HookBinding hook) { var featureContainerId = PluginHelper.GetFeatureContainerId(contextManager.FeatureContext?.FeatureInfo); @@ -50,9 +50,10 @@ public override object InvokeBinding(IBinding binding, IContextManager contextMa return base.InvokeBinding(binding, contextManager, arguments, testTracer, out duration); } else + { try { - this.StartFixture(hook, featureContainerId); + StartFixture(hook, featureContainerId); var result = base.InvokeBinding(binding, contextManager, arguments, testTracer, out duration); allure.StopFixture(x => x.status = Status.passed); @@ -62,7 +63,7 @@ public override object InvokeBinding(IBinding binding, IContextManager contextMa { allure.StopFixture(x => x.status = Status.broken); - // if BeforeFeature is failed execution is stopped. We need to create, update, stop and write everything here. + // if BeforeFeature is failed, execution is already stopped. We need to create, update, stop and write everything here. // create fake scenario container var scenarioContainer = @@ -78,7 +79,6 @@ public override object InvokeBinding(IBinding binding, IContextManager contextMa { x.status = Status.broken; x.statusDetails = PluginHelper.GetStatusDetails(ex); - }) .WriteTestCase(scenario.uuid) .StopTestContainer(scenarioContainer.uuid) @@ -88,40 +88,43 @@ public override object InvokeBinding(IBinding binding, IContextManager contextMa throw; } + } case HookType.BeforeStep: case HookType.AfterStep: - { - var scenario = PluginHelper.GetCurrentTestCase(contextManager.ScenarioContext); + { + var scenario = PluginHelper.GetCurrentTestCase(contextManager.ScenarioContext); - try - { - return base.InvokeBinding(binding, contextManager, arguments, testTracer, out duration); - } - catch (Exception ex) - { - allure - .UpdateTestCase(scenario.uuid, - x => - { - x.status = Status.broken; - x.statusDetails = PluginHelper.GetStatusDetails(ex); - }); - throw; - } + try + { + return base.InvokeBinding(binding, contextManager, arguments, testTracer, out duration); } + catch (Exception ex) + { + allure + .UpdateTestCase(scenario.uuid, + x => + { + x.status = Status.broken; + x.statusDetails = PluginHelper.GetStatusDetails(ex); + }); + throw; + } + } case HookType.BeforeScenario: case HookType.AfterScenario: if (hook.HookOrder == int.MinValue || hook.HookOrder == int.MaxValue) + { return base.InvokeBinding(binding, contextManager, arguments, testTracer, out duration); + } else { var scenarioContainer = PluginHelper.GetCurrentTestConainer(contextManager.ScenarioContext); try { - this.StartFixture(hook, scenarioContainer.uuid); + StartFixture(hook, scenarioContainer.uuid); var result = base.InvokeBinding(binding, contextManager, arguments, testTracer, out duration); allure.StopFixture(x => x.status = Status.passed); @@ -129,8 +132,9 @@ public override object InvokeBinding(IBinding binding, IContextManager contextMa } catch (Exception ex) { - var status = (ex.GetType().Name.Contains(PluginHelper.IGNORE_EXCEPTION)) ? - Status.skipped : Status.broken; + var status = ex.GetType().Name.Contains(PluginHelper.IGNORE_EXCEPTION) + ? Status.skipped + : Status.broken; allure.StopFixture(x => x.status = status); @@ -151,7 +155,7 @@ public override object InvokeBinding(IBinding binding, IContextManager contextMa case HookType.AfterFeature: if (hook.HookOrder == int.MaxValue) - // finish point + // finish point { WriteScenarios(contextManager); allure @@ -200,10 +204,8 @@ public override object InvokeBinding(IBinding binding, IContextManager contextMa return base.InvokeBinding(binding, contextManager, arguments, testTracer, out duration); } } - else - { - return base.InvokeBinding(binding, contextManager, arguments, testTracer, out duration); - } + + return base.InvokeBinding(binding, contextManager, arguments, testTracer, out duration); } private void StartFixture(HookBinding hook, string containerId) @@ -226,39 +228,29 @@ private static void StartStep(StepInfo stepInfo, string containerId) if (stepInfo.Table != null) { var csvFile = $"{Guid.NewGuid().ToString()}.csv"; - using (var csv = new CsvWriter(File.CreateText(csvFile))) + using (var csv = new CsvWriter(File.CreateText(csvFile),CultureInfo.InvariantCulture)) { - foreach (var item in stepInfo.Table.Header) - { - csv.WriteField(item); - } + foreach (var item in stepInfo.Table.Header) csv.WriteField(item); csv.NextRecord(); foreach (var row in stepInfo.Table.Rows) { - foreach (var item in row.Values) - { - csv.WriteField(item); - } + foreach (var item in row.Values) csv.WriteField(item); csv.NextRecord(); } } + allure.AddAttachment("table", "text/csv", csvFile); } } private static void WriteScenarios(IContextManager contextManager) { - foreach (var s in contextManager.FeatureContext.Get>()) - { - allure.WriteTestCase(s.uuid); - } + foreach (var s in contextManager.FeatureContext.Get>()) allure.WriteTestCase(s.uuid); foreach (var c in contextManager.FeatureContext.Get>()) - { allure .StopTestContainer(c.uuid) .WriteTestContainer(c.uuid); - } } } } \ No newline at end of file diff --git a/Allure.SpecFlowPlugin/AllureBindings.cs b/Allure.SpecFlowPlugin/AllureBindings.cs index 6a9c965a..c9cc0c82 100644 --- a/Allure.SpecFlowPlugin/AllureBindings.cs +++ b/Allure.SpecFlowPlugin/AllureBindings.cs @@ -6,10 +6,10 @@ namespace Allure.SpecFlowPlugin [Binding] public class AllureBindings { - static AllureLifecycle allure = AllureLifecycle.Instance; + private static readonly AllureLifecycle allure = AllureLifecycle.Instance; - private FeatureContext featureContext; - private ScenarioContext scenarioContext; + private readonly FeatureContext featureContext; + private readonly ScenarioContext scenarioContext; public AllureBindings(FeatureContext featureContext, ScenarioContext scenarioContext) { @@ -52,7 +52,7 @@ public void FirstAfterScenario() // update status to passed if there were no step of binding failures allure .UpdateTestCase(scenarioId, - x => x.status = (x.status != Status.none) ? x.status : Status.passed) + x => x.status = x.status != Status.none ? x.status : Status.passed) .StopTestCase(scenarioId); } } diff --git a/Allure.SpecFlowPlugin/AllurePlugin.cs b/Allure.SpecFlowPlugin/AllurePlugin.cs index c2d797d9..3137943e 100644 --- a/Allure.SpecFlowPlugin/AllurePlugin.cs +++ b/Allure.SpecFlowPlugin/AllurePlugin.cs @@ -1,14 +1,19 @@ -using TechTalk.SpecFlow.Plugins; +using System; +using System.IO; +using Allure.SpecFlowPlugin; using TechTalk.SpecFlow.Bindings; +using TechTalk.SpecFlow.Plugins; using TechTalk.SpecFlow.Tracing; +using TechTalk.SpecFlow.UnitTestProvider; -[assembly: RuntimePlugin(typeof(Allure.SpecFlowPlugin.AllurePlugin))] +[assembly: RuntimePlugin(typeof(AllurePlugin))] namespace Allure.SpecFlowPlugin { public class AllurePlugin : IRuntimePlugin { - public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginParameters runtimePluginParameters) + public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginParameters runtimePluginParameters, + UnitTestProviderConfiguration unitTestProviderConfiguration) { runtimePluginEvents.CustomizeGlobalDependencies += (sender, args) => args.ObjectContainer.RegisterTypeAs(); diff --git a/Allure.SpecFlowPlugin/AllureTestTracerWrapper.cs b/Allure.SpecFlowPlugin/AllureTestTracerWrapper.cs index f09711fb..ff5baa4f 100644 --- a/Allure.SpecFlowPlugin/AllureTestTracerWrapper.cs +++ b/Allure.SpecFlowPlugin/AllureTestTracerWrapper.cs @@ -1,12 +1,12 @@ -using Allure.Commons; -using CsvHelper; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; +using Allure.Commons; +using CsvHelper; using TechTalk.SpecFlow; using TechTalk.SpecFlow.Bindings; using TechTalk.SpecFlow.BindingSkeletons; @@ -17,9 +17,9 @@ namespace Allure.SpecFlowPlugin { public class AllureTestTracerWrapper : TestTracer, ITestTracer { - readonly string noMatchingStepMessage = "No matching step definition found for the step"; - static AllureLifecycle allure = AllureLifecycle.Instance; - static PluginConfiguration pluginConfiguration = PluginHelper.PluginConfiguration; + private static readonly AllureLifecycle allure = AllureLifecycle.Instance; + private static readonly PluginConfiguration pluginConfiguration = PluginHelper.PluginConfiguration; + private readonly string noMatchingStepMessage = "No matching step definition found for the step"; public AllureTestTracerWrapper(ITraceListener traceListener, IStepFormatter stepFormatter, IStepDefinitionSkeletonProvider stepDefinitionSkeletonProvider, SpecFlowConfiguration specFlowConfiguration) @@ -64,10 +64,10 @@ void ITestTracer.TraceNoMatchingStepDefinition(StepInstance stepInstance, Progra TraceNoMatchingStepDefinition(stepInstance, targetLanguage, bindingCulture, matchesWithoutScopeCheck); allure.StopStep(x => x.status = Status.broken); allure.UpdateTestCase(x => - { - x.status = Status.broken; - x.statusDetails = new StatusDetails { message = noMatchingStepMessage }; - }); + { + x.status = Status.broken; + x.statusDetails = new StatusDetails {message = noMatchingStepMessage}; + }); } private void StartStep(StepInstance stepInstance) @@ -87,7 +87,7 @@ private void StartStep(StepInstance stepInstance) ".txt"); var table = stepInstance.TableArgument; - bool isTableProcessed = (table == null); + var isTableProcessed = table == null; // parse table as step params if (table != null) @@ -101,25 +101,21 @@ private void StartStep(StepInstance stepInstance) if (table.Header.Count == 2) { var paramNameMatch = Regex.IsMatch(header[0], pluginConfiguration.stepArguments.paramNameRegex); - var paramValueMatch = Regex.IsMatch(header[1], pluginConfiguration.stepArguments.paramValueRegex); + var paramValueMatch = + Regex.IsMatch(header[1], pluginConfiguration.stepArguments.paramValueRegex); if (paramNameMatch && paramValueMatch) { - for (int i = 0; i < table.RowCount; i++) - { - parameters.Add(new Parameter { name = table.Rows[i][0], value = table.Rows[i][1] }); - } + for (var i = 0; i < table.RowCount; i++) + parameters.Add(new Parameter {name = table.Rows[i][0], value = table.Rows[i][1]}); isTableProcessed = true; } - } // add step params for 1 row table else if (table.RowCount == 1) { - for (int i = 0; i < table.Header.Count; i++) - { - parameters.Add(new Parameter { name = header[i], value = table.Rows[0][i] }); - } + for (var i = 0; i < table.Header.Count; i++) + parameters.Add(new Parameter {name = header[i], value = table.Rows[0][i]}); isTableProcessed = true; } @@ -130,28 +126,19 @@ private void StartStep(StepInstance stepInstance) allure.StartStep(PluginHelper.NewId(), stepResult); // add csv table for multi-row table if was not processed as params already - if (!isTableProcessed) + if (isTableProcessed) return; + using var sw = new StringWriter(); + using var csv = new CsvWriter(sw, CultureInfo.InvariantCulture); + foreach (var item in table.Header) csv.WriteField(item); + csv.NextRecord(); + foreach (var row in table.Rows) { - using (var sw = new StringWriter()) - using (var csv = new CsvWriter(sw)) - { - foreach (var item in table.Header) - { - csv.WriteField(item); - } - csv.NextRecord(); - foreach (var row in table.Rows) - { - foreach (var item in row.Values) - { - csv.WriteField(item); - } - csv.NextRecord(); - } - allure.AddAttachment("table", "text/csv", - Encoding.ASCII.GetBytes(sw.ToString()), ".csv"); - } + foreach (var item in row.Values) csv.WriteField(item); + csv.NextRecord(); } + + allure.AddAttachment("table", "text/csv", + Encoding.ASCII.GetBytes(sw.ToString()), ".csv"); } private static void FailScenario(Exception ex) @@ -159,7 +146,7 @@ private static void FailScenario(Exception ex) allure.UpdateTestCase( x => { - x.status = (x.status != Status.none) ? x.status : Status.failed; + x.status = x.status != Status.none ? x.status : Status.failed; x.statusDetails = PluginHelper.GetStatusDetails(ex); }); } diff --git a/Allure.SpecFlowPlugin/PluginConfiguration.cs b/Allure.SpecFlowPlugin/PluginConfiguration.cs index 76c5c4c9..14e88c8c 100644 --- a/Allure.SpecFlowPlugin/PluginConfiguration.cs +++ b/Allure.SpecFlowPlugin/PluginConfiguration.cs @@ -54,5 +54,4 @@ public class Links public string issue { get; set; } public string tms { get; set; } } - -} +} \ No newline at end of file diff --git a/Allure.SpecFlowPlugin/PluginHelper.cs b/Allure.SpecFlowPlugin/PluginHelper.cs index a908e50f..b6b3c358 100644 --- a/Allure.SpecFlowPlugin/PluginHelper.cs +++ b/Allure.SpecFlowPlugin/PluginHelper.cs @@ -1,10 +1,10 @@ -using Allure.Commons; -using Newtonsoft.Json.Linq; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; +using Allure.Commons; +using Newtonsoft.Json.Linq; using TechTalk.SpecFlow; using TechTalk.SpecFlow.Bindings; @@ -13,11 +13,13 @@ namespace Allure.SpecFlowPlugin public static class PluginHelper { public static string IGNORE_EXCEPTION = "IgnoreException"; - private static readonly ScenarioInfo emptyScenarioInfo = new ScenarioInfo(string.Empty, string.Empty); - private static FeatureInfo emptyFeatureInfo = new FeatureInfo( + private static readonly ScenarioInfo emptyScenarioInfo = new ScenarioInfo("Unknown", string.Empty); + + private static readonly FeatureInfo emptyFeatureInfo = new FeatureInfo( CultureInfo.CurrentCulture, string.Empty, string.Empty); - internal static PluginConfiguration PluginConfiguration = GetConfiguration(AllureLifecycle.Instance.JsonConfiguration); + internal static PluginConfiguration PluginConfiguration = + GetConfiguration(AllureLifecycle.Instance.JsonConfiguration); public static PluginConfiguration GetConfiguration(string allureConfiguration) { @@ -27,9 +29,10 @@ public static PluginConfiguration GetConfiguration(string allureConfiguration) config = specflowSection.ToObject(); return config; } + internal static string GetFeatureContainerId(FeatureInfo featureInfo) { - var id = (featureInfo != null) + var id = featureInfo != null ? featureInfo.GetHashCode().ToString() : emptyFeatureInfo.GetHashCode().ToString(); @@ -64,7 +67,9 @@ internal static TestResult StartTestCase(string containerId, FeatureContext feat labels = new List