Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 9 additions & 12 deletions E2E/TestFramework/BDD/Feature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,11 @@ import SwiftHamcrest

open class Feature: XCTestCase {
let id: String = UUID().uuidString
open var currentScenario: Scenario? = nil

open func title() -> String {
fatalError("Set feature title")
}

open func description() -> String {
return ""
}
public var currentScenario: Scenario? = nil

open var tags: [String] { return [] }
open var title: String { fatalError("Set feature title") }
open var narrative: String { return "" }

/// our lifecycle starts after xctest is ending
public override func tearDown() async throws {
Expand Down Expand Up @@ -49,7 +45,7 @@ open class Feature: XCTestCase {

func run() async throws {
let currentTestMethodName = self.name
if currentScenario == nil {
guard let scenario = currentScenario else {
let rawMethodName = currentTestMethodName.split(separator: " ").last?.dropLast() ?? "yourTestMethod"

let errorMessage = """
Expand All @@ -66,9 +62,10 @@ open class Feature: XCTestCase {
"""
throw ConfigurationError.missingScenario(errorMessage)
}
if currentScenario!.disabled {
throw XCTSkip("Scenario '\(currentScenario!.name)' in test method \(currentTestMethodName) is disabled.")
if scenario.disabled {
throw XCTSkip("Scenario '\(scenario.name)' in test method \(currentTestMethodName) is disabled.")
}

try await TestConfiguration.setUpInstance()

if let parameterizedScenario = currentScenario as? ParameterizedScenario {
Expand Down
10 changes: 8 additions & 2 deletions E2E/TestFramework/BDD/Scenario.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ public class Scenario {
var disabled: Bool = false
var feature: Feature?
var parameters: [String: String]?

var tags: [String] = []

private var lastContext: String = ""

public init(_ title: String, parameters: [String: String] = [:]) {
Expand All @@ -31,6 +32,11 @@ public class Scenario {
steps.append(stepInstance)
}

public func tags(_ tags: String...) -> Scenario {
self.tags.append(contentsOf: tags)
return self
}

public func given(_ step: String) -> Scenario {
lastContext = "Given"
addStep(step)
Expand All @@ -54,7 +60,7 @@ public class Scenario {
addStep(step)
return self
}

public func and(_ step: String) -> Scenario {
if (lastContext.isEmpty) {
fatalError("Trying to add an [and] step without previous context.")
Expand Down
62 changes: 62 additions & 0 deletions E2E/TestFramework/Configuration/TagFilter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import Foundation

struct TagFilter {
private let expression: String

/// Initializes the filter with the raw tag expression string from the environment.
init(from expression: String?) {
self.expression = expression?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
}

/// Determines if a scenario with the given tags should be executed based on the expression.
func shouldRun(scenarioTags: [String]) -> Bool {
// If there's no expression, run everything.
if expression.isEmpty {
return true
}

let scenarioTagSet = Set(scenarioTags)

// Split by "or" to handle the lowest precedence operator first.
// If any of these OR clauses are true, the whole expression is true.
let orClauses = expression.components(separatedBy: " or ")

for orClause in orClauses {
// Check if this "AND" group is satisfied.
if evaluateAndClause(clause: orClause, scenarioTags: scenarioTagSet) {
return true
}
}

// If none of the OR clauses were satisfied, the expression is false.
return false
}

/// Evaluates a sub-expression containing only "and" and "not" conditions.
/// This clause is true only if ALL of its conditions are met.
private func evaluateAndClause(clause: String, scenarioTags: Set<String>) -> Bool {
let andConditions = clause.components(separatedBy: " and ")

for condition in andConditions {
if !evaluateCondition(condition: condition, scenarioTags: scenarioTags) {
return false // If any condition is false, the whole AND clause is false.
}
}

// If all conditions passed, the AND clause is true.
return true
}

/// Evaluates a single tag condition (e.g., "smoke" or "not wip").
private func evaluateCondition(condition: String, scenarioTags: Set<String>) -> Bool {
let trimmedCondition = condition.trimmingCharacters(in: .whitespacesAndNewlines)

if trimmedCondition.hasPrefix("not ") {
let tag = String(trimmedCondition.dropFirst(4))
return !scenarioTags.contains(tag)
} else {
let tag = trimmedCondition
return scenarioTags.contains(tag)
}
}
}
29 changes: 21 additions & 8 deletions E2E/TestFramework/Configuration/TestConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,19 @@ open class TestConfiguration: ITestConfiguration {
}

func runSteps(_ scenario: Scenario) async throws -> ScenarioOutcome {
let tagString = TestConfiguration.shared().environment["TAGS"]
let tagFilter = TagFilter(from: tagString)
let scenarioTags = scenario.feature!.tags + scenario.tags
if !tagFilter.shouldRun(scenarioTags: scenarioTags) {
scenario.disabled = true
}
if scenario.disabled {
return ScenarioOutcome(scenario)
let outcome = ScenarioOutcome(scenario)
outcome.status = .skipped
return outcome
}


let scenarioOutcome = ScenarioOutcome(scenario)
scenarioOutcome.start()

Expand Down Expand Up @@ -222,6 +231,9 @@ open class TestConfiguration: ITestConfiguration {
currentFeatureOut.scenarioOutcomes.append(scenarioOutcome)
try await report(.AFTER_SCENARIO, scenarioOutcome)
try await tearDownActors()
if (scenarioOutcome.status == .skipped) {
throw XCTSkip()
}
}

public func afterFeature(_ featureOutcome: FeatureOutcome) async throws {
Expand Down Expand Up @@ -335,13 +347,14 @@ open class TestConfiguration: ITestConfiguration {
instance.suiteOutcome.start()
self.instance = instance

do {
try await instance.setUp()
try await instance.setUpReporters()
try await instance.setUpSteps()
} catch {
throw ConfigurationError.setup(message: error.localizedDescription)
}
print("Setting up configuration instance")
try await instance.setUp()

print("Setting up reporters")
try await instance.setUpReporters()

print("Setting up steps")
try await instance.setUpSteps()

/// setup hamcrest to update variable if failed
HamcrestReportFunction = { message, file, line in
Expand Down
6 changes: 0 additions & 6 deletions E2E/TestFramework/Errors/ConfigurationError.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
open class ConfigurationError {
public final class setup: BaseError {
public init(message: String, file: StaticString = #file, line: UInt = #line) {
super.init(message: message, error: "Configuration error", file: file, line: line)
}
}

public final class missingScenario: Error, CustomStringConvertible {
public var errorDescription: String
public var failureReason: String = "Missing scenario"
Expand Down
2 changes: 1 addition & 1 deletion E2E/TestFramework/Outcome/FeatureOutcome.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public class FeatureOutcome {
} else {
// This case should ideally not be reached if the above logic is complete.
// Could default to .broken if there's an unexpected mix.
print("Warning: FeatureOutcome for '\(feature.title())' has an undetermined status mix.")
// print("Warning: FeatureOutcome for '\(feature.title())' has an undetermined status mix.")
self.status = .broken // Default for unexpected mixed states
}
}
Expand Down
6 changes: 3 additions & 3 deletions E2E/TestFramework/Report/AllureReporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -266,14 +266,14 @@ public class AllureReporter: Reporter {
testCaseId: calculatedTestCaseId,
name: scenario.name,
fullName: scenarioUniqueName,
description: scenario.feature?.description(),
description: scenario.feature?.narrative,
labels: [
AllureLabel(name: "host", value: ProcessInfo.processInfo.hostName),
AllureLabel(name: "thread", value: getCurrentPid()),
AllureLabel(name: "package", value: generatePackageName(fromFeatureType: type(of: scenario.feature!))),
AllureLabel(name: "language", value: "swift"),
AllureLabel(name: "framework", value: "identus-e2e-framework"),
AllureLabel(name: "feature", value: scenario.feature!.title()),
AllureLabel(name: "feature", value: scenario.feature!.title),
//AllureLabel(name: "suite", value: "suite"), // FIXME: property? config?
//AllureLabel(name: "epic", value: "suite"), // FIXME: property? config?
//AllureLabel(name: "story", value: scenario.name)
Expand Down Expand Up @@ -386,7 +386,7 @@ public class AllureReporter: Reporter {
container.stop = millisecondsSince1970(from: featureOutcome.endTime)
if (featureOutcome.status == .broken || featureOutcome.status == .failed),
let featureErr = featureOutcome.error { // From your FeatureOutcome model
let fixtureName = "Feature Level Issue: \(featureOutcome.feature.title())"
let fixtureName = "Feature Level Issue: \(featureOutcome.feature.title)"
let problemFixture = AllureFixtureResult(
name: fixtureName,
status: mapFrameworkStatusToAllureStatus(featureOutcome.status),
Expand Down
2 changes: 1 addition & 1 deletion E2E/TestFramework/Report/ConsoleReporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class ConsoleReporter: Reporter {
public func beforeFeature(_ feature: Feature) async throws {
print()
print("---")
print("Feature:", feature.title())
print("Feature:", feature.title)
}

public func beforeScenario(_ scenario: Scenario) async throws {
Expand Down
4 changes: 2 additions & 2 deletions E2E/TestFramework/Report/DebugReporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class DebugReporter: Reporter {
public required init() {}

public func beforeFeature(_ feature: Feature) async throws {
if debug { print("Before Feature:", feature.title()) }
if debug { print("Before Feature:", feature.title) }
}

public func beforeScenario(_ scenario: Scenario) async throws {
Expand Down Expand Up @@ -35,7 +35,7 @@ public class DebugReporter: Reporter {
}

public func afterFeature(_ featureOutcome: FeatureOutcome) async throws {
print("After Feature", featureOutcome.feature.title())
print("After Feature", featureOutcome.feature.title)
}

public func afterFeatures(_ featuresOutcome: [FeatureOutcome]) async throws {
Expand Down
2 changes: 1 addition & 1 deletion E2E/TestFramework/Report/DotReporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class DotReporter: Reporter {
public func afterFeatures(_ featuresOutcome: [FeatureOutcome]) async throws {
print("Executed", featuresOutcome.count, "features")
for featureOutcome in featuresOutcome {
print(" ", "Feature:", featureOutcome.feature.title())
print(" ", "Feature:", featureOutcome.feature.title)
for scenarioOutcome in featureOutcome.scenarioOutcomes {
print(
" ",
Expand Down
2 changes: 1 addition & 1 deletion E2E/TestFramework/Report/HtmlReporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public class HtmlReporter: Reporter {
let htmlReport: HtmlReport = HtmlReport()
for featureOutcome in featuresOutcome {
let featureReport = FeatureReport()
featureReport.name = featureOutcome.feature.title()
featureReport.name = featureOutcome.feature.title
htmlReport.data.append(featureReport)

for scenarioOutcome in featureOutcome.scenarioOutcomes {
Expand Down
2 changes: 1 addition & 1 deletion E2E/TestFramework/Report/JunitReporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class JunitReporter: Reporter {
featureFailures = 0

let id = XMLNode.attribute(withName: "id", stringValue: feature.id) as! XMLNode
let name = XMLNode.attribute(withName: "name", stringValue: feature.title()) as! XMLNode
let name = XMLNode.attribute(withName: "name", stringValue: feature.title) as! XMLNode

currentFeature.addAttribute(id)
currentFeature.addAttribute(name)
Expand Down
2 changes: 1 addition & 1 deletion E2E/TestFramework/Report/SummaryReporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public class SummaryReporter: Reporter {

for featureOutcome in featuresOutcome { // Corrected Swift loop syntax
totalFeaturesExecuted += 1
let featureTitle = featureOutcome.feature.title()
let featureTitle = featureOutcome.feature.title
let featureStatusString = featureOutcome.status.rawValue.uppercased()
let featureDurationString = String(format: "%.2fs", featureOutcome.duration ?? 0.0)

Expand Down
12 changes: 1 addition & 11 deletions E2E/Tests/Source/Abilities/DidcommAgentAbility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,17 +164,7 @@ class DidcommAgentAbility: Ability {
let did = (jsonData["from"] as? String)!
return try DID(string: did)
}

static private func getRootsMediatorDid() async throws -> DID {
let url = URL(string: Config.mediatorOobUrl)!
let invitationUrl: String = try await Api.get(from: url)
let base64data: String = String(invitationUrl.split(separator: "?_oob=").last!)
let decodedData = Data(base64Encoded: base64data)!
let json = try (JSONSerialization.jsonObject(with: decodedData, options: []) as? [String: Any])!
let from = (json["from"] as? String)!
return try DID(string: from)
}


private static func fromBase64(_ encoded: String) -> Data {
var encoded = encoded;
let remainder = encoded.count % 4
Expand Down
14 changes: 5 additions & 9 deletions E2E/Tests/Source/Features/Backup.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import Foundation
import TestFramework

final class BackupFeature: Feature {
override func title() -> String {
"Backup"
}

override func description() -> String {
"The Edge Agent should be able to create and restore a backup"
}
final class Backup: Feature {
override var tags: [String] { ["backup"] }
override var title: String { "Backup" }
override var narrative: String { "The Edge Agent should be able to create and restore a backup" }

func testCreateAndRestoreABackup() async throws {
currentScenario = Scenario("Create and restore a backup")
Expand All @@ -18,6 +13,7 @@ final class BackupFeature: Feature {

func testAgentWithoutProperSeedShouldNotBeAbleToRestoreTheBackup() async throws {
currentScenario = Scenario("Agent without a seed should not be able to restore the backup")
.tags("quick")
.given("Edge Agent has created a backup")
.then("a new Restored Agent cannot be restored from Edge Agent with wrong seed")
}
Expand Down
12 changes: 4 additions & 8 deletions E2E/Tests/Source/Features/CreateConnection.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import TestFramework

final class ConnectionFeature: Feature {
override func title() -> String {
"Create connection"
}

override func description() -> String {
"The Edge Agent should be able to create a connection to Open Enterprise Agent"
}
final class CreateConnection: Feature {
override var tags: [String] { ["connection"] }
override var title: String { "Create connection" }
override var narrative: String { "The Edge Agent should be able to create a connection to Open Enterprise Agent" }

func testConnection() async throws {
let table: [[String: String]] = [
Expand Down
Loading
Loading