snapshot 0.10.2 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +92 -333
- data/bin/snapshot +11 -28
- data/lib/assets/SnapfileTemplate +14 -33
- data/lib/assets/SnapshotHelper.swift +50 -0
- data/lib/snapshot.rb +23 -4
- data/lib/snapshot/collector.rb +72 -0
- data/lib/snapshot/dependency_checker.rb +14 -26
- data/lib/snapshot/detect_values.rb +33 -0
- data/lib/snapshot/error_handler.rb +31 -0
- data/lib/snapshot/latest_ios_version.rb +9 -4
- data/lib/snapshot/options.rb +98 -0
- data/lib/snapshot/page.html.erb +1 -1
- data/lib/snapshot/reports_generator.rb +33 -37
- data/lib/snapshot/reset_simulators.rb +4 -4
- data/lib/snapshot/runner.rb +59 -246
- data/lib/snapshot/screenshot_flatten.rb +2 -2
- data/lib/snapshot/screenshot_rotate.rb +7 -9
- data/lib/snapshot/setup.rb +33 -0
- data/lib/snapshot/test_command_generator.rb +93 -0
- data/lib/snapshot/version.rb +2 -1
- metadata +83 -13
- data/lib/assets/SnapshotHelper.js +0 -63
- data/lib/assets/snapshot.js +0 -9
- data/lib/snapshot/builder.rb +0 -85
- data/lib/snapshot/simulators.rb +0 -40
- data/lib/snapshot/snapfile_creator.rb +0 -21
- data/lib/snapshot/snapshot_config.rb +0 -233
- data/lib/snapshot/snapshot_file.rb +0 -87
data/bin/snapshot
CHANGED
@@ -4,12 +4,9 @@ $:.push File.expand_path("../../lib", __FILE__)
|
|
4
4
|
|
5
5
|
require 'snapshot'
|
6
6
|
require 'commander'
|
7
|
-
require 'snapshot/snapfile_creator'
|
8
|
-
require 'snapshot/snapshot_config'
|
9
7
|
|
10
8
|
HighLine.track_eof = false
|
11
9
|
|
12
|
-
|
13
10
|
class SnapshotApplication
|
14
11
|
include Commander::Methods
|
15
12
|
|
@@ -21,38 +18,23 @@ class SnapshotApplication
|
|
21
18
|
program :help, 'GitHub', 'https://github.com/krausefx/snapshot'
|
22
19
|
program :help_formatter, :compact
|
23
20
|
|
24
|
-
global_option
|
25
|
-
global_option '--nobuild', 'Skips the build process when running snapshot.'
|
26
|
-
global_option '--noclean', 'Skips the clean process of the build command when running snapshot.'
|
27
|
-
global_option('--verbose', 'Shows more output including all output printed by Instruments.') { $verbose = true }
|
21
|
+
global_option('--verbose', 'Shows a more verbose output') { $verbose = true }
|
28
22
|
|
29
23
|
always_trace!
|
30
24
|
|
25
|
+
FastlaneCore::CommanderGenerator.new.generate(Snapshot::Options.available_options)
|
26
|
+
|
31
27
|
command :run do |c|
|
32
28
|
c.syntax = 'snapshot'
|
33
29
|
c.description = 'Take new screenshots based on the Snapfile.'
|
34
30
|
|
35
31
|
c.action do |args, options|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
Snapshot::SnapshotConfig.shared_instance(options.snapfile)
|
40
|
-
Snapshot::Runner.new.work(clean: !options.noclean, build: !options.nobuild)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
command :test do |c|
|
46
|
-
c.syntax = 'snapshot test'
|
47
|
-
c.description = 'Runs the automated tests.'
|
32
|
+
o = options.__hash__.dup
|
33
|
+
o.delete(:verbose)
|
34
|
+
Snapshot.config = FastlaneCore::Configuration.create(Snapshot::Options.available_options, o)
|
48
35
|
|
49
|
-
|
50
|
-
|
51
|
-
Dir.chdir(path) do # switch the context
|
52
|
-
Snapshot::DependencyChecker.check_simulators
|
53
|
-
Snapshot::SnapshotConfig.shared_instance(options.snapfile)
|
54
|
-
Snapshot::Runner.new.work(clean: !options.noclean, build: !options.nobuild, take_snapshots: false)
|
55
|
-
end
|
36
|
+
Snapshot::DependencyChecker.check_simulators
|
37
|
+
Snapshot::Runner.new.work
|
56
38
|
end
|
57
39
|
end
|
58
40
|
|
@@ -61,8 +43,9 @@ class SnapshotApplication
|
|
61
43
|
c.description = "Creates a new Snapfile in the current directory"
|
62
44
|
|
63
45
|
c.action do |args, options|
|
64
|
-
|
65
|
-
Snapshot::
|
46
|
+
require 'snapshot/setup'
|
47
|
+
path = (Snapshot::Helper.fastlane_enabled? ? './fastlane' : '.')
|
48
|
+
Snapshot::Setup.create(path)
|
66
49
|
end
|
67
50
|
end
|
68
51
|
|
data/lib/assets/SnapfileTemplate
CHANGED
@@ -6,45 +6,26 @@ devices([
|
|
6
6
|
"iPhone 6 Plus",
|
7
7
|
"iPhone 5",
|
8
8
|
"iPhone 4s",
|
9
|
-
"iPad
|
9
|
+
"iPad Retina"
|
10
10
|
])
|
11
11
|
|
12
12
|
languages([
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
"en-US",
|
14
|
+
"de-DE",
|
15
|
+
"it-IT"
|
16
16
|
])
|
17
17
|
|
18
|
-
#
|
19
|
-
|
20
|
-
|
21
|
-
# clear_previous_screenshots # remove the '#'' to clear all previously generated screenshots before creating new ones
|
22
|
-
|
23
|
-
# JavaScript UIAutomation file
|
24
|
-
# js_file './snapshot.js'
|
25
|
-
|
26
|
-
# The name of the project's scheme
|
27
|
-
# scheme 'SchemeName'
|
28
|
-
|
29
|
-
# Where is your project (or workspace)? Provide the full path here
|
30
|
-
# project_path './YourProject.xcworkspace'
|
31
|
-
|
32
|
-
# By default, the latest version should be used automatically. If you want to change it, do it here
|
33
|
-
# ios_version '8.1'
|
34
|
-
|
35
|
-
# Comment out the line below to add a `SNAPSHOT` preprocessor macro
|
36
|
-
# More information available: https://github.com/krausefx/snapshot#custom-args-for-the-build-command
|
37
|
-
# custom_build_args "GCC_PREPROCESSOR_DEFINITIONS='$(inherited) SNAPSHOT=1' OTHER_SWIFT_FLAGS='$(inherited) -D SNAPSHOT'"
|
18
|
+
# The name of the scheme which contains the UI Tests
|
19
|
+
# scheme "SchemeName"
|
38
20
|
|
21
|
+
# Where should the resulting screenshots be stored?
|
22
|
+
# screenshots_path "./screenshots"
|
39
23
|
|
40
|
-
#
|
24
|
+
# clear_previous_screenshots # remove the '#' to clear all previously generated screenshots before creating new ones
|
41
25
|
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
# end
|
26
|
+
# Choose which project/workspace to use
|
27
|
+
# project "./Project.xcodeproj"
|
28
|
+
# workspace "./Project.xcworkspace"
|
46
29
|
|
47
|
-
#
|
48
|
-
#
|
49
|
-
# system("./cleanup.sh")
|
50
|
-
# end
|
30
|
+
# For more information about all available options run
|
31
|
+
# snapshot --help
|
@@ -0,0 +1,50 @@
|
|
1
|
+
//
|
2
|
+
// SnapshotHelper.swift
|
3
|
+
// Example
|
4
|
+
//
|
5
|
+
// Created by Felix Krause on 10/8/15.
|
6
|
+
// Copyright © 2015 Felix Krause. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
import Foundation
|
10
|
+
import XCTest
|
11
|
+
|
12
|
+
var deviceLanguage = ""
|
13
|
+
|
14
|
+
func setLanguage(app: XCUIApplication)
|
15
|
+
{
|
16
|
+
let path = "/tmp/language.txt"
|
17
|
+
|
18
|
+
do {
|
19
|
+
let locale = try NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding) as String
|
20
|
+
deviceLanguage = locale
|
21
|
+
app.launchArguments = ["-AppleLanguages", "(\(locale))"]
|
22
|
+
} catch {
|
23
|
+
print("Couldn't detect/set language...")
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
func snapshot(name: String, waitForLoadingIndicator: Bool = true)
|
28
|
+
{
|
29
|
+
if (waitForLoadingIndicator)
|
30
|
+
{
|
31
|
+
waitForLoadingIndicatorToDisappear()
|
32
|
+
}
|
33
|
+
print("snapshot: \(name)") // more information about this, check out https://github.com/krausefx/snapshot
|
34
|
+
|
35
|
+
let view = XCUIApplication()
|
36
|
+
let start = view.coordinateWithNormalizedOffset(CGVectorMake(32.10, 30000))
|
37
|
+
let finish = view.coordinateWithNormalizedOffset(CGVectorMake(31, 30000))
|
38
|
+
start.pressForDuration(0, thenDragToCoordinate: finish)
|
39
|
+
sleep(1)
|
40
|
+
}
|
41
|
+
|
42
|
+
func waitForLoadingIndicatorToDisappear()
|
43
|
+
{
|
44
|
+
let query = XCUIApplication().statusBars.childrenMatchingType(.Other).elementBoundByIndex(1).childrenMatchingType(.Other)
|
45
|
+
|
46
|
+
while (query.count > 4) {
|
47
|
+
sleep(1)
|
48
|
+
print("Number of Elements in Status Bar: \(query.count)... waiting for status bar to disappear")
|
49
|
+
}
|
50
|
+
}
|
data/lib/snapshot.rb
CHANGED
@@ -1,18 +1,37 @@
|
|
1
1
|
require 'snapshot/version'
|
2
|
-
require 'snapshot/snapshot_config'
|
3
2
|
require 'snapshot/runner'
|
4
|
-
require 'snapshot/builder'
|
5
|
-
require 'snapshot/snapshot_file'
|
6
3
|
require 'snapshot/reports_generator'
|
4
|
+
require 'snapshot/detect_values'
|
7
5
|
require 'snapshot/screenshot_flatten'
|
8
6
|
require 'snapshot/screenshot_rotate'
|
9
|
-
require 'snapshot/simulators'
|
10
7
|
require 'snapshot/dependency_checker'
|
11
8
|
require 'snapshot/latest_ios_version'
|
9
|
+
require 'snapshot/test_command_generator'
|
10
|
+
require 'snapshot/error_handler'
|
11
|
+
require 'snapshot/collector'
|
12
|
+
require 'snapshot/options'
|
12
13
|
|
13
14
|
require 'fastlane_core'
|
14
15
|
|
16
|
+
require 'open3'
|
17
|
+
|
15
18
|
module Snapshot
|
19
|
+
# Use this to just setup the configuration attribute and set it later somewhere else
|
20
|
+
class << self
|
21
|
+
attr_accessor :config
|
22
|
+
|
23
|
+
attr_accessor :project
|
24
|
+
|
25
|
+
def config=(value)
|
26
|
+
@config = value
|
27
|
+
DetectValues.set_additional_default_values
|
28
|
+
end
|
29
|
+
|
30
|
+
def snapfile_name
|
31
|
+
"Snapfile"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
16
35
|
Helper = FastlaneCore::Helper # you gotta love Ruby: Helper.* should use the Helper class contained in FastlaneCore
|
17
36
|
|
18
37
|
Snapshot::DependencyChecker.check_dependencies
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Snapshot
|
2
|
+
# Responsible for collecting the generated screenshots and copying them over to the output directory
|
3
|
+
class Collector
|
4
|
+
def self.fetch_screenshots(output, language, device_type)
|
5
|
+
# Documentation about how this works in the project README
|
6
|
+
containing = File.join(TestCommandGenerator.derived_data_path, "Logs", "Test")
|
7
|
+
attachments_path = File.join(containing, "Attachments")
|
8
|
+
|
9
|
+
to_store = attachments(containing)
|
10
|
+
matches = output.scan(/snapshot: (.*)/)
|
11
|
+
|
12
|
+
if matches.count != to_store.count
|
13
|
+
Helper.log.error "Looks like the number of screenshots (#{to_store.count}) doesn't match the number of names (#{matches.count})"
|
14
|
+
end
|
15
|
+
|
16
|
+
matches.each_with_index do |current, index|
|
17
|
+
name = current[0]
|
18
|
+
filename = to_store[index]
|
19
|
+
|
20
|
+
language_folder = File.join(Snapshot.config[:output_directory], language)
|
21
|
+
FileUtils.mkdir_p(language_folder)
|
22
|
+
|
23
|
+
device_name = device_type.delete(" ")
|
24
|
+
output_path = File.join(language_folder, [device_name, name].join("-") + ".png")
|
25
|
+
from_path = File.join(attachments_path, filename)
|
26
|
+
if $verbose
|
27
|
+
Helper.log.info "Copying file '#{from_path}' to '#{output_path}'...".green
|
28
|
+
else
|
29
|
+
Helper.log.info "Copying '#{output_path}'...".green
|
30
|
+
end
|
31
|
+
FileUtils.cp(from_path, output_path)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.attachments(containing)
|
36
|
+
Helper.log.info "Collecting screenshots..."
|
37
|
+
|
38
|
+
plist_path = Dir[File.join(containing, "*.plist")].last # we clean the folder before each run
|
39
|
+
Helper.log.info "Loading up '#{plist_path}'..." if $verbose
|
40
|
+
report = Plist.parse_xml(plist_path)
|
41
|
+
|
42
|
+
activities = []
|
43
|
+
report["TestableSummaries"].each do |summary|
|
44
|
+
(summary["Tests"] || []).each do |test|
|
45
|
+
(test["Subtests"] || []).each do |subtest|
|
46
|
+
(subtest["Subtests"] || []).each do |subtest2|
|
47
|
+
(subtest2["Subtests"] || []).each do |subtest3|
|
48
|
+
(subtest3["ActivitySummaries"] || []).each do |activity|
|
49
|
+
# We now check if it's the drag gesture with a negative value
|
50
|
+
was_snapshot = activity["Title"].match(/Press and drag from Target Application.*\[32.10.*\].*/)
|
51
|
+
activities << activity if was_snapshot
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Helper.log.info "Found #{activities.count} screenshots..."
|
60
|
+
|
61
|
+
to_store = [] # contains the names of all the attachments we want to use
|
62
|
+
activities.each do |activity|
|
63
|
+
# We do care about this, all "Long press Target" events mean screenshots
|
64
|
+
attachment_entry = activity["SubActivities"].last # the latest event is fine
|
65
|
+
to_store << attachment_entry["Attachments"].last["FileName"]
|
66
|
+
end
|
67
|
+
|
68
|
+
Helper.log.info "Found #{to_store.join(', ')}" if $verbose
|
69
|
+
return to_store
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -2,13 +2,11 @@ module Snapshot
|
|
2
2
|
class DependencyChecker
|
3
3
|
def self.check_dependencies
|
4
4
|
self.check_xcode_select
|
5
|
-
self.check_xctool
|
6
|
-
self.check_for_automation_subfolder
|
7
5
|
self.check_simctl
|
8
6
|
end
|
9
7
|
|
10
8
|
def self.check_xcode_select
|
11
|
-
unless `xcode-select -v`.include?"xcode-select version
|
9
|
+
unless `xcode-select -v`.include? "xcode-select version"
|
12
10
|
Helper.log.fatal '#############################################################'
|
13
11
|
Helper.log.fatal "# You have to install the Xcode commdand line tools to use snapshot"
|
14
12
|
Helper.log.fatal "# Install the latest version of Xcode from the AppStore"
|
@@ -16,11 +14,21 @@ module Snapshot
|
|
16
14
|
Helper.log.fatal '#############################################################'
|
17
15
|
raise "Run 'xcode-select --install' and start snapshot again"
|
18
16
|
end
|
17
|
+
|
18
|
+
if Snapshot::LatestIosVersion.version.to_f < 9 # to_f is bad, but should be good enough
|
19
|
+
Helper.log.fatal '#############################################################'
|
20
|
+
Helper.log.fatal "# Your xcode-select Xcode version is below 9.0"
|
21
|
+
Helper.log.fatal "# To use snapshot 1.0 and above you need at leat iOS 9"
|
22
|
+
Helper.log.fatal "# Set the path to the Xcode version that supports UI Tests"
|
23
|
+
Helper.log.fatal "# or downgrade to versions older than snapshot 1.0"
|
24
|
+
Helper.log.fatal '#############################################################'
|
25
|
+
raise "Run 'sudo xcode-select -s /Applications/Xcode-beta.app'"
|
26
|
+
end
|
19
27
|
end
|
20
28
|
|
21
29
|
def self.check_simulators
|
22
|
-
Helper.log.debug "Found #{
|
23
|
-
if
|
30
|
+
Helper.log.debug "Found #{FastlaneCore::Simulator.all.count} simulators." if $verbose
|
31
|
+
if FastlaneCore::Simulator.all.count == 0
|
24
32
|
Helper.log.fatal '#############################################################'
|
25
33
|
Helper.log.fatal "# You have to add new simulators using Xcode"
|
26
34
|
Helper.log.fatal "# You can let snapshot create new simulators: 'snapshot reset_simulators'"
|
@@ -31,28 +39,8 @@ module Snapshot
|
|
31
39
|
end
|
32
40
|
end
|
33
41
|
|
34
|
-
def self.xctool_installed?
|
35
|
-
return `which xctool`.length > 1
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.check_xctool
|
39
|
-
if not self.xctool_installed?
|
40
|
-
Helper.log.info '#############################################################'
|
41
|
-
Helper.log.info "# xctool is recommended to build the apps"
|
42
|
-
Helper.log.info "# Install it using 'brew install xctool'"
|
43
|
-
Helper.log.info "# Falling back to xcodebuild instead "
|
44
|
-
Helper.log.info '#############################################################'
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def self.check_for_automation_subfolder
|
49
|
-
if File.directory?"./Automation" or File.exists?"./Automation"
|
50
|
-
raise "Seems like you have an 'Automation' folder in the current directory. You need to delete/rename it!".red
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
42
|
def self.check_simctl
|
55
|
-
unless `xcrun simctl`.include?"openurl"
|
43
|
+
unless `xcrun simctl`.include? "openurl"
|
56
44
|
raise "Could not find `xcrun simctl`. Make sure you have the latest version of Xcode and Mac OS installed.".red
|
57
45
|
end
|
58
46
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Snapshot
|
2
|
+
class DetectValues
|
3
|
+
# This is needed as these are more complex default values
|
4
|
+
def self.set_additional_default_values
|
5
|
+
config = Snapshot.config
|
6
|
+
|
7
|
+
FastlaneCore::Project.detect_projects(config)
|
8
|
+
|
9
|
+
Snapshot.project = FastlaneCore::Project.new(config)
|
10
|
+
|
11
|
+
# Go into the project's folder
|
12
|
+
Dir.chdir(File.expand_path("..", Snapshot.project.path)) do
|
13
|
+
config.load_configuration_file(Snapshot.snapfile_name)
|
14
|
+
end
|
15
|
+
|
16
|
+
Snapshot.project.select_scheme
|
17
|
+
|
18
|
+
# Devices
|
19
|
+
unless config[:devices]
|
20
|
+
config[:devices] = []
|
21
|
+
|
22
|
+
# We only care about a subset of the simulators
|
23
|
+
FastlaneCore::Simulator.all.each do |sim|
|
24
|
+
next if sim.name.include?("iPad") and !sim.name.include?("Retina") # we only need one iPad
|
25
|
+
next if sim.name.include?("6s") # same screen resolution
|
26
|
+
next if sim.name.include?("5s") # same screen resolution
|
27
|
+
|
28
|
+
config[:devices] << sim.name
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Snapshot
|
2
|
+
# This classes methods are called when something goes wrong in the building process
|
3
|
+
class ErrorHandler
|
4
|
+
class TestsFailedException < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
class << self
|
8
|
+
# @param [Array] The output of the errored build (line by line)
|
9
|
+
# This method should raise an exception in any case, as the return code indicated a failed build
|
10
|
+
def handle_test_error(output, return_code)
|
11
|
+
# The order of the handling below is import
|
12
|
+
|
13
|
+
if return_code == 65
|
14
|
+
raise TestsFailedException.new, "Tests failed - check out the log above".red
|
15
|
+
end
|
16
|
+
|
17
|
+
case output
|
18
|
+
when /com\.apple\.CoreSimulator\.SimError/
|
19
|
+
print "The simulator failed to launch - retrying..."
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Just to make things easier
|
26
|
+
def print(text)
|
27
|
+
Helper.log.error text.red
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -5,9 +5,14 @@ module Snapshot
|
|
5
5
|
return ENV["SNAPSHOT_IOS_VERSION"] if ENV["SNAPSHOT_IOS_VERSION"]
|
6
6
|
return @version if @version
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
# We do all this, because we would get all kind of crap output generated by xcodebuild
|
9
|
+
# so we need to ignore stderror
|
10
|
+
output = ''
|
11
|
+
Open3.popen3('xcodebuild -version -sdk') do |stdin, stdout, stderr, wait_thr|
|
12
|
+
output = stdout.read
|
13
|
+
end
|
14
|
+
|
15
|
+
matched = output.match(/iOS ([\d\.]+) \(.*/)
|
11
16
|
if matched.length > 1
|
12
17
|
return @version ||= matched[1]
|
13
18
|
else
|
@@ -15,4 +20,4 @@ module Snapshot
|
|
15
20
|
end
|
16
21
|
end
|
17
22
|
end
|
18
|
-
end
|
23
|
+
end
|