Merge pull request 'feature/ci/gitea-macos-runner' (#49) from feature/ci/gitea-macos-runner into develop
Reviewed-on: Loverde-Company-LTDA/Pedi-Foods-Skip#49
This commit is contained in:
@@ -20,6 +20,18 @@ jobs:
|
|||||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/archive/${{ github.sha }}.tar.gz"
|
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/archive/${{ github.sha }}.tar.gz"
|
||||||
tar -xzf /tmp/repo.tar.gz --strip-components=1 -C "$GITHUB_WORKSPACE"
|
tar -xzf /tmp/repo.tar.gz --strip-components=1 -C "$GITHUB_WORKSPACE"
|
||||||
|
|
||||||
|
- name: Check ASC secrets are present (no values printed)
|
||||||
|
run: |
|
||||||
|
for s in ASC_KEY_ID ASC_ISSUER_ID ASC_KEY_CONTENT CI_KEYCHAIN_PASSWORD; do
|
||||||
|
eval "v=\${${s}:-}"
|
||||||
|
if [ -n "$v" ]; then echo "$s: SET (${#v} chars)"; else echo "$s: EMPTY"; fi
|
||||||
|
done
|
||||||
|
env:
|
||||||
|
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
|
||||||
|
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
|
||||||
|
ASC_KEY_CONTENT: ${{ secrets.ASC_KEY_CONTENT }}
|
||||||
|
CI_KEYCHAIN_PASSWORD: ${{ secrets.CI_KEYCHAIN_PASSWORD }}
|
||||||
|
|
||||||
- name: Write App Store Connect API key
|
- name: Write App Store Connect API key
|
||||||
run: |
|
run: |
|
||||||
cat > fastlane/apikey.json <<EOF
|
cat > fastlane/apikey.json <<EOF
|
||||||
|
|||||||
@@ -19,5 +19,8 @@ jobs:
|
|||||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/archive/${{ github.sha }}.tar.gz"
|
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/archive/${{ github.sha }}.tar.gz"
|
||||||
tar -xzf /tmp/repo.tar.gz --strip-components=1 -C "$GITHUB_WORKSPACE"
|
tar -xzf /tmp/repo.tar.gz --strip-components=1 -C "$GITHUB_WORKSPACE"
|
||||||
|
|
||||||
|
- name: Prune orphaned simulators (shared runner, avoids stale-UDID boot failures)
|
||||||
|
run: xcrun simctl delete unavailable || true
|
||||||
|
|
||||||
- name: Run tests with coverage
|
- name: Run tests with coverage
|
||||||
run: fastlane tests
|
run: fastlane tests
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
29E376FB24A2F19361D5BB6A /* ApiCustomerPayloadModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 246FDD4C8985BF2F5C900001 /* ApiCustomerPayloadModels.swift */; };
|
29E376FB24A2F19361D5BB6A /* ApiCustomerPayloadModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 246FDD4C8985BF2F5C900001 /* ApiCustomerPayloadModels.swift */; };
|
||||||
2A1940BB0EDC3FF50127B39A /* SnackbarOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4745C12F14B695C77DFE178 /* SnackbarOverlay.swift */; };
|
2A1940BB0EDC3FF50127B39A /* SnackbarOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4745C12F14B695C77DFE178 /* SnackbarOverlay.swift */; };
|
||||||
2B202626C34FC9F5C87C0388 /* ApiOrderModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4625B70E3E8BEEB9D0B2042 /* ApiOrderModels.swift */; };
|
2B202626C34FC9F5C87C0388 /* ApiOrderModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4625B70E3E8BEEB9D0B2042 /* ApiOrderModels.swift */; };
|
||||||
2F56755488E951C453AC2126 /* LCEssentials in Frameworks */ = {isa = PBXBuildFile; productRef = B8D5E8821C95A331C3A19DA1 /* LCEssentials */; };
|
|
||||||
2FCB3403A764AD74AC3BD47C /* LegalDocumentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AA914FB8B32B106911FD67B /* LegalDocumentTests.swift */; };
|
2FCB3403A764AD74AC3BD47C /* LegalDocumentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AA914FB8B32B106911FD67B /* LegalDocumentTests.swift */; };
|
||||||
33AB0B87867400F3D0C65FA0 /* ApiClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DBC4B9B43C015C4DD411359 /* ApiClient.swift */; };
|
33AB0B87867400F3D0C65FA0 /* ApiClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DBC4B9B43C015C4DD411359 /* ApiClient.swift */; };
|
||||||
3689239282AADF06783CBBEA /* AuthFormatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B3C375FC18002A884A44F1 /* AuthFormatters.swift */; };
|
3689239282AADF06783CBBEA /* AuthFormatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B3C375FC18002A884A44F1 /* AuthFormatters.swift */; };
|
||||||
@@ -97,6 +96,7 @@
|
|||||||
A3DFC65C635BE5D96660A432 /* Inputs.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB95D6804877D2ED244811D0 /* Inputs.swift */; };
|
A3DFC65C635BE5D96660A432 /* Inputs.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB95D6804877D2ED244811D0 /* Inputs.swift */; };
|
||||||
A68ED6FBE5307F8674A5A4AB /* HomeView+Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E0CCEF22D356F9EC723999D /* HomeView+Data.swift */; };
|
A68ED6FBE5307F8674A5A4AB /* HomeView+Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E0CCEF22D356F9EC723999D /* HomeView+Data.swift */; };
|
||||||
A6AE21C3CC220D50251865BE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 63DCCE423400D4D3785EE0F0 /* Assets.xcassets */; };
|
A6AE21C3CC220D50251865BE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 63DCCE423400D4D3785EE0F0 /* Assets.xcassets */; };
|
||||||
|
A8DD2855305422C50014CFA9 /* LCEssentials in Frameworks */ = {isa = PBXBuildFile; productRef = A8DD2854305422C50014CFA9 /* LCEssentials */; };
|
||||||
AA0A4F088033729F9B74B2BB /* LegalViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B3B6E69309F3EDF28E81CC /* LegalViews.swift */; };
|
AA0A4F088033729F9B74B2BB /* LegalViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30B3B6E69309F3EDF28E81CC /* LegalViews.swift */; };
|
||||||
ABE383C64BD045B9732AA8C5 /* HomeView+Filtering.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D7ECA55B1FE5FC617E84CB /* HomeView+Filtering.swift */; };
|
ABE383C64BD045B9732AA8C5 /* HomeView+Filtering.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0D7ECA55B1FE5FC617E84CB /* HomeView+Filtering.swift */; };
|
||||||
AC7E0F52FEF7976D97190205 /* ApiModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60DC2520DBC74D68AEE659E5 /* ApiModels.swift */; };
|
AC7E0F52FEF7976D97190205 /* ApiModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60DC2520DBC74D68AEE659E5 /* ApiModels.swift */; };
|
||||||
@@ -318,7 +318,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
2F56755488E951C453AC2126 /* LCEssentials in Frameworks */,
|
A8DD2855305422C50014CFA9 /* LCEssentials in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -659,7 +659,7 @@
|
|||||||
);
|
);
|
||||||
name = PediFoods;
|
name = PediFoods;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
B8D5E8821C95A331C3A19DA1 /* LCEssentials */,
|
A8DD2854305422C50014CFA9 /* LCEssentials */,
|
||||||
);
|
);
|
||||||
productName = PediFoods;
|
productName = PediFoods;
|
||||||
productReference = 7A6D74FD2D2F2F79349EA04E /* PediFoods.app */;
|
productReference = 7A6D74FD2D2F2F79349EA04E /* PediFoods.app */;
|
||||||
@@ -700,7 +700,7 @@
|
|||||||
);
|
);
|
||||||
mainGroup = C0C1E2AAD06BC8D9F221FFD5;
|
mainGroup = C0C1E2AAD06BC8D9F221FFD5;
|
||||||
packageReferences = (
|
packageReferences = (
|
||||||
A90709E0CE6A667BD0C0FF25 /* XCRemoteSwiftPackageReference "LCEssentials" */,
|
A8DD2853305422C50014CFA9 /* XCRemoteSwiftPackageReference "LCEssentials" */,
|
||||||
);
|
);
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
@@ -1001,7 +1001,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 2026.0.25.0.0.1;
|
CURRENT_PROJECT_VERSION = 2026.09.11.0.0.1;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -1046,7 +1046,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 2026.0.25.0.0.1;
|
CURRENT_PROJECT_VERSION = 2026.09.11.0.0.1;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -1111,7 +1111,7 @@
|
|||||||
CLANG_ENABLE_OBJC_WEAK = NO;
|
CLANG_ENABLE_OBJC_WEAK = NO;
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 2026.09.11.0.0.1;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V;
|
||||||
INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
|
INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
|
||||||
@@ -1242,7 +1242,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 2026.09.11.0.0.1;
|
||||||
DEVELOPMENT_TEAM = "";
|
DEVELOPMENT_TEAM = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = K4E5BZMM4V;
|
||||||
INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
|
INFOPLIST_FILE = NotificationServiceExtension/Info.plist;
|
||||||
@@ -1314,20 +1314,20 @@
|
|||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
|
||||||
/* Begin XCRemoteSwiftPackageReference section */
|
/* Begin XCRemoteSwiftPackageReference section */
|
||||||
A90709E0CE6A667BD0C0FF25 /* XCRemoteSwiftPackageReference "LCEssentials" */ = {
|
A8DD2853305422C50014CFA9 /* XCRemoteSwiftPackageReference "LCEssentials" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://git.loverde.com.br/Loverde-Company-LTDA/LCEssentials.git";
|
repositoryURL = "https://git.loverde.com.br/Loverde-Company-LTDA/LCEssentials";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = upToNextMajorVersion;
|
kind = upToNextMajorVersion;
|
||||||
minimumVersion = 1.0.11;
|
minimumVersion = 2.0.2;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/* End XCRemoteSwiftPackageReference section */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
B8D5E8821C95A331C3A19DA1 /* LCEssentials */ = {
|
A8DD2854305422C50014CFA9 /* LCEssentials */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = A90709E0CE6A667BD0C0FF25 /* XCRemoteSwiftPackageReference "LCEssentials" */;
|
package = A8DD2853305422C50014CFA9 /* XCRemoteSwiftPackageReference "LCEssentials" */;
|
||||||
productName = LCEssentials;
|
productName = LCEssentials;
|
||||||
};
|
};
|
||||||
/* End XCSwiftPackageProductDependency section */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
|
|||||||
@@ -175,12 +175,12 @@ extension ApiClient {
|
|||||||
headers: [String: String]
|
headers: [String: String]
|
||||||
) async throws -> String {
|
) async throws -> String {
|
||||||
let httpMethod = toHTTPMethod(method)
|
let httpMethod = toHTTPMethod(method)
|
||||||
|
let body: (any HTTPBody)? = params.map { RawBody(data: $0, contentType: "application/json; charset=UTF-8") }
|
||||||
return try await API.shared.request(
|
return try await API.shared.request(
|
||||||
url: url,
|
url: url,
|
||||||
params: params,
|
|
||||||
method: httpMethod,
|
method: httpMethod,
|
||||||
|
body: body,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
jsonEncoding: true,
|
|
||||||
debug: true
|
debug: true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -345,6 +345,11 @@ extension StoreDetailView {
|
|||||||
.disabled(isStoreOpen == false)
|
.disabled(isStoreOpen == false)
|
||||||
.opacity(isStoreOpen ? 1 : 0.65)
|
.opacity(isStoreOpen ? 1 : 0.65)
|
||||||
.offset(x: 7, y: 7)
|
.offset(x: 7, y: 7)
|
||||||
|
// Explicit, unique identifier for UI tests - the inner
|
||||||
|
// "plus" SF Symbol's implicit/auto-generated identifier is
|
||||||
|
// ambiguous (the same systemName is reused for CartView's
|
||||||
|
// quantity stepper) and unreliable to match against.
|
||||||
|
.accessibilityIdentifier("storeDetailProductAddButton")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(12)
|
.padding(12)
|
||||||
|
|||||||
@@ -13,9 +13,13 @@ final class AuthenticatedProfileNavigationTests: XCTestCase {
|
|||||||
continueAfterFailure = false
|
continueAfterFailure = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tearDownWithError() throws {
|
// No per-test logout: the standing QA account's Keychain session is
|
||||||
XCUIApplication().logoutIfAuthenticated()
|
// meant to persist across authenticated-flow tests, so only the first
|
||||||
}
|
// one in a suite run pays for a real OTP round trip. Tests that need a
|
||||||
|
// guaranteed logged-out state (HomeGuestFlowTests,
|
||||||
|
// ProfileLoggedOutFlowTests) force it themselves in their own setUp
|
||||||
|
// instead of relying on every authenticated class to log out after
|
||||||
|
// itself - see decisions/2026-09-11-ui-test-shared-login-session.md.
|
||||||
|
|
||||||
func testOrdersScreenReachableAndLoadsRealData() throws {
|
func testOrdersScreenReachableAndLoadsRealData() throws {
|
||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
|
|||||||
@@ -2,17 +2,14 @@ import XCTest
|
|||||||
|
|
||||||
/// Covers a real, authenticated session with the standing QA account (see
|
/// Covers a real, authenticated session with the standing QA account (see
|
||||||
/// TestFixtures.swift / decisions/2026-08-06-ui-test-account-and-app-attest-bypass.md).
|
/// TestFixtures.swift / decisions/2026-08-06-ui-test-account-and-app-attest-bypass.md).
|
||||||
/// Logs out in tearDown so guest-only tests elsewhere in the same run don't
|
/// No per-test logout - the Keychain session is meant to persist across
|
||||||
/// inherit an authenticated Keychain session.
|
/// authenticated-flow tests in the same run; see
|
||||||
|
/// decisions/2026-09-11-ui-test-shared-login-session.md.
|
||||||
final class AuthenticatedSessionFlowTests: XCTestCase {
|
final class AuthenticatedSessionFlowTests: XCTestCase {
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
continueAfterFailure = false
|
continueAfterFailure = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tearDownWithError() throws {
|
|
||||||
XCUIApplication().logoutIfAuthenticated()
|
|
||||||
}
|
|
||||||
|
|
||||||
func testLoginReachesAuthenticatedProfile() throws {
|
func testLoginReachesAuthenticatedProfile() throws {
|
||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
app.launch()
|
app.launch()
|
||||||
|
|||||||
@@ -6,18 +6,99 @@ import XCTest
|
|||||||
/// so Home shows a real store list - see
|
/// so Home shows a real store list - see
|
||||||
/// decisions/2026-08-06-ui-test-account-and-app-attest-bypass.md.
|
/// decisions/2026-08-06-ui-test-account-and-app-attest-bypass.md.
|
||||||
final class CartCheckoutFlowTests: XCTestCase {
|
final class CartCheckoutFlowTests: XCTestCase {
|
||||||
|
/// Two earlier diagnostic attempts for the timeouts below both failed
|
||||||
|
/// silently: plain `print()` never reached fastlane's xcodebuild log
|
||||||
|
/// (its formatter only relays lines matching its own known patterns),
|
||||||
|
/// and a file written via `NSTemporaryDirectory()` was never found by
|
||||||
|
/// a later CI step's `cat` either - because a UI test target's code
|
||||||
|
/// runs as `PediFoodsUITests-Runner`, an app *inside the simulator*,
|
||||||
|
/// sandboxed to that simulator device's own container filesystem;
|
||||||
|
/// `NSTemporaryDirectory()` there resolves to a path under
|
||||||
|
/// `.../CoreSimulator/Devices/<UDID>/data/...`, invisible to the
|
||||||
|
/// host shell's `$TMPDIR` a later workflow step reads from.
|
||||||
|
///
|
||||||
|
/// A `XCTAssertTrue` failure *message* is the one diagnostic channel
|
||||||
|
/// already confirmed working end to end every single run (the
|
||||||
|
/// "Store Detail's product list never loaded" text itself always
|
||||||
|
/// showed up). Its `message` parameter is `@autoclosure`, evaluated
|
||||||
|
/// only on actual failure - so building a short live-state summary
|
||||||
|
/// and interpolating it into the message costs nothing when the
|
||||||
|
/// assertion passes, and needs no file access or CI plumbing at all.
|
||||||
|
private func quickDiagnostics(_ app: XCUIApplication) -> String {
|
||||||
|
var signals: [String] = []
|
||||||
|
if app.staticTexts["Loja fechada no momento."].exists {
|
||||||
|
signals.append("closed-store snackbar visible")
|
||||||
|
}
|
||||||
|
if app.activityIndicators.firstMatch.exists {
|
||||||
|
signals.append("activity indicator still spinning")
|
||||||
|
}
|
||||||
|
if app.alerts.firstMatch.exists {
|
||||||
|
signals.append("alert showing: \(app.alerts.firstMatch.label)")
|
||||||
|
}
|
||||||
|
let errorText = app.staticTexts.matching(NSPredicate(format: "label CONTAINS[c] %@", "erro")).firstMatch
|
||||||
|
if errorText.exists {
|
||||||
|
signals.append("error text visible: \(errorText.label)")
|
||||||
|
}
|
||||||
|
// A bare `\.label` key path literal is illegal here - `label` is
|
||||||
|
// @MainActor-isolated, and a key path must be formable from any
|
||||||
|
// isolation context. A closure isn't subject to that restriction.
|
||||||
|
let visibleTexts = app.staticTexts.allElementsBoundByIndex
|
||||||
|
.prefix(15)
|
||||||
|
.map { $0.label }
|
||||||
|
.joined(separator: " | ")
|
||||||
|
signals.append("visible static texts: [\(visibleTexts)]")
|
||||||
|
return signals.joined(separator: "; ")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The cart is on-device state (`CartState`/`SessionStateStore`, see
|
||||||
|
/// `AppState.swift`), not server-side - it persists across app
|
||||||
|
/// relaunches on this simulator just like the login session (see
|
||||||
|
/// decisions/2026-09-11-ui-test-shared-login-session.md), and keeps
|
||||||
|
/// accumulating real quantities across every manual and automated run
|
||||||
|
/// against the "MARIBA" test store. There's no direct "empty cart" UI
|
||||||
|
/// action - the "Trocar de loja?" alert's "Limpar carrinho e
|
||||||
|
/// adicionar" (`StoreDetailView.swift`) only fires for a genuinely
|
||||||
|
/// different store (`shouldAskForStoreSwitch`,
|
||||||
|
/// `StoreDetailView+Logic.swift`), which this suite never triggers
|
||||||
|
/// since every test targets the same store. So: drain the cart via
|
||||||
|
/// its own per-item "minus" control, once per class (not once per
|
||||||
|
/// test - each drain can take several taps), so every run starts from
|
||||||
|
/// a known-empty cart instead of an ever-growing one.
|
||||||
|
override class func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
let app = XCUIApplication()
|
||||||
|
app.launch()
|
||||||
|
app.ensureLoggedIn()
|
||||||
|
|
||||||
|
let cartTab = app.images.matching(identifier: "cart.fill").firstMatch
|
||||||
|
guard cartTab.waitForExistence(timeout: 10) else { return }
|
||||||
|
cartTab.tap()
|
||||||
|
guard app.staticTexts["Meu Carrinho"].waitForExistence(timeout: 10) else { return }
|
||||||
|
|
||||||
|
// No "set quantity" input exists in CartView - only per-tap
|
||||||
|
// increment/decrement - so draining is inherently one tap per
|
||||||
|
// unit. Capped rather than unbounded so a genuinely stuck cart
|
||||||
|
// fails fast instead of hanging the whole class.
|
||||||
|
let minusButton = app.images.matching(identifier: "minus").firstMatch
|
||||||
|
var remainingTaps = 500
|
||||||
|
while app.staticTexts["Seu carrinho está vazio"].exists == false, remainingTaps > 0 {
|
||||||
|
guard minusButton.waitForExistence(timeout: 3) else { break }
|
||||||
|
minusButton.tap()
|
||||||
|
usleep(150_000)
|
||||||
|
remainingTaps -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
continueAfterFailure = false
|
continueAfterFailure = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tearDownWithError() throws {
|
// No per-test logout - see decisions/2026-09-11-ui-test-shared-login-session.md.
|
||||||
XCUIApplication().logoutIfAuthenticated()
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAddProductToCartAndReachCheckout() throws {
|
func testAddProductToCartAndReachCheckout() throws {
|
||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
app.launch()
|
app.launch()
|
||||||
try reachCheckoutWithOneItem(app)
|
try reachCheckoutWithProducts(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Exercises CheckoutView's payment-method selection and the address
|
/// Exercises CheckoutView's payment-method selection and the address
|
||||||
@@ -27,7 +108,7 @@ final class CartCheckoutFlowTests: XCTestCase {
|
|||||||
func testCheckoutPaymentMethodSelectionAndAddressAlterar() throws {
|
func testCheckoutPaymentMethodSelectionAndAddressAlterar() throws {
|
||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
app.launch()
|
app.launch()
|
||||||
try reachCheckoutWithOneItem(app)
|
try reachCheckoutWithProducts(app)
|
||||||
|
|
||||||
XCTAssertTrue(app.staticTexts["MÉTODO DE PAGAMENTO"].waitForExistence(timeout: 5))
|
XCTAssertTrue(app.staticTexts["MÉTODO DE PAGAMENTO"].waitForExistence(timeout: 5))
|
||||||
|
|
||||||
@@ -68,11 +149,10 @@ final class CartCheckoutFlowTests: XCTestCase {
|
|||||||
if dismissed == false {
|
if dismissed == false {
|
||||||
app.swipeDown()
|
app.swipeDown()
|
||||||
}
|
}
|
||||||
if app.staticTexts["Finalizar Pedido"].waitForExistence(timeout: 10) == false {
|
XCTAssertTrue(
|
||||||
let dir = "/private/tmp/claude-501/-Users-loverde-co-Documents-Loverde-JOBs-Producao-Loverde-Co-LC-RAG-Struct-projects-PediFoods-ios/4a9ec4f7-c3ad-4cf5-8dad-073446f6fd81/scratchpad"
|
app.staticTexts["Finalizar Pedido"].waitForExistence(timeout: 10),
|
||||||
try? app.screenshot().pngRepresentation.write(to: URL(fileURLWithPath: "\(dir)/after_alterar_dismiss.png"))
|
"Never returned to Checkout. \(quickDiagnostics(app))"
|
||||||
}
|
)
|
||||||
XCTAssertTrue(app.staticTexts["Finalizar Pedido"].exists, "Never returned to Checkout")
|
|
||||||
|
|
||||||
// Deliberately not tapping "Confirmar e Pagar" - see doc comment.
|
// Deliberately not tapping "Confirmar e Pagar" - see doc comment.
|
||||||
}
|
}
|
||||||
@@ -87,7 +167,7 @@ final class CartCheckoutFlowTests: XCTestCase {
|
|||||||
func testConfirmarEPagarWithCreditCardOpensCardSelectionWithoutSubmitting() throws {
|
func testConfirmarEPagarWithCreditCardOpensCardSelectionWithoutSubmitting() throws {
|
||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
app.launch()
|
app.launch()
|
||||||
try reachCheckoutWithOneItem(app)
|
try reachCheckoutWithProducts(app)
|
||||||
|
|
||||||
let creditCardRow = app.buttons.matching(NSPredicate(format: "label CONTAINS[c] %@", "Cartão de Crédito")).firstMatch
|
let creditCardRow = app.buttons.matching(NSPredicate(format: "label CONTAINS[c] %@", "Cartão de Crédito")).firstMatch
|
||||||
XCTAssertTrue(creditCardRow.waitForExistence(timeout: 5), "This store is expected to offer Cartão de Crédito")
|
XCTAssertTrue(creditCardRow.waitForExistence(timeout: 5), "This store is expected to offer Cartão de Crédito")
|
||||||
@@ -129,7 +209,49 @@ final class CartCheckoutFlowTests: XCTestCase {
|
|||||||
app.swipeDown()
|
app.swipeDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reachCheckoutWithOneItem(_ app: XCUIApplication) throws {
|
/// Taps the Nth product row's add button (0-based, top to bottom) and
|
||||||
|
/// handles both real outcomes on this catalog: a product with add-ons
|
||||||
|
/// (e.g. "Alcatra", the first product) opens `ProductDetailSheet`
|
||||||
|
/// ("Detalhes") and needs its own confirm tap; a plain product (no
|
||||||
|
/// `addonGroups`) adds directly with no sheet at all
|
||||||
|
/// (`StoreDetailView+Components.swift`). Which products have add-ons
|
||||||
|
/// is real catalog data, not something this suite controls, so both
|
||||||
|
/// paths must be handled rather than assumed - asserting "no sheet
|
||||||
|
/// appeared" for a product that genuinely has add-ons is exactly the
|
||||||
|
/// wrong-diagnosis mistake this suite already made once for this same
|
||||||
|
/// screen (see decisions/2026-09-11-ui-test-shared-login-session.md).
|
||||||
|
/// `ProductDetailSheet` requires no addon selection to confirm -
|
||||||
|
/// `quantity` defaults to 1 and addons default to none, so its main
|
||||||
|
/// action button ("Atualizar • <price>") is always tappable as-is.
|
||||||
|
private func addProduct(atIndex index: Int, in app: XCUIApplication) {
|
||||||
|
let addButtons = app.buttons.matching(identifier: "storeDetailProductAddButton")
|
||||||
|
let button = addButtons.element(boundBy: index)
|
||||||
|
XCTAssertTrue(
|
||||||
|
button.waitForExistence(timeout: 25),
|
||||||
|
"Product add button at index \(index) never appeared. \(quickDiagnostics(app))"
|
||||||
|
)
|
||||||
|
button.tap()
|
||||||
|
|
||||||
|
if app.staticTexts["Detalhes"].waitForExistence(timeout: 3) {
|
||||||
|
let confirmButton = app.buttons.matching(NSPredicate(format: "label BEGINSWITH %@", "Atualizar")).firstMatch
|
||||||
|
XCTAssertTrue(
|
||||||
|
confirmButton.waitForExistence(timeout: 5),
|
||||||
|
"ProductDetailSheet's confirm button never appeared for product at index \(index). \(quickDiagnostics(app))"
|
||||||
|
)
|
||||||
|
confirmButton.tap()
|
||||||
|
XCTAssertFalse(
|
||||||
|
app.staticTexts["Detalhes"].waitForExistence(timeout: 3),
|
||||||
|
"ProductDetailSheet never dismissed after confirming product at index \(index)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds the first `count` products from Store Detail's list, top to
|
||||||
|
/// bottom, then reaches Checkout. Defaults to 3 so both the add-on
|
||||||
|
/// flow (first product) and the plain-add flow (later products) are
|
||||||
|
/// both exercised on every run, not just whichever one the first
|
||||||
|
/// product happens to be.
|
||||||
|
private func reachCheckoutWithProducts(_ app: XCUIApplication, count: Int = 3) throws {
|
||||||
XCTAssertTrue(app.ensureLoggedIn(), "Login never completed")
|
XCTAssertTrue(app.ensureLoggedIn(), "Login never completed")
|
||||||
|
|
||||||
// ensureLoggedIn can land on whichever tab it detected the
|
// ensureLoggedIn can land on whichever tab it detected the
|
||||||
@@ -142,33 +264,32 @@ final class CartCheckoutFlowTests: XCTestCase {
|
|||||||
// distance + status all combine into its accessibility label), not
|
// distance + status all combine into its accessibility label), not
|
||||||
// a plain static text.
|
// a plain static text.
|
||||||
let storeCard = app.buttons.matching(NSPredicate(format: "label CONTAINS[c] %@", "MARIBA")).firstMatch
|
let storeCard = app.buttons.matching(NSPredicate(format: "label CONTAINS[c] %@", "MARIBA")).firstMatch
|
||||||
if storeCard.waitForExistence(timeout: 15) == false {
|
XCTAssertTrue(
|
||||||
let dir = "/private/tmp/claude-501/-Users-loverde-co-Documents-Loverde-JOBs-Producao-Loverde-Co-LC-RAG-Struct-projects-PediFoods-ios/4a9ec4f7-c3ad-4cf5-8dad-073446f6fd81/scratchpad"
|
storeCard.waitForExistence(timeout: 15),
|
||||||
try? app.screenshot().pngRepresentation.write(to: URL(fileURLWithPath: "\(dir)/no_store_card.png"))
|
"Expected store card never appeared on Home. \(quickDiagnostics(app))"
|
||||||
}
|
)
|
||||||
XCTAssertTrue(storeCard.exists, "Expected store card never appeared on Home")
|
|
||||||
storeCard.tap()
|
storeCard.tap()
|
||||||
|
|
||||||
// A plain (non-addon, non-pizza) product row's "+" control adds
|
// The product row's add button carries an explicit
|
||||||
// directly with no sheet. Once a product's quantity is > 0, its
|
// `.accessibilityIdentifier("storeDetailProductAddButton")`
|
||||||
// outer Button's identifier moves off itself - the same badge-
|
// (StoreDetailView+Components.swift) - added after discovering
|
||||||
// merging bug already fixed for the tab bar's cart icon - because
|
// that matching on the inner "plus" SF Symbol's implicit/
|
||||||
// the row's own quantity Text becomes the Button's accessible
|
// auto-generated identifier was unreliable: `quickDiagnostics`'s
|
||||||
// label/identity instead. The QA account's cart has genuinely
|
// visible-text dump showed a real product ("Alcatra", R$ 14,00)
|
||||||
// accumulated real quantities across many runs today, so
|
// clearly rendered on screen while `images.matching(identifier:
|
||||||
// app.buttons.matching(identifier: "plus") stopped matching once
|
// "plus")` still found nothing, and the same "plus" systemName is
|
||||||
// the first several products all had quantity > 0 (confirmed via
|
// separately reused by CartView's quantity stepper, making it an
|
||||||
// screenshot: the "+" controls were clearly visible on screen
|
// ambiguous identifier to search by in the first place.
|
||||||
// while the buttons-only query found nothing). The nested Image
|
let addButtons = app.buttons.matching(identifier: "storeDetailProductAddButton")
|
||||||
// keeps identifier "plus" regardless of quantity, so target that
|
XCTAssertTrue(
|
||||||
// directly instead - same fix pattern as the cart-tab icon.
|
addButtons.firstMatch.waitForExistence(timeout: 25),
|
||||||
let addButton = app.images.matching(identifier: "plus").firstMatch
|
"Store Detail's product list never loaded. \(quickDiagnostics(app))"
|
||||||
if addButton.waitForExistence(timeout: 25) == false {
|
)
|
||||||
let dir = "/private/tmp/claude-501/-Users-loverde-co-Documents-Loverde-JOBs-Producao-Loverde-Co-LC-RAG-Struct-projects-PediFoods-ios/4a9ec4f7-c3ad-4cf5-8dad-073446f6fd81/scratchpad"
|
|
||||||
try? app.screenshot().pngRepresentation.write(to: URL(fileURLWithPath: "\(dir)/store_detail_timeout40.png"))
|
let actualCount = min(count, addButtons.count)
|
||||||
|
for index in 0..<actualCount {
|
||||||
|
addProduct(atIndex: index, in: app)
|
||||||
}
|
}
|
||||||
XCTAssertTrue(addButton.exists, "Store Detail's product list never loaded")
|
|
||||||
addButton.tap()
|
|
||||||
|
|
||||||
// Once the cart has items, the tab bar button's accessible label
|
// Once the cart has items, the tab bar button's accessible label
|
||||||
// becomes just the badge count ("4") instead of "Shopping Cart" -
|
// becomes just the badge count ("4") instead of "Shopping Cart" -
|
||||||
@@ -181,7 +302,7 @@ final class CartCheckoutFlowTests: XCTestCase {
|
|||||||
XCTAssertTrue(cartTab.waitForExistence(timeout: 5))
|
XCTAssertTrue(cartTab.waitForExistence(timeout: 5))
|
||||||
cartTab.tap()
|
cartTab.tap()
|
||||||
XCTAssertTrue(app.staticTexts["Meu Carrinho"].waitForExistence(timeout: 10))
|
XCTAssertTrue(app.staticTexts["Meu Carrinho"].waitForExistence(timeout: 10))
|
||||||
XCTAssertFalse(app.staticTexts["Seu carrinho está vazio"].exists, "Cart still shows empty after adding a product")
|
XCTAssertFalse(app.staticTexts["Seu carrinho está vazio"].exists, "Cart still shows empty after adding products")
|
||||||
|
|
||||||
app.buttons["Ir para o Pagamento"].tap()
|
app.buttons["Ir para o Pagamento"].tap()
|
||||||
XCTAssertTrue(app.staticTexts["Finalizar Pedido"].waitForExistence(timeout: 10), "Never reached Checkout")
|
XCTAssertTrue(app.staticTexts["Finalizar Pedido"].waitForExistence(timeout: 10), "Never reached Checkout")
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ final class HomeFiltersFlowTests: XCTestCase {
|
|||||||
continueAfterFailure = false
|
continueAfterFailure = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tearDownWithError() throws {
|
// No per-test logout - see decisions/2026-09-11-ui-test-shared-login-session.md.
|
||||||
XCUIApplication().logoutIfAuthenticated()
|
|
||||||
}
|
|
||||||
|
|
||||||
func testFiltersSheetSelectionsAndApply() throws {
|
func testFiltersSheetSelectionsAndApply() throws {
|
||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
|
|||||||
@@ -6,6 +6,24 @@ import XCTest
|
|||||||
/// blocked by a separate login-navigation issue documented there and are
|
/// blocked by a separate login-navigation issue documented there and are
|
||||||
/// not covered here.
|
/// not covered here.
|
||||||
final class HomeGuestFlowTests: XCTestCase {
|
final class HomeGuestFlowTests: XCTestCase {
|
||||||
|
/// Authenticated-flow tests no longer log out after themselves (see
|
||||||
|
/// decisions/2026-09-11-ui-test-shared-login-session.md), so this
|
||||||
|
/// class must force a clean logged-out state before its tests run,
|
||||||
|
/// rather than assume one. Done once per class, not once per test:
|
||||||
|
/// the underlying navigate-to-Profile-tab dance
|
||||||
|
/// (`reachProfileTabRegardlessOfAuthState`) is a known race against
|
||||||
|
/// the guest address-picker sheet (see
|
||||||
|
/// decisions/2026-08-06-ui-test-account-and-app-attest-bypass.md) -
|
||||||
|
/// running it a second time back-to-back inside every test (once
|
||||||
|
/// here, once again inside `reachGuestHome`'s own callers) doubled
|
||||||
|
/// exposure to that race and broke tests that were previously stable.
|
||||||
|
override class func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
let app = XCUIApplication()
|
||||||
|
app.launch()
|
||||||
|
app.logoutIfAuthenticated()
|
||||||
|
}
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
continueAfterFailure = false
|
continueAfterFailure = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ final class OrderDetailsFlowTests: XCTestCase {
|
|||||||
continueAfterFailure = false
|
continueAfterFailure = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tearDownWithError() throws {
|
// No per-test logout - see decisions/2026-09-11-ui-test-shared-login-session.md.
|
||||||
XCUIApplication().logoutIfAuthenticated()
|
|
||||||
}
|
|
||||||
|
|
||||||
func testOrderDetailsReachableFromOrdersList() throws {
|
func testOrderDetailsReachableFromOrdersList() throws {
|
||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
@@ -23,8 +21,14 @@ final class OrderDetailsFlowTests: XCTestCase {
|
|||||||
XCTAssertTrue(app.staticTexts["Meus Pedidos"].waitForExistence(timeout: 10))
|
XCTAssertTrue(app.staticTexts["Meus Pedidos"].waitForExistence(timeout: 10))
|
||||||
|
|
||||||
// "Ver Detalhes" appears once per order card; this account has real
|
// "Ver Detalhes" appears once per order card; this account has real
|
||||||
// order history (confirmed in AuthenticatedProfileNavigationTests).
|
// order history (confirmed in AuthenticatedProfileNavigationTests)
|
||||||
let detailsButton = app.buttons["Ver Detalhes"]
|
// which has grown to more than one order across many manual and
|
||||||
|
// automated runs, so an exact-match subscript now resolves to
|
||||||
|
// multiple elements and throws on .tap() ("Find single matching
|
||||||
|
// element"). .firstMatch tolerates any count - the test only
|
||||||
|
// needs *an* order's details screen to be reachable, not a
|
||||||
|
// specific one.
|
||||||
|
let detailsButton = app.buttons.matching(NSPredicate(format: "label == %@", "Ver Detalhes")).firstMatch
|
||||||
XCTAssertTrue(detailsButton.waitForExistence(timeout: 10), "No orders with a 'Ver Detalhes' action found")
|
XCTAssertTrue(detailsButton.waitForExistence(timeout: 10), "No orders with a 'Ver Detalhes' action found")
|
||||||
detailsButton.tap()
|
detailsButton.tap()
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,25 @@ import XCTest
|
|||||||
/// Covers the Profile tab's logged-out state (`ProfileLoggedOutView`) and
|
/// Covers the Profile tab's logged-out state (`ProfileLoggedOutView`) and
|
||||||
/// the auth intro screen (`LoginView`) it opens.
|
/// the auth intro screen (`LoginView`) it opens.
|
||||||
final class ProfileLoggedOutFlowTests: XCTestCase {
|
final class ProfileLoggedOutFlowTests: XCTestCase {
|
||||||
|
/// Authenticated-flow tests no longer log out after themselves (see
|
||||||
|
/// decisions/2026-09-11-ui-test-shared-login-session.md), so this
|
||||||
|
/// class must force a clean logged-out state before its tests run,
|
||||||
|
/// rather than assume one. Done once per class, not once per test:
|
||||||
|
/// the underlying navigate-to-Profile-tab dance
|
||||||
|
/// (`reachProfileTabRegardlessOfAuthState`) is a known race against
|
||||||
|
/// the guest address-picker sheet (see
|
||||||
|
/// decisions/2026-08-06-ui-test-account-and-app-attest-bypass.md) -
|
||||||
|
/// running it a second time back-to-back inside every test (once
|
||||||
|
/// here, once again inside `reachLoggedOutProfile`'s own callers)
|
||||||
|
/// doubled exposure to that race and broke tests that were
|
||||||
|
/// previously stable.
|
||||||
|
override class func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
let app = XCUIApplication()
|
||||||
|
app.launch()
|
||||||
|
app.logoutIfAuthenticated()
|
||||||
|
}
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
continueAfterFailure = false
|
continueAfterFailure = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ final class SavedCardsFlowTests: XCTestCase {
|
|||||||
continueAfterFailure = false
|
continueAfterFailure = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tearDownWithError() throws {
|
// No per-test logout - see decisions/2026-09-11-ui-test-shared-login-session.md.
|
||||||
XCUIApplication().logoutIfAuthenticated()
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAddCardFormReachableFromSavedCards() throws {
|
func testAddCardFormReachableFromSavedCards() throws {
|
||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ final class UserProfileFlowTests: XCTestCase {
|
|||||||
continueAfterFailure = false
|
continueAfterFailure = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tearDownWithError() throws {
|
// No per-test logout - see decisions/2026-09-11-ui-test-shared-login-session.md.
|
||||||
XCUIApplication().logoutIfAuthenticated()
|
|
||||||
}
|
|
||||||
|
|
||||||
func testUserProfileReachableFromProfileHeader() throws {
|
func testUserProfileReachableFromProfileHeader() throws {
|
||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
|
|||||||
@@ -34,10 +34,38 @@ lane :assemble do |options|
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Manual signing (PROVISIONING_PROFILE_SPECIFIER baked directly into
|
||||||
|
# PediFoods.xcodeproj's build settings, per target) means Xcode resolves
|
||||||
|
# a profile by matching that exact Name string against installed
|
||||||
|
# .mobileprovision files - not by bundle identifier alone. Without an
|
||||||
|
# explicit provisioning_name:, sigh names a freshly (re)created profile
|
||||||
|
# "<bundle identifier> AppStore" by default, which doesn't match the
|
||||||
|
# project's expected names ("LC Prov PediFoods Dist Profile" / "LC Prov
|
||||||
|
# PediFoods Dist Push Profile") - Xcode then can't find it and falls
|
||||||
|
# back to whatever stale profile happens to already be installed under
|
||||||
|
# the old name, which is exactly the doesn't-include-this-cert error
|
||||||
|
# force: true alone didn't fix. Also needs one call per target: sigh
|
||||||
|
# only touches the app_identifier it's given, and the
|
||||||
|
# NotificationServiceExtension has its own separate bundle id.
|
||||||
|
private_lane :renew_provisioning_profiles do
|
||||||
|
get_provisioning_profile(
|
||||||
|
api_key_path: "fastlane/apikey.json",
|
||||||
|
app_identifier: "com.br.pedifoods.app",
|
||||||
|
provisioning_name: "LC Prov PediFoods Dist Profile",
|
||||||
|
force: true
|
||||||
|
)
|
||||||
|
get_provisioning_profile(
|
||||||
|
api_key_path: "fastlane/apikey.json",
|
||||||
|
app_identifier: "com.br.pedifoods.app.NotificationService",
|
||||||
|
provisioning_name: "LC Prov PediFoods Dist Push Profile",
|
||||||
|
force: true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
lane :beta do |options|
|
lane :beta do |options|
|
||||||
desc "Build and upload to TestFlight"
|
desc "Build and upload to TestFlight"
|
||||||
|
|
||||||
get_provisioning_profile(api_key_path: "fastlane/apikey.json")
|
renew_provisioning_profiles
|
||||||
|
|
||||||
assemble
|
assemble
|
||||||
|
|
||||||
@@ -55,7 +83,7 @@ lane :release do |options|
|
|||||||
|
|
||||||
# if you have an apikey.json file (https://developer.apple.com/documentation/appstoreconnectapi/creating-api-keys-for-app-store-connect-api), fastlane can automatically fetch certificates and the ASC authentication information
|
# if you have an apikey.json file (https://developer.apple.com/documentation/appstoreconnectapi/creating-api-keys-for-app-store-connect-api), fastlane can automatically fetch certificates and the ASC authentication information
|
||||||
#get_certificates(api_key_path: "fastlane/apikey.json")
|
#get_certificates(api_key_path: "fastlane/apikey.json")
|
||||||
get_provisioning_profile(api_key_path: "fastlane/apikey.json")
|
renew_provisioning_profiles
|
||||||
|
|
||||||
assemble
|
assemble
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user