Prepara iOS 2.0.12 build 35 per TestFlight e rafforza i test in produzione.
Forza dirette unlisted sul sito (non YouTube), copertina di test e verifica che in pausa gli spettatori vedano la slate. Alza il numero di build oltre le 33–34 già su App Store Connect. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
90d481a2d1
commit
1185d0ce61
@@ -677,7 +677,7 @@
|
|||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 33;
|
CURRENT_PROJECT_VERSION = 35;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_API_BASE_URL = "$(API_BASE_URL)";
|
INFOPLIST_KEY_API_BASE_URL = "$(API_BASE_URL)";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||||
@@ -698,7 +698,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 33;
|
CURRENT_PROJECT_VERSION = 35;
|
||||||
DEVELOPMENT_TEAM = S8Q9TWBRG5;
|
DEVELOPMENT_TEAM = S8Q9TWBRG5;
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
INFOPLIST_FILE = MatchLiveTv/Resources/Info.plist;
|
INFOPLIST_FILE = MatchLiveTv/Resources/Info.plist;
|
||||||
@@ -727,7 +727,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 33;
|
CURRENT_PROJECT_VERSION = 35;
|
||||||
DEVELOPMENT_TEAM = S8Q9TWBRG5;
|
DEVELOPMENT_TEAM = S8Q9TWBRG5;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
@@ -756,7 +756,7 @@
|
|||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 33;
|
CURRENT_PROJECT_VERSION = 35;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_API_BASE_URL = "$(API_BASE_URL)";
|
INFOPLIST_KEY_API_BASE_URL = "$(API_BASE_URL)";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||||
@@ -775,7 +775,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 33;
|
CURRENT_PROJECT_VERSION = 35;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_API_BASE_URL = "$(API_BASE_URL)";
|
INFOPLIST_KEY_API_BASE_URL = "$(API_BASE_URL)";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||||
@@ -794,7 +794,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 33;
|
CURRENT_PROJECT_VERSION = 35;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_API_BASE_URL = "$(API_BASE_URL)";
|
INFOPLIST_KEY_API_BASE_URL = "$(API_BASE_URL)";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>33</string>
|
<string>35</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PhotosUI
|
import PhotosUI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
struct MatchCoverSection: View {
|
struct MatchCoverSection: View {
|
||||||
let effectiveCoverUrl: String?
|
let effectiveCoverUrl: String?
|
||||||
@@ -29,6 +30,7 @@ struct MatchCoverSection: View {
|
|||||||
.font(MatchTypography.bodyMedium)
|
.font(MatchTypography.bodyMedium)
|
||||||
.foregroundStyle(MatchColors.textSecondary)
|
.foregroundStyle(MatchColors.textSecondary)
|
||||||
.padding(.top, 4)
|
.padding(.top, 4)
|
||||||
|
.accessibilityIdentifier("wizard.cover.source")
|
||||||
|
|
||||||
Group {
|
Group {
|
||||||
if let localCoverImage {
|
if let localCoverImage {
|
||||||
@@ -71,6 +73,17 @@ struct MatchCoverSection: View {
|
|||||||
.accessibilityIdentifier("wizard.cover.change")
|
.accessibilityIdentifier("wizard.cover.change")
|
||||||
.padding(.top, 12)
|
.padding(.top, 12)
|
||||||
|
|
||||||
|
if ProcessInfo.processInfo.arguments.contains("--uitesting-reset") {
|
||||||
|
Button("Usa copertina di test") {
|
||||||
|
localCoverImage = Self.makeUiTestCoverImage()
|
||||||
|
}
|
||||||
|
.font(MatchTypography.labelLarge)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(height: 44)
|
||||||
|
.accessibilityIdentifier("wizard.cover.uitest")
|
||||||
|
.padding(.top, 8)
|
||||||
|
}
|
||||||
|
|
||||||
if hasMatchOverride || localCoverImage != nil {
|
if hasMatchOverride || localCoverImage != nil {
|
||||||
MatchSecondaryButton(
|
MatchSecondaryButton(
|
||||||
label: L10n.t("wizard.match.cover.reset"),
|
label: L10n.t("wizard.match.cover.reset"),
|
||||||
@@ -95,4 +108,14 @@ struct MatchCoverSection: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// PNG 16:9 magenta, usata solo dai UI test (`--uitesting-reset`).
|
||||||
|
static func makeUiTestCoverImage() -> UIImage {
|
||||||
|
let size = CGSize(width: 1280, height: 720)
|
||||||
|
let renderer = UIGraphicsImageRenderer(size: size)
|
||||||
|
return renderer.image { ctx in
|
||||||
|
UIColor(red: 1, green: 0.125, blue: 0.75, alpha: 1).setFill()
|
||||||
|
ctx.fill(CGRect(origin: .zero, size: size))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,8 @@ struct StepNetworkTestScreen: View {
|
|||||||
if testCompleted, let shareUrl {
|
if testCompleted, let shareUrl {
|
||||||
WizardReadOnlyField(
|
WizardReadOnlyField(
|
||||||
label: currentSession.platform == "youtube" ? L10n.t("wizard.network.link.youtube.label") : L10n.t("wizard.network.link.live.label"),
|
label: currentSession.platform == "youtube" ? L10n.t("wizard.network.link.youtube.label") : L10n.t("wizard.network.link.live.label"),
|
||||||
value: shareUrl
|
value: shareUrl,
|
||||||
|
valueIdentifier: "wizard.network.shareurl"
|
||||||
)
|
)
|
||||||
.padding(.top, 16)
|
.padding(.top, 16)
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ struct StepTransmissionScreen: View {
|
|||||||
|
|
||||||
@State private var team: Team?
|
@State private var team: Team?
|
||||||
@State private var platform = "matchlivetv"
|
@State private var platform = "matchlivetv"
|
||||||
@State private var privacy = "public"
|
@State private var privacy = ProcessInfo.processInfo.arguments.contains("--uitesting-reset") ? "unlisted" : "public"
|
||||||
@State private var creating = false
|
@State private var creating = false
|
||||||
|
|
||||||
private var youtubeReady: Bool {
|
private var youtubeReady: Bool {
|
||||||
@@ -42,6 +42,7 @@ struct StepTransmissionScreen: View {
|
|||||||
selected: platform == "matchlivetv",
|
selected: platform == "matchlivetv",
|
||||||
onClick: { platform = "matchlivetv" }
|
onClick: { platform = "matchlivetv" }
|
||||||
)
|
)
|
||||||
|
.accessibilityIdentifier("wizard.platform.site")
|
||||||
.padding(.top, 12)
|
.padding(.top, 12)
|
||||||
|
|
||||||
WizardPlatformCard(
|
WizardPlatformCard(
|
||||||
@@ -70,6 +71,7 @@ struct StepTransmissionScreen: View {
|
|||||||
selected: privacy == "unlisted",
|
selected: privacy == "unlisted",
|
||||||
action: { privacy = "unlisted" }
|
action: { privacy = "unlisted" }
|
||||||
)
|
)
|
||||||
|
.accessibilityIdentifier("wizard.privacy.unlisted")
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
.padding(.top, 12)
|
.padding(.top, 12)
|
||||||
|
|||||||
@@ -68,12 +68,19 @@ struct WizardOutlinedField: View {
|
|||||||
struct WizardReadOnlyField: View {
|
struct WizardReadOnlyField: View {
|
||||||
let label: String
|
let label: String
|
||||||
let value: String
|
let value: String
|
||||||
|
var valueIdentifier: String? = nil
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(label).font(MatchTypography.bodyMedium)
|
Text(label).font(MatchTypography.bodyMedium)
|
||||||
|
if let valueIdentifier {
|
||||||
|
Text(value)
|
||||||
|
.font(MatchTypography.titleMedium)
|
||||||
|
.accessibilityIdentifier(valueIdentifier)
|
||||||
|
} else {
|
||||||
Text(value).font(MatchTypography.titleMedium)
|
Text(value).font(MatchTypography.titleMedium)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.padding(14)
|
.padding(14)
|
||||||
.background(MatchColors.surfaceElevated, in: RoundedRectangle(cornerRadius: 10))
|
.background(MatchColors.surfaceElevated, in: RoundedRectangle(cornerRadius: 10))
|
||||||
|
|||||||
@@ -169,25 +169,7 @@ final class HubFlowsUITests: XCTestCase {
|
|||||||
opponent.typeText("UI Wizard")
|
opponent.typeText("UI Wizard")
|
||||||
dismissKeyboard()
|
dismissKeyboard()
|
||||||
}
|
}
|
||||||
if any("wizard.cover.change").waitForExistence(timeout: 3)
|
applyMatchCover()
|
||||||
|| app.buttons["Cambia copertina per questa partita"].waitForExistence(timeout: 2) {
|
|
||||||
let cover = firstExisting(["wizard.cover.change"], labels: ["Cambia copertina per questa partita"])
|
|
||||||
tapHittable(cover)
|
|
||||||
sleep(1)
|
|
||||||
capture("06b-cover-picker")
|
|
||||||
let spring = XCUIApplication(bundleIdentifier: "com.apple.springboard")
|
|
||||||
for label in ["Annulla", "Cancel", "Chiudi", "Close"] {
|
|
||||||
if spring.buttons[label].exists {
|
|
||||||
spring.buttons[label].tap()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if app.buttons[label].exists {
|
|
||||||
tapHittable(app.buttons[label])
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dismissSystemDialogs()
|
|
||||||
}
|
|
||||||
scrollToId("wizard.next")
|
scrollToId("wizard.next")
|
||||||
tapHittable(any("wizard.next"))
|
tapHittable(any("wizard.next"))
|
||||||
XCTAssertTrue(
|
XCTAssertTrue(
|
||||||
@@ -196,13 +178,18 @@ final class HubFlowsUITests: XCTestCase {
|
|||||||
"Wizard step 2 (trasmissione) non aperto.\n\(app.debugDescription)"
|
"Wizard step 2 (trasmissione) non aperto.\n\(app.debugDescription)"
|
||||||
)
|
)
|
||||||
capture("07-wizard-step2")
|
capture("07-wizard-step2")
|
||||||
if app.buttons["NON IN ELENCO"].waitForExistence(timeout: 3) {
|
if any("wizard.platform.site").waitForExistence(timeout: 4) {
|
||||||
tapHittable(app.buttons["NON IN ELENCO"])
|
tapHittable(any("wizard.platform.site"))
|
||||||
}
|
|
||||||
if app.staticTexts["YouTube Live"].waitForExistence(timeout: 3) {
|
|
||||||
// Non selezionare YouTube in produzione: solo verifica che la card esista.
|
|
||||||
capture("07b-youtube-visible")
|
|
||||||
}
|
}
|
||||||
|
let unlisted = firstExisting(["wizard.privacy.unlisted"], labels: ["NON IN ELENCO", "UNLISTED"])
|
||||||
|
XCTAssertTrue(unlisted.waitForExistence(timeout: 6), "Manca l'opzione NON IN ELENCO")
|
||||||
|
tapHittable(unlisted)
|
||||||
|
XCTAssertTrue(
|
||||||
|
app.staticTexts.matching(NSPredicate(format: "label CONTAINS 'Solo chi ha il link' OR label CONTAINS 'Only those with the link'")).firstMatch.waitForExistence(timeout: 4)
|
||||||
|
|| any("wizard.privacy.unlisted").exists,
|
||||||
|
"Visibilità non impostata su privato/unlisted"
|
||||||
|
)
|
||||||
|
capture("07b-privacy-unlisted")
|
||||||
scrollToId("wizard.next")
|
scrollToId("wizard.next")
|
||||||
tapHittable(any("wizard.next"))
|
tapHittable(any("wizard.next"))
|
||||||
XCTAssertTrue(
|
XCTAssertTrue(
|
||||||
@@ -220,6 +207,18 @@ final class HubFlowsUITests: XCTestCase {
|
|||||||
"Test rete non completato.\n\(app.debugDescription)"
|
"Test rete non completato.\n\(app.debugDescription)"
|
||||||
)
|
)
|
||||||
capture("08b-network-ready")
|
capture("08b-network-ready")
|
||||||
|
let tree = app.debugDescription
|
||||||
|
XCTAssertFalse(tree.contains("youtube.com"), "Il test ha creato un video YouTube: deve restare sul sito, unlisted")
|
||||||
|
XCTAssertTrue(
|
||||||
|
tree.contains("matchlivetv.it/live") || tree.contains("/live/"),
|
||||||
|
"Manca il link diretta sul sito"
|
||||||
|
)
|
||||||
|
let sessionId = extractLiveSessionId()
|
||||||
|
XCTAssertNotNil(sessionId, "Impossibile leggere l'id sessione dal link diretta.\n\(tree)")
|
||||||
|
if let sessionId {
|
||||||
|
try? sessionId.write(toFile: "\(shotDir)/live-session-id.txt", atomically: true, encoding: .utf8)
|
||||||
|
assertLivePageIsUnlistedWithMatchCover(sessionId: sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
let start = any("wizard.start")
|
let start = any("wizard.start")
|
||||||
XCTAssertTrue(start.waitForExistence(timeout: 6))
|
XCTAssertTrue(start.waitForExistence(timeout: 6))
|
||||||
@@ -278,6 +277,26 @@ final class HubFlowsUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if any("broadcast.pause").waitForExistence(timeout: 4) {
|
||||||
|
tapHittable(any("broadcast.pause"))
|
||||||
|
sleep(1)
|
||||||
|
capture("09c5-pause-confirm")
|
||||||
|
let confirmPause = firstExisting([], labels: ["Conferma", "Confirm"])
|
||||||
|
if confirmPause.waitForExistence(timeout: 3) {
|
||||||
|
tapHittable(confirmPause)
|
||||||
|
}
|
||||||
|
sleep(3)
|
||||||
|
capture("09c6-paused")
|
||||||
|
XCTAssertTrue(
|
||||||
|
app.staticTexts["PAUSA"].waitForExistence(timeout: 8)
|
||||||
|
|| app.staticTexts.matching(NSPredicate(format: "label CONTAINS 'PAUSA' OR label CONTAINS 'PAUSED'")).firstMatch.exists,
|
||||||
|
"Dopo la conferma non compare lo stato PAUSA"
|
||||||
|
)
|
||||||
|
if let sessionId {
|
||||||
|
assertViewerShowsCoverWhilePaused(sessionId: sessionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if any("broadcast.terminate").waitForExistence(timeout: 3) {
|
if any("broadcast.terminate").waitForExistence(timeout: 3) {
|
||||||
tapHittable(any("broadcast.terminate"))
|
tapHittable(any("broadcast.terminate"))
|
||||||
sleep(1)
|
sleep(1)
|
||||||
@@ -295,6 +314,174 @@ final class HubFlowsUITests: XCTestCase {
|
|||||||
capture("09e-back-hub")
|
capture("09e-back-hub")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func applyMatchCover() {
|
||||||
|
scrollToId("wizard.cover.change")
|
||||||
|
let change = firstExisting(["wizard.cover.change"], labels: ["Cambia copertina per questa partita"])
|
||||||
|
XCTAssertTrue(change.waitForExistence(timeout: 8), "Manca Cambia copertina per questa partita")
|
||||||
|
|
||||||
|
scrollToId("wizard.cover.uitest")
|
||||||
|
let testCover = any("wizard.cover.uitest")
|
||||||
|
XCTAssertTrue(testCover.waitForExistence(timeout: 6), "Manca il fallback copertina di test")
|
||||||
|
tapHittable(testCover)
|
||||||
|
XCTAssertTrue(
|
||||||
|
app.staticTexts.matching(
|
||||||
|
NSPredicate(format: "label CONTAINS 'Questa partita' OR label CONTAINS 'This match'")
|
||||||
|
).firstMatch.waitForExistence(timeout: 8),
|
||||||
|
"La copertina di test non è stata applicata a questa partita.\n\(app.debugDescription)"
|
||||||
|
)
|
||||||
|
capture("06c-cover-match")
|
||||||
|
|
||||||
|
tapHittable(change)
|
||||||
|
sleep(2)
|
||||||
|
capture("06b-cover-picker")
|
||||||
|
_ = pickFirstPhotoFromSystemPicker()
|
||||||
|
dismissPhotoPicker()
|
||||||
|
XCTAssertTrue(
|
||||||
|
app.staticTexts.matching(
|
||||||
|
NSPredicate(format: "label CONTAINS 'Questa partita' OR label CONTAINS 'This match'")
|
||||||
|
).firstMatch.waitForExistence(timeout: 6),
|
||||||
|
"Dopo il selettore foto la copertina della partita è sparita"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
private func pickFirstPhotoFromSystemPicker() -> Bool {
|
||||||
|
let photos = XCUIApplication(bundleIdentifier: "com.apple.PhotosUIService")
|
||||||
|
guard photos.wait(for: .runningForeground, timeout: 5) else { return false }
|
||||||
|
let candidates: [XCUIElement] = [
|
||||||
|
photos.images.firstMatch,
|
||||||
|
photos.collectionViews.cells.firstMatch,
|
||||||
|
photos.cells.firstMatch
|
||||||
|
]
|
||||||
|
for el in candidates {
|
||||||
|
if el.waitForExistence(timeout: 2) {
|
||||||
|
el.tap()
|
||||||
|
sleep(1)
|
||||||
|
for label in ["Aggiungi", "Add", "Scegli", "Choose", "Done", "Fine"] {
|
||||||
|
if photos.buttons[label].exists {
|
||||||
|
photos.buttons[label].tap()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let source = app.staticTexts.matching(
|
||||||
|
NSPredicate(format: "label CONTAINS 'Questa partita' OR label CONTAINS 'This match'")
|
||||||
|
).firstMatch
|
||||||
|
if source.waitForExistence(timeout: 4) { return true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func dismissPhotoPicker() {
|
||||||
|
let photos = XCUIApplication(bundleIdentifier: "com.apple.PhotosUIService")
|
||||||
|
let spring = XCUIApplication(bundleIdentifier: "com.apple.springboard")
|
||||||
|
for label in ["Annulla", "Cancel", "Chiudi", "Close"] {
|
||||||
|
if photos.buttons[label].exists {
|
||||||
|
photos.buttons[label].tap()
|
||||||
|
sleep(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if spring.buttons[label].exists {
|
||||||
|
spring.buttons[label].tap()
|
||||||
|
sleep(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if app.buttons[label].exists {
|
||||||
|
tapHittable(app.buttons[label])
|
||||||
|
sleep(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if photos.state == .runningForeground {
|
||||||
|
photos.swipeDown()
|
||||||
|
sleep(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func extractLiveSessionId() -> String? {
|
||||||
|
let share = any("wizard.network.shareurl")
|
||||||
|
let raw = share.exists ? share.label : app.debugDescription
|
||||||
|
guard let range = raw.range(of: #"https?://[^\s]+/live/[0-9a-fA-F-]{36}"#, options: .regularExpression) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let url = String(raw[range])
|
||||||
|
return url.components(separatedBy: "/live/").last
|
||||||
|
}
|
||||||
|
|
||||||
|
private func assertLivePageIsUnlistedWithMatchCover(sessionId: String) {
|
||||||
|
let pageURL = "https://www.matchlivetv.it/live/\(sessionId)"
|
||||||
|
let deadline = Date().addingTimeInterval(40)
|
||||||
|
var html = ""
|
||||||
|
var poster = ""
|
||||||
|
while Date() < deadline {
|
||||||
|
html = httpGet(pageURL) ?? ""
|
||||||
|
if html.contains("youtube.com/watch") {
|
||||||
|
XCTFail("La pagina pubblica punta a YouTube invece che al sito")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let range = html.range(of: #"poster="([^"]+)""#, options: .regularExpression) {
|
||||||
|
poster = String(html[range])
|
||||||
|
.replacingOccurrences(of: "poster=\"", with: "")
|
||||||
|
.replacingOccurrences(of: "\"", with: "")
|
||||||
|
}
|
||||||
|
let unlisted = html.contains("noindex")
|
||||||
|
let customPoster = !poster.isEmpty && !poster.contains("copertina-canale")
|
||||||
|
if unlisted && customPoster { break }
|
||||||
|
RunLoop.current.run(until: Date().addingTimeInterval(2))
|
||||||
|
}
|
||||||
|
XCTAssertTrue(html.contains("noindex") || html.contains("nofollow"), "La diretta deve restare non in elenco (noindex)")
|
||||||
|
XCTAssertFalse(poster.isEmpty, "Manca l'attributo poster sul player live")
|
||||||
|
XCTAssertFalse(poster.contains("copertina-canale"), "Il poster è ancora la copertina di default: \(poster)")
|
||||||
|
try? html.write(toFile: "\(shotDir)/live-page.html", atomically: true, encoding: .utf8)
|
||||||
|
try? poster.write(toFile: "\(shotDir)/live-poster.txt", atomically: true, encoding: .utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func assertViewerShowsCoverWhilePaused(sessionId: String) {
|
||||||
|
let statusURL = "https://www.matchlivetv.it/live/\(sessionId)/status.json"
|
||||||
|
let deadline = Date().addingTimeInterval(25)
|
||||||
|
var payload: [String: Any] = [:]
|
||||||
|
while Date() < deadline {
|
||||||
|
payload = httpGetJSON(statusURL) ?? [:]
|
||||||
|
let paused = payload["paused"] as? Bool ?? false
|
||||||
|
let status = payload["status"] as? String ?? ""
|
||||||
|
let showingCover = payload["showing_cover"] as? Bool ?? false
|
||||||
|
if (paused || status == "paused") && showingCover {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
RunLoop.current.run(until: Date().addingTimeInterval(1.5))
|
||||||
|
}
|
||||||
|
if let data = try? JSONSerialization.data(withJSONObject: payload, options: [.prettyPrinted]),
|
||||||
|
let text = String(data: data, encoding: .utf8) {
|
||||||
|
try? text.write(toFile: "\(shotDir)/live-status-paused.json", atomically: true, encoding: .utf8)
|
||||||
|
}
|
||||||
|
let paused = payload["paused"] as? Bool ?? false
|
||||||
|
let status = payload["status"] as? String ?? ""
|
||||||
|
let showingCover = payload["showing_cover"] as? Bool ?? false
|
||||||
|
XCTAssertTrue(paused || status == "paused", "status.json non è in pausa: \(payload)")
|
||||||
|
XCTAssertTrue(showingCover, "Gli spettatori non stanno vedendo la copertina (showing_cover=false): \(payload)")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func httpGet(_ urlString: String) -> String? {
|
||||||
|
guard let url = URL(string: urlString) else { return nil }
|
||||||
|
let sem = DispatchSemaphore(value: 0)
|
||||||
|
var result: String?
|
||||||
|
URLSession.shared.dataTask(with: url) { data, _, _ in
|
||||||
|
if let data { result = String(data: data, encoding: .utf8) }
|
||||||
|
sem.signal()
|
||||||
|
}.resume()
|
||||||
|
_ = sem.wait(timeout: .now() + 15)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private func httpGetJSON(_ urlString: String) -> [String: Any]? {
|
||||||
|
guard let body = httpGet(urlString),
|
||||||
|
let data = body.data(using: .utf8),
|
||||||
|
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
|
||||||
func testResumeLiveSheet() throws {
|
func testResumeLiveSheet() throws {
|
||||||
loginIfNeeded()
|
loginIfNeeded()
|
||||||
waitHub()
|
waitHub()
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ lines += [
|
|||||||
app_settings = """
|
app_settings = """
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 33;
|
CURRENT_PROJECT_VERSION = 35;
|
||||||
GENERATE_INFOPLIST_FILE = NO;
|
GENERATE_INFOPLIST_FILE = NO;
|
||||||
INFOPLIST_FILE = MatchLiveTv/Resources/Info.plist;
|
INFOPLIST_FILE = MatchLiveTv/Resources/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
@@ -122,7 +122,7 @@ app_debug_settings = app_settings + """
|
|||||||
test_settings = """
|
test_settings = """
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 33;
|
CURRENT_PROJECT_VERSION = 35;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
MARKETING_VERSION = 2.0.12;
|
MARKETING_VERSION = 2.0.12;
|
||||||
|
|||||||
Reference in New Issue
Block a user