Autogenerate framed screenshots from HTML/CSS (#1000)

Add TV screenshots and organize PassepartoutUITests with two test plans
for generating iOS/macOS (Main) and tvOS (TV) screenshots. Revert to the
.attachment destination and use `xcparse` to export the screenshots.
Change iPad screenshots to portrait.

Then autogenerate framed screenshots in two steps:

- Export the UITests screenshots per device (`export.sh`)
- Embed the results in a HTML/CSS template and take snapshots with
Chrome headless (`compose.sh`)
- Repeat for all devices (iPhone, iPad, Mac and Apple TV)
- Save framed screenshots to the `fastlane` screenshots directory
This commit is contained in:
Davide 2024-12-11 20:33:58 +01:00 committed by GitHub
parent ff88b3562d
commit 76a570b7b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 747 additions and 59 deletions

7
.gitignore vendored
View File

@ -10,9 +10,10 @@ fastlane/**/review_information
fastlane/**/trade_representative_contact_information
build/
dist/
/iap
templates/
vendor/
/iap/
screenshots/html/*/0*.png
screenshots/results/
/templates/
Preview.html
default.profraw
.api

View File

@ -58,7 +58,7 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
aws-partitions (1.1019.0)
aws-partitions (1.1020.0)
aws-sdk-core (3.214.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
@ -185,10 +185,10 @@ GEM
nanaimo (0.4.0)
naturally (2.2.1)
nkf (0.2.0)
nokogiri (1.17.0)
nokogiri (1.17.1)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.17.0-arm64-darwin)
nokogiri (1.17.1-arm64-darwin)
racc (~> 1.4)
optparse (0.6.0)
os (1.1.4)

View File

@ -8,6 +8,8 @@
/* Begin PBXBuildFile section */
0E08447C2CF86F2A00ECED7C /* XCTestCase+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E08447B2CF86F2A00ECED7C /* XCTestCase+Extensions.swift */; };
0E2267862D0A059B0000B557 /* MainScreenshots.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 0E2267852D0A059B0000B557 /* MainScreenshots.xctestplan */; platformFilters = (ios, macos, ); };
0E2267882D0A05D20000B557 /* TVScreenshots.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 0E2267872D0A05D20000B557 /* TVScreenshots.xctestplan */; platformFilters = (tvos, ); };
0E3E22962CE53510005135DF /* AppUIMain in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, macos, ); productRef = 0E3E22952CE53510005135DF /* AppUIMain */; };
0E3E22982CE53510005135DF /* AppUITV in Frameworks */ = {isa = PBXBuildFile; platformFilters = (tvos, ); productRef = 0E3E22972CE53510005135DF /* AppUITV */; };
0E3FF4BA2CE3AFBC00BFF640 /* Profiles.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = 0E3FF4B72CE3AFBC00BFF640 /* Profiles.sqlite */; };
@ -23,7 +25,7 @@
0E7C3CCD2C9AF44600B72E69 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7C3CCC2C9AF44600B72E69 /* AppDelegate.swift */; };
0E7E3D692B9345FD002BBDB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E7E3D5C2B9345FD002BBDB4 /* Assets.xcassets */; };
0E7E3D6B2B9345FD002BBDB4 /* PassepartoutApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3D5F2B9345FD002BBDB4 /* PassepartoutApp.swift */; };
0E7F460E2CF7F01600B1C53A /* FlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F460B2CF7F01600B1C53A /* FlowTests.swift */; platformFilters = (ios, macos, ); };
0E7F460E2CF7F01600B1C53A /* MainFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F460B2CF7F01600B1C53A /* MainFlowTests.swift */; platformFilters = (ios, macos, ); };
0E7F460F2CF7F01600B1C53A /* XCUIApplication+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F460C2CF7F01600B1C53A /* XCUIApplication+Extensions.swift */; };
0E7F46122CF7F44C00B1C53A /* AppScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7F46102CF7F44C00B1C53A /* AppScreen.swift */; platformFilters = (ios, macos, ); };
0E81955A2CFDA75200CC8FFD /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8195592CFDA75200CC8FFD /* Dependencies.swift */; };
@ -40,10 +42,10 @@
0E916B7C2CF811EB0072921A /* XCUIElement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E916B7B2CF811EB0072921A /* XCUIElement+Extensions.swift */; };
0E94EE582B93554B00588243 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3D672B9345FD002BBDB4 /* PacketTunnelProvider.swift */; };
0EA6340C2D088C8200180D7C /* UIAccessibility in Frameworks */ = {isa = PBXBuildFile; productRef = 0EA6340B2D088C8200180D7C /* UIAccessibility */; };
0EA6340E2D08995800180D7C /* ScreenshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA6340D2D08995800180D7C /* ScreenshotTests.swift */; platformFilters = (tvos, ); };
0EA634122D08997300180D7C /* FlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA634112D08997300180D7C /* FlowTests.swift */; platformFilters = (tvos, ); };
0EA6340E2D08995800180D7C /* TVScreenshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA6340D2D08995800180D7C /* TVScreenshotTests.swift */; platformFilters = (tvos, ); };
0EA634122D08997300180D7C /* TVFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA634112D08997300180D7C /* TVFlowTests.swift */; platformFilters = (tvos, ); };
0EA634142D08998700180D7C /* AppScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA634132D08998700180D7C /* AppScreen.swift */; platformFilters = (tvos, ); };
0EAD6A1B2CF7F79A00CC1F02 /* ScreenshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAD6A1A2CF7F79A00CC1F02 /* ScreenshotTests.swift */; platformFilters = (ios, macos, ); };
0EAD6A1B2CF7F79A00CC1F02 /* MainScreenshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAD6A1A2CF7F79A00CC1F02 /* MainScreenshotTests.swift */; platformFilters = (ios, macos, ); };
0EAEC8A92D05DB8D001AA50C /* DefaultAppProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAEC8A62D05DB8D001AA50C /* DefaultAppProcessor.swift */; };
0EAEC8AA2D05DB8D001AA50C /* DefaultTunnelProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EAEC8A72D05DB8D001AA50C /* DefaultTunnelProcessor.swift */; };
0EB08B982CA46F4900A02591 /* AppPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0EB08B962CA46F4900A02591 /* AppPlist.strings */; };
@ -143,6 +145,8 @@
/* Begin PBXFileReference section */
0E06D18F2B87629100176E1D /* Passepartout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Passepartout.app; sourceTree = BUILT_PRODUCTS_DIR; };
0E08447B2CF86F2A00ECED7C /* XCTestCase+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+Extensions.swift"; sourceTree = "<group>"; };
0E2267852D0A059B0000B557 /* MainScreenshots.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MainScreenshots.xctestplan; sourceTree = "<group>"; };
0E2267872D0A05D20000B557 /* TVScreenshots.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = TVScreenshots.xctestplan; sourceTree = "<group>"; };
0E3FF4AE2CE3AF6F00BFF640 /* PassepartoutTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PassepartoutTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
0E3FF4B72CE3AFBC00BFF640 /* Profiles.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; path = Profiles.sqlite; sourceTree = "<group>"; };
0E3FF4B92CE3AFBC00BFF640 /* MigrationManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationManagerTests.swift; sourceTree = "<group>"; };
@ -163,7 +167,7 @@
0E7E3D5F2B9345FD002BBDB4 /* PassepartoutApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassepartoutApp.swift; sourceTree = "<group>"; };
0E7E3D662B9345FD002BBDB4 /* Tunnel.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Tunnel.entitlements; sourceTree = "<group>"; };
0E7E3D672B9345FD002BBDB4 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
0E7F460B2CF7F01600B1C53A /* FlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowTests.swift; sourceTree = "<group>"; };
0E7F460B2CF7F01600B1C53A /* MainFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlowTests.swift; sourceTree = "<group>"; };
0E7F460C2CF7F01600B1C53A /* XCUIApplication+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIApplication+Extensions.swift"; sourceTree = "<group>"; };
0E7F46102CF7F44C00B1C53A /* AppScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScreen.swift; sourceTree = "<group>"; };
0E8195592CFDA75200CC8FFD /* Dependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dependencies.swift; sourceTree = "<group>"; };
@ -176,10 +180,10 @@
0E916B772CF80FD60072921A /* ProfileEditorScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileEditorScreen.swift; sourceTree = "<group>"; };
0E916B7B2CF811EB0072921A /* XCUIElement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extensions.swift"; sourceTree = "<group>"; };
0E94EE5C2B93570600588243 /* Tunnel.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Tunnel.plist; sourceTree = "<group>"; };
0EA6340D2D08995800180D7C /* ScreenshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotTests.swift; sourceTree = "<group>"; };
0EA634112D08997300180D7C /* FlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowTests.swift; sourceTree = "<group>"; };
0EA6340D2D08995800180D7C /* TVScreenshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVScreenshotTests.swift; sourceTree = "<group>"; };
0EA634112D08997300180D7C /* TVFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVFlowTests.swift; sourceTree = "<group>"; };
0EA634132D08998700180D7C /* AppScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScreen.swift; sourceTree = "<group>"; };
0EAD6A1A2CF7F79A00CC1F02 /* ScreenshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotTests.swift; sourceTree = "<group>"; };
0EAD6A1A2CF7F79A00CC1F02 /* MainScreenshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainScreenshotTests.swift; sourceTree = "<group>"; };
0EAEC8A62D05DB8D001AA50C /* DefaultAppProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultAppProcessor.swift; sourceTree = "<group>"; };
0EAEC8A72D05DB8D001AA50C /* DefaultTunnelProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTunnelProcessor.swift; sourceTree = "<group>"; };
0EB08B972CA46F4900A02591 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/AppPlist.strings; sourceTree = "<group>"; };
@ -309,8 +313,8 @@
isa = PBXGroup;
children = (
0EA634102D08996500180D7C /* Screens */,
0EA634112D08997300180D7C /* FlowTests.swift */,
0EA6340D2D08995800180D7C /* ScreenshotTests.swift */,
0EA634112D08997300180D7C /* TVFlowTests.swift */,
0EA6340D2D08995800180D7C /* TVScreenshotTests.swift */,
);
path = TV;
sourceTree = "<group>";
@ -386,6 +390,8 @@
0E916B7A2CF811DE0072921A /* Extensions */,
0EC418C92CF81C6A00AC6F2F /* Main */,
0E418AB72D0752D100D33D47 /* TV */,
0E2267852D0A059B0000B557 /* MainScreenshots.xctestplan */,
0E2267872D0A05D20000B557 /* TVScreenshots.xctestplan */,
);
path = UITests;
sourceTree = "<group>";
@ -445,8 +451,8 @@
isa = PBXGroup;
children = (
0E916B7E2CF81A110072921A /* Screens */,
0E7F460B2CF7F01600B1C53A /* FlowTests.swift */,
0EAD6A1A2CF7F79A00CC1F02 /* ScreenshotTests.swift */,
0E7F460B2CF7F01600B1C53A /* MainFlowTests.swift */,
0EAD6A1A2CF7F79A00CC1F02 /* MainScreenshotTests.swift */,
);
path = Main;
sourceTree = "<group>";
@ -685,6 +691,8 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0E2267862D0A059B0000B557 /* MainScreenshots.xctestplan in Resources */,
0E2267882D0A05D20000B557 /* TVScreenshots.xctestplan in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -770,13 +778,13 @@
buildActionMask = 2147483647;
files = (
0E08447C2CF86F2A00ECED7C /* XCTestCase+Extensions.swift in Sources */,
0EA6340E2D08995800180D7C /* ScreenshotTests.swift in Sources */,
0EA634122D08997300180D7C /* FlowTests.swift in Sources */,
0E7F460E2CF7F01600B1C53A /* FlowTests.swift in Sources */,
0EA6340E2D08995800180D7C /* TVScreenshotTests.swift in Sources */,
0EA634122D08997300180D7C /* TVFlowTests.swift in Sources */,
0E7F460E2CF7F01600B1C53A /* MainFlowTests.swift in Sources */,
0E916B782CF80FD60072921A /* ProfileEditorScreen.swift in Sources */,
0EA634142D08998700180D7C /* AppScreen.swift in Sources */,
0E916B7C2CF811EB0072921A /* XCUIElement+Extensions.swift in Sources */,
0EAD6A1B2CF7F79A00CC1F02 /* ScreenshotTests.swift in Sources */,
0EAD6A1B2CF7F79A00CC1F02 /* MainScreenshotTests.swift in Sources */,
0E7F460F2CF7F01600B1C53A /* XCUIApplication+Extensions.swift in Sources */,
0E418AB52D074F0E00D33D47 /* VPNServersScreen.swift in Sources */,
0EC418D22CF86B7400AC6F2F /* ProfileMenuScreen.swift in Sources */,

View File

@ -22,8 +22,15 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans>
<TestPlanReference
reference = "container:Passepartout/UITests/TVScreenshots.xctestplan">
</TestPlanReference>
<TestPlanReference
reference = "container:Passepartout/UITests/MainScreenshots.xctestplan">
</TestPlanReference>
</TestPlans>
<Testables>
<TestableReference
skipped = "NO"
@ -55,15 +62,6 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0E78FE4B2CF799F400B0C5BF"
BuildableName = "PassepartoutUITests.xctest"
BlueprintName = "PassepartoutUITests"
ReferencedContainer = "container:Passepartout.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@ -59,8 +59,9 @@ extension XCUIApplicationProviding where Self: XCTestCase {
}
func snapshot(
_ name: String,
destination: ScreenshotDestination = .temporary,
_ index: String,
_ title: String,
destination: ScreenshotDestination = .attachment,
target: ScreenshotTarget = .window
) throws {
let container = container(for: target)
@ -69,26 +70,26 @@ extension XCUIApplicationProviding where Self: XCTestCase {
switch destination {
case .attachment:
let attachment = XCTAttachment(screenshot: screenshot)
attachment.name = name
attachment.name = index
attachment.lifetime = .keepAlways
add(attachment)
case .temporary:
let filename = deviceFilename(for: name)
let filename = deviceFilename(for: index)
let url = URL(fileURLWithPath: filename, relativeTo: destination.url)
try screenshot.pngRepresentation.write(to: url)
}
}
private func deviceFilename(for name: String) -> String {
private func deviceFilename(for index: String) -> String {
#if os(iOS)
let device = UIDevice.current.userInterfaceIdiom == .pad ? "iPad" : "iPhone"
let device = UIDevice.current.userInterfaceIdiom == .pad ? "ipad" : "iphone"
#elseif os(macOS)
let device = "Mac"
let device = "mac"
#elseif os(tvOS)
let device = "AppleTV"
let device = "appletv"
#endif
return "\(device)_\(name).png"
return "\(device)_\(index).png"
}
private func container(for target: ScreenshotTarget) -> XCUIElement {

View File

@ -1,5 +1,5 @@
//
// FlowTests.swift
// MainFlowTests.swift
// Passepartout
//
// Created by Davide De Rosa on 11/27/24.
@ -28,7 +28,7 @@ import UIAccessibility
import XCTest
@MainActor
final class FlowTests: XCTestCase {
final class MainFlowTests: XCTestCase {
private var app: XCUIApplication!
override func setUp() async throws {

View File

@ -1,5 +1,5 @@
//
// ScreenshotTests.swift
// MainScreenshotTests.swift
// Passepartout
//
// Created by Davide De Rosa on 11/28/24.
@ -28,7 +28,7 @@ import UIAccessibility
import XCTest
@MainActor
final class ScreenshotTests: XCTestCase, XCUIApplicationProviding {
final class MainScreenshotTests: XCTestCase, XCUIApplicationProviding {
let app: XCUIApplication = {
let app = XCUIApplication()
app.appArguments = [.uiTesting]
@ -40,7 +40,7 @@ final class ScreenshotTests: XCTestCase, XCUIApplicationProviding {
app.launch()
#if os(iOS)
if UIDevice.current.userInterfaceIdiom == .pad {
XCUIDevice.shared.orientation = .landscapeLeft
XCUIDevice.shared.orientation = .portrait
}
#endif
}
@ -55,27 +55,27 @@ final class ScreenshotTests: XCTestCase, XCUIApplicationProviding {
.editProfile()
await pause()
try snapshot("02_ProfileEditor", target: .sheet)
try snapshot("03", "ProfileEditor", target: .sheet)
profile
.enterModule(at: 1)
await pause()
try snapshot("03_OnDemand", target: .sheet)
try snapshot("02", "OnDemand", target: .sheet)
profile
.leaveModule()
.enterModule(at: 2)
await pause()
try snapshot("04_DNS", target: .sheet)
try snapshot("04", "DNS", target: .sheet)
let app = profile
.leaveModule()
.closeProfile()
await pause()
try snapshot("01_Connected")
try snapshot("01", "Connected")
app
.openProfileMenu(at: 2)
@ -85,7 +85,7 @@ final class ScreenshotTests: XCTestCase, XCUIApplicationProviding {
#endif
await pause()
try snapshot("05_ProviderServers", target: .sheet)
try snapshot("05", "ProviderServers", target: .sheet)
print("Saved to: \(ScreenshotDestination.temporary.url)")
}

View File

@ -0,0 +1,48 @@
{
"configurations" : [
{
"id" : "30682571-78CE-444A-9D31-4E1310B4D166",
"name" : "Configuration 1",
"options" : {
}
}
],
"defaultOptions" : {
"testTimeoutsEnabled" : true
},
"testTargets" : [
{
"skippedTests" : [
"FlowTests",
"FlowTests\/testConnect()",
"FlowTests\/testConnectToProviderServer()",
"FlowTests\/testDiscloseProviderCountry()",
"FlowTests\/testEditProfile()",
"FlowTests\/testEditProfileModule()",
"FlowTests\/testPresentProfiles()",
"FlowTests\/testReconnectToOtherProfile()",
"FlowTests\/testShow()",
"MainFlowTests",
"MainFlowTests\/testConnect()",
"MainFlowTests\/testConnectToProviderServer()",
"MainFlowTests\/testDiscloseProviderCountry()",
"MainFlowTests\/testEditProfile()",
"MainFlowTests\/testEditProfileModule()",
"TVFlowTests",
"TVFlowTests\/testConnect()",
"TVFlowTests\/testPresentProfiles()",
"TVFlowTests\/testReconnectToOtherProfile()",
"TVFlowTests\/testShow()",
"TVScreenshotTests",
"TVScreenshotTests\/testTakeScreenshots()"
],
"target" : {
"containerPath" : "container:Passepartout.xcodeproj",
"identifier" : "0E78FE4B2CF799F400B0C5BF",
"name" : "PassepartoutUITests"
}
}
],
"version" : 1
}

View File

@ -1,5 +1,5 @@
//
// FlowTests.swift
// TVFlowTests.swift
// Passepartout
//
// Created by Davide De Rosa on 12/10/24.
@ -28,7 +28,7 @@ import UIAccessibility
import XCTest
@MainActor
final class FlowTests: XCTestCase {
final class TVFlowTests: XCTestCase {
private var app: XCUIApplication!
override func setUp() async throws {

View File

@ -1,5 +1,5 @@
//
// ScreenshotTests.swift
// TVScreenshotTests.swift
// Passepartout
//
// Created by Davide De Rosa on 12/10/24.
@ -28,7 +28,7 @@ import UIAccessibility
import XCTest
@MainActor
final class ScreenshotTests: XCTestCase, XCUIApplicationProviding {
final class TVScreenshotTests: XCTestCase, XCUIApplicationProviding {
let app: XCUIApplication = {
let app = XCUIApplication()
app.appArguments = [.uiTesting]
@ -47,19 +47,19 @@ final class ScreenshotTests: XCTestCase, XCUIApplicationProviding {
.enableProfile(up: 1)
await pause()
try snapshot("01_Connected")
try snapshot("01", "Connected")
root
.presentProfilesWhileConnected()
await pause()
try snapshot("02_ConnectedWithProfileList")
try snapshot("02", "ConnectedWithProfileList")
root
.enableProfile(up: 0)
await pause()
try snapshot("03_OnDemand")
try snapshot("03", "OnDemand")
print("Saved to: \(ScreenshotDestination.temporary.url)")
}

View File

@ -0,0 +1,48 @@
{
"configurations" : [
{
"id" : "00256A48-9D59-44ED-822A-B35521760457",
"name" : "Configuration 1",
"options" : {
}
}
],
"defaultOptions" : {
"testTimeoutsEnabled" : true
},
"testTargets" : [
{
"skippedTests" : [
"FlowTests",
"FlowTests\/testConnect()",
"FlowTests\/testConnectToProviderServer()",
"FlowTests\/testEditProfile()",
"FlowTests\/testEditProfileModule()",
"FlowTests\/testPresentProfiles()",
"FlowTests\/testReconnectToOtherProfile()",
"FlowTests\/testShow()",
"MainFlowTests",
"MainFlowTests\/testConnect()",
"MainFlowTests\/testConnectToProviderServer()",
"MainFlowTests\/testDiscloseProviderCountry()",
"MainFlowTests\/testEditProfile()",
"MainFlowTests\/testEditProfileModule()",
"MainScreenshotTests",
"MainScreenshotTests\/testTakeScreenshots()",
"ScreenshotTests",
"TVFlowTests",
"TVFlowTests\/testConnect()",
"TVFlowTests\/testPresentProfiles()",
"TVFlowTests\/testReconnectToOtherProfile()",
"TVFlowTests\/testShow()"
],
"target" : {
"containerPath" : "container:Passepartout.xcodeproj",
"identifier" : "0E78FE4B2CF799F400B0C5BF",
"name" : "PassepartoutUITests"
}
}
],
"version" : 1
}

7
ci/gen-screenshots.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
cwd=`dirname $0`
devices=("iphone ipad mac appletv")
for device in $devices; do
$cwd/../screenshots/export.sh $device
$cwd/../screenshots/compose-device.sh $device
done

49
screenshots/compose-device.sh Executable file
View File

@ -0,0 +1,49 @@
#!/bin/bash
cwd=`dirname $0`
device=$1
compose_cmd="$cwd/compose.sh"
fastlane_screenshots_root="$cwd/../fastlane/screenshots"
case $device in
"iphone")
nums=("01 02 03 04 05")
template="main"
width=1242
height=2688
fastlane="iOS"
;;
"ipad")
nums=("01 02 03 04 05")
template="main"
width=2048
height=2732
fastlane="iOS"
;;
"mac")
nums=("01 02 03 04 05")
template="main"
width=2880
height=1800
fastlane="macOS"
;;
"appletv")
nums=("01 02 03")
template="tv"
width=3840
height=2160
fastlane="tvOS"
;;
*)
echo "Unknown device: $device"
exit 1
;;
esac
for num in $nums; do
$compose_cmd $template $device $num $width $height "$fastlane_screenshots_root/$fastlane/en-US"
done

30
screenshots/compose.sh Executable file
View File

@ -0,0 +1,30 @@
#!/bin/bash
cwd=`dirname $0`
chrome_app="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
# e.g.: <self> main iphone 01 1242 2688 "fastlane/screenshots/iOS"
template=$1
device=$2
num=$3
width=$4
height=$5
screenshots_root="$6"
# work around Chrome bug
height_bottom_padding="100"
padded_height=$(($height + $height_bottom_padding))
tmp_screenshot_path="tmp.png"
echo "Take screenshot $num for $device..."
page_url="file://`pwd`/$cwd/html/${template}.html?classes=${device},screen-${num}"
"$chrome_app" --headless --disable-gpu --window-size="$width,$padded_height" --screenshot="$tmp_screenshot_path" --virtual-time-budget=10000 "$page_url"
if [[ $device = "ipad" ]]; then
device="ipadPro129"
fi
screenshot_path="$screenshots_root/$device-$num.png"
magick $tmp_screenshot_path -geometry 50% -crop ${width}x${height}+0+0 +repage "$screenshot_path"
rm $tmp_screenshot_path

54
screenshots/export.sh Executable file
View File

@ -0,0 +1,54 @@
#!/bin/bash
cwd=`dirname $0`
device="$1"
xcscheme="PassepartoutUITests"
results_root="$cwd/results"
results_path="$results_root/$device"
screenshots_path="$cwd/html/$device"
mkdir -p "$results_root"
mkdir -p "$screenshots_path"
case $device in
"iphone")
xcplan="MainScreenshots"
xcdestination="name=iPhone 16 Pro Max"
;;
"ipad")
xcplan="MainScreenshots"
xcdestination="name=iPad (10th generation)"
;;
"mac")
xcplan="MainScreenshots"
xcdestination="platform=macOS,arch=arm64"
;;
"appletv")
xcplan="TVScreenshots"
xcdestination="name=Apple TV 4K (3rd generation)"
;;
*)
echo "Unknown device: $device"
exit 1
;;
esac
# 1. run the tests
rm -rf "$results_path"
xcodebuild -scheme "$xcscheme" -testPlan "$xcplan" -destination "$xcdestination" -resultBundlePath "$results_path" test
# 2. parse the screenshots
xcparse screenshots "$results_path" "$screenshots_path"
# 3. drop the filename suffix
cd "$screenshots_path"
for file in 0[1-9]_*.png; do
if [[ -e "$file" ]]; then
new_name="${file%%_*}.png"
mv "$file" "$new_name"
fi
done

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

View File

@ -0,0 +1,55 @@
.appletv #background {
width: 3840px;
height: 2160px;
}
.appletv #background header {
height: 5em;
}
.appletv #background .heading {
font-size: 1.2em;
}
.appletv #box {
left: 50%;
top: 240%;
}
.appletv #appletv {
display: block;
}
#appletv .screenshot {
width: 2376px;
top: 100px;
}
#appletv .frame {
width: 2520px;
top: 10px;
}
.screen-01.appletv .heading {
padding: 2em;
}
.screen-02.appletv .heading {
padding: 1em;
}
.screen-03.appletv .heading {
padding: 1em;
}
.screen-01 #appletv :nth-child(1) {
display: block;
}
.screen-02 #appletv :nth-child(2) {
display: block;
}
.screen-03 #appletv :nth-child(3) {
display: block;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -0,0 +1,71 @@
.ipad #background {
width: 2048px;
height: 2732px;
}
.ipad #background header {
height: 10em;
}
.ipad #background .heading {
font-size: 1.4em;
}
.ipad #box {
left: 60%;
bottom: -95%;
}
.ipad #ipad {
display: block;
}
#ipad .screenshot {
width: 1600px;
top: 100px;
}
#ipad .frame {
width: 1780px;
top: -68px;
}
.screen-01.ipad .heading {
padding: 3.5em;
}
.screen-02.ipad .heading {
padding: 1em;
}
.screen-03.ipad .heading {
padding: 2em;
}
.screen-04.ipad .heading {
padding: 3em;
}
.screen-05.ipad .heading {
padding: 4em;
}
.screen-01 #ipad :nth-child(1) {
display: block;
}
.screen-02 #ipad :nth-child(2) {
display: block;
}
.screen-03 #ipad :nth-child(3) {
display: block;
}
.screen-04 #ipad :nth-child(4) {
display: block;
}
.screen-05 #ipad :nth-child(5) {
display: block;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

View File

@ -0,0 +1,72 @@
.iphone #background {
width: 1242px;
height: 2688px;
}
.iphone #background header {
height: 10em;
}
.iphone #background .heading {
font-size: 1.2em;
}
.iphone #box {
left: 15%;
bottom: -90%;
}
.iphone #iphone {
display: block;
}
#iphone .screenshot {
width: 900px;
top: 100px;
border-radius: 100px;
}
#iphone .frame {
width: 1000px;
top: 70px;
}
.screen-01.iphone .heading {
padding: 2em;
}
.screen-02.iphone .heading {
padding: 1em;
}
.screen-03.iphone .heading {
padding: 1em;
}
.screen-04.iphone .heading {
padding: 1em;
}
.screen-05.iphone .heading {
padding: 1.5em;
}
.screen-01 #iphone :nth-child(1) {
display: block;
}
.screen-02 #iphone :nth-child(2) {
display: block;
}
.screen-03 #iphone :nth-child(3) {
display: block;
}
.screen-04 #iphone :nth-child(4) {
display: block;
}
.screen-05 #iphone :nth-child(5) {
display: block;
}

View File

@ -0,0 +1,63 @@
.mac #background {
width: 2880px;
height: 1800px;
}
.mac #background header {
height: 6em;
}
.mac #background .heading {
font-size: 1.4em;
}
.mac #box {
left: 45%;
top: 140%;
}
.mac #mac {
display: block;
}
#mac .screenshot {
width: 1600px;
top: 20px;
border-radius: 25px;
}
.screen-01.mac .heading {
padding: 5em;
}
.screen-02.mac .heading {
padding: 3em;
}
.screen-03.mac .heading {
padding: 5em;
}
.screen-04.mac .heading {
padding: 6em;
}
.screen-01 #mac :nth-child(1) {
display: block;
}
.screen-02 #mac :nth-child(2) {
display: block;
}
.screen-03 #mac :nth-child(3) {
display: block;
}
.screen-04 #mac :nth-child(4) {
display: block;
}
.screen-05 #mac :nth-child(5) {
display: block;
}

View File

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en" itemscope itemtype="http://schema.org/Blog">
<head>
<link rel="stylesheet" href="style.css?1" />
<link rel="stylesheet" href="iphone/style.css?1" />
<link rel="stylesheet" href="ipad/style.css?1" />
<link rel="stylesheet" href="mac/style.css?1" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link href="https://fonts.googleapis.com/css2?family=Ubuntu&display=swap" rel="stylesheet" />
</head>
<body>
<div id="background">
<div id="box"></div>
<header>
<p class="heading">
All your <em>Profiles</em> in a single <em>App</em>
</p>
<p class="heading">
Turn <em>VPN off</em> automatically when <em>at Home</em>
</p>
<p class="heading">
Use with <em>Shortcuts</em>, <em>iCloud</em> and <em>Apple TV</em>
</p>
<p class="heading">
<em>Override</em> your network settings
</p>
<p class="heading">
<em>Presets</em> for<br />well-known <em>VPN Providers</em>
</p>
</header>
<main>
<div id="iphone" class="container">
<img class="screenshot" src="iphone/01.png" />
<img class="screenshot" src="iphone/02.png" />
<img class="screenshot" src="iphone/03.png" />
<img class="screenshot" src="iphone/04.png" />
<img class="screenshot" src="iphone/05.png" />
<img class="frame" src="iphone/frame.png" />
</div>
<div id="ipad" class="container">
<img class="screenshot" src="ipad/01.png" />
<img class="screenshot" src="ipad/02.png" />
<img class="screenshot" src="ipad/03.png" />
<img class="screenshot" src="ipad/04.png" />
<img class="screenshot" src="ipad/05.png" />
<img class="frame" src="ipad/frame.png" />
</div>
<div id="mac" class="container">
<img class="screenshot" src="mac/01.png" />
<img class="screenshot" src="mac/02.png" />
<img class="screenshot" src="mac/03.png" />
<img class="screenshot" src="mac/04.png" />
<img class="screenshot" src="mac/05.png" />
</div>
</main>
</div>
</body>
<script src="script.js"></script>
</html>

View File

@ -0,0 +1,4 @@
let params = new URLSearchParams(document.location.search);
let classes = params.get("classes").split(",");
document.body.className = classes.join(" ");

View File

@ -0,0 +1,86 @@
* {
margin: 0;
padding: 0;
}
body {
font-family: "Ubuntu", sans-serif;
font-size: 100px;
}
#background {
position: absolute;
background-color: #515d71;
overflow: hidden;
}
header {
height: 10em;
}
header p {
position: relative;
top: 50%;
transform: translateY(-50%);
color: #fff;
text-align: center;
line-height: 1.7em;
}
em {
color: #d69c68;
font-style: normal;
}
main {
position: relative;
}
#box {
position: absolute;
background-color: #d69c68;
width: 250%;
height: 150%;
transform: rotate(45deg);
}
.container {
display: none;
}
.screenshot {
position: relative;
left: 50%;
transform: translateX(-50%);
display: none;
}
.frame {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
.heading {
display: none;
}
.screen-01 .heading:nth-of-type(1) {
display: block;
}
.screen-02 .heading:nth-of-type(2) {
display: block;
}
.screen-03 .heading:nth-of-type(3) {
display: block;
}
.screen-04 .heading:nth-of-type(4) {
display: block;
}
.screen-05 .heading:nth-of-type(5) {
display: block;
}

34
screenshots/html/tv.html Normal file
View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en" itemscope itemtype="http://schema.org/Blog">
<head>
<link rel="stylesheet" href="style.css?1" />
<link rel="stylesheet" href="appletv/style.css?1" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link href="https://fonts.googleapis.com/css2?family=Ubuntu&display=swap" rel="stylesheet" />
</head>
<body>
<div id="background">
<header>
<p class="heading">
<em>OpenVPN</em> and <em>WireGuard</em> on your <em>Apple TV</em>
</p>
<p class="heading">
Synchronize your <em>Profiles</em> within <em>seconds</em>
</p>
<p class="heading">
Customize <em>On-demand</em>, <em>DNS</em>, <em>Proxy</em> and <em>Routing</em>
</p>
</header>
<main>
<div id="box"></div>
<div id="appletv" class="container">
<img class="screenshot" src="appletv/01.png" />
<img class="screenshot" src="appletv/02.png" />
<img class="screenshot" src="appletv/03.png" />
<img class="frame" src="appletv/frame.png" />
</div>
</main>
</div>
</body>
<script src="script.js"></script>
</html>