Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
28dfb445
Unverified
Commit
28dfb445
authored
Aug 06, 2021
by
Jenn Magder
Committed by
GitHub
Aug 06, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add native iOS screenshots to integration_test (#84611)
parent
a6947785
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
124 additions
and
50 deletions
+124
-50
IDEWorkspaceChecks.plist
.../Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+8
-0
README.md
packages/integration_test/README.md
+5
-4
project.pbxproj
...gration_test/example/ios/Runner.xcodeproj/project.pbxproj
+0
-28
IntegrationTestIosTest.h
...ges/integration_test/ios/Classes/IntegrationTestIosTest.h
+26
-4
IntegrationTestIosTest.m
...ges/integration_test/ios/Classes/IntegrationTestIosTest.m
+17
-1
IntegrationTestPlugin.h
...ages/integration_test/ios/Classes/IntegrationTestPlugin.h
+10
-2
IntegrationTestPlugin.m
...ages/integration_test/ios/Classes/IntegrationTestPlugin.m
+43
-3
integration_test.podspec
packages/integration_test/ios/integration_test.podspec
+2
-0
_callback_io.dart
packages/integration_test/lib/_callback_io.dart
+13
-8
No files found.
dev/integration_tests/ui/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
0 → 100644
View file @
28dfb445
<
?xml
v
e
rsion="
1
.
0
"
e
n
c
o
d
ing="UT
F
-
8
"?
>
<
!
D
O
C
TYP
E
plist
PU
B
LI
C
"-//
A
ppl
e
//
D
T
D
PLIST
1
.
0
//
E
N"
"http://www.
a
ppl
e
.
c
om/
D
T
D
s/Prop
e
rtyList-
1
.
0
.
d
t
d
"
>
<
plist
v
e
rsion="
1
.
0
"
>
<
d
i
c
t
>
<
k
e
y
>
IDEDidComputeMac32BitWarning
<
/k
e
y
>
<
tru
e
/
>
<
/
d
i
c
t
>
<
/plist
>
packages/integration_test/README.md
View file @
28dfb445
...
...
@@ -100,9 +100,9 @@ flutter drive \
You can use
`integration_test`
to take screenshots of the UI rendered on the mobile device or
Web browser at a specific time during the test.
This feature is currently supported on Android, and Web.
This feature is currently supported on Android,
iOS,
and Web.
#### Android
#### Android
and iOS
**integration_test/screenshot_test.dart**
...
...
@@ -115,7 +115,7 @@ void main() {
// Build the app.
app
.
main
();
// This is required prior to taking the screenshot.
// This is required prior to taking the screenshot
(Android only)
.
await
binding
.
convertFlutterSurfaceToImage
();
// Trigger a frame.
...
...
@@ -126,7 +126,8 @@ void main() {
```
You can use a driver script to pull in the screenshot from the device.
This way, you can store the images locally on your computer.
This way, you can store the images locally on your computer. On iOS, the
screenshot will also be available in Xcode test results.
**test_driver/integration_test.dart**
...
...
packages/integration_test/example/ios/Runner.xcodeproj/project.pbxproj
View file @
28dfb445
...
...
@@ -475,21 +475,11 @@
baseConfigurationReference
=
09505407E99803EF7AA92DE7
/* Pods-RunnerTests.debug.xcconfig */
;
buildSettings
=
{
BUNDLE_LOADER
=
"$(TEST_HOST)"
;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION
=
YES_AGGRESSIVE
;
CLANG_CXX_LANGUAGE_STANDARD
=
"gnu++14"
;
CLANG_ENABLE_OBJC_WEAK
=
YES
;
CLANG_WARN_DOCUMENTATION_COMMENTS
=
YES
;
CLANG_WARN_UNGUARDED_AVAILABILITY
=
YES_AGGRESSIVE
;
CODE_SIGN_STYLE
=
Automatic
;
GCC_C_LANGUAGE_STANDARD
=
gnu11
;
INFOPLIST_FILE
=
RunnerTests/Info.plist
;
IPHONEOS_DEPLOYMENT_TARGET
=
13.0
;
LD_RUNPATH_SEARCH_PATHS
=
"$(inherited) @executable_path/Frameworks @loader_path/Frameworks"
;
MTL_ENABLE_DEBUG_INFO
=
INCLUDE_SOURCE
;
MTL_FAST_MATH
=
YES
;
PRODUCT_BUNDLE_IDENTIFIER
=
com.example.instrumentationAdapterExample.RunnerTests
;
PRODUCT_NAME
=
"$(TARGET_NAME)"
;
TARGETED_DEVICE_FAMILY
=
"1,2"
;
TEST_HOST
=
"$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"
;
};
name
=
Debug
;
...
...
@@ -499,20 +489,11 @@
baseConfigurationReference
=
FCE3953801588FC13ED9E898
/* Pods-RunnerTests.release.xcconfig */
;
buildSettings
=
{
BUNDLE_LOADER
=
"$(TEST_HOST)"
;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION
=
YES_AGGRESSIVE
;
CLANG_CXX_LANGUAGE_STANDARD
=
"gnu++14"
;
CLANG_ENABLE_OBJC_WEAK
=
YES
;
CLANG_WARN_DOCUMENTATION_COMMENTS
=
YES
;
CLANG_WARN_UNGUARDED_AVAILABILITY
=
YES_AGGRESSIVE
;
CODE_SIGN_STYLE
=
Automatic
;
GCC_C_LANGUAGE_STANDARD
=
gnu11
;
INFOPLIST_FILE
=
RunnerTests/Info.plist
;
IPHONEOS_DEPLOYMENT_TARGET
=
13.0
;
LD_RUNPATH_SEARCH_PATHS
=
"$(inherited) @executable_path/Frameworks @loader_path/Frameworks"
;
MTL_FAST_MATH
=
YES
;
PRODUCT_BUNDLE_IDENTIFIER
=
com.example.instrumentationAdapterExample.RunnerTests
;
PRODUCT_NAME
=
"$(TARGET_NAME)"
;
TARGETED_DEVICE_FAMILY
=
"1,2"
;
TEST_HOST
=
"$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"
;
};
name
=
Release
;
...
...
@@ -522,20 +503,11 @@
baseConfigurationReference
=
750225973AAB5D7832AFA60C
/* Pods-RunnerTests.profile.xcconfig */
;
buildSettings
=
{
BUNDLE_LOADER
=
"$(TEST_HOST)"
;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION
=
YES_AGGRESSIVE
;
CLANG_CXX_LANGUAGE_STANDARD
=
"gnu++14"
;
CLANG_ENABLE_OBJC_WEAK
=
YES
;
CLANG_WARN_DOCUMENTATION_COMMENTS
=
YES
;
CLANG_WARN_UNGUARDED_AVAILABILITY
=
YES_AGGRESSIVE
;
CODE_SIGN_STYLE
=
Automatic
;
GCC_C_LANGUAGE_STANDARD
=
gnu11
;
INFOPLIST_FILE
=
RunnerTests/Info.plist
;
IPHONEOS_DEPLOYMENT_TARGET
=
13.0
;
LD_RUNPATH_SEARCH_PATHS
=
"$(inherited) @executable_path/Frameworks @loader_path/Frameworks"
;
MTL_FAST_MATH
=
YES
;
PRODUCT_BUNDLE_IDENTIFIER
=
com.example.instrumentationAdapterExample.RunnerTests
;
PRODUCT_NAME
=
"$(TARGET_NAME)"
;
TARGETED_DEVICE_FAMILY
=
"1,2"
;
TEST_HOST
=
"$(BUILT_PRODUCTS_DIR)/Runner.app/Runner"
;
};
name
=
Profile
;
...
...
packages/integration_test/ios/Classes/IntegrationTestIosTest.h
View file @
28dfb445
...
...
@@ -4,23 +4,45 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol
FLTIntegrationTestScreenshotDelegate
;
@interface
IntegrationTestIosTest
:
NSObject
-
(
BOOL
)
testIntegrationTest
:(
NSString
**
)
testResult
;
-
(
instancetype
)
initWithScreenshotDelegate
:(
nullable
id
<
FLTIntegrationTestScreenshotDelegate
>
)
delegate
NS_DESIGNATED_INITIALIZER
;
/**
* Initate dart tests and wait for results. @c testResult will be set to a string describing the results.
*
* @return @c YES if all tests succeeded.
*/
-
(
BOOL
)
testIntegrationTest
:(
NSString
*
_Nullable
*
_Nullable
)
testResult
;
@end
#define INTEGRATION_TEST_IOS_RUNNER(__test_class) \
@interface __test_class : XCTestCase
\
@interface __test_class : XCTestCase
<FLTIntegrationTestScreenshotDelegate>
\
@end \
\
@implementation __test_class \
\
-
(void)testIntegrationTest {
\
-
(void)testIntegrationTest {
\
NSString *testResult; \
IntegrationTestIosTest *integrationTestIosTest =
[[IntegrationTestIosTest alloc] init
]; \
IntegrationTestIosTest *integrationTestIosTest =
integrationTestIosTest = [[IntegrationTestIosTest alloc] initWithScreenshotDelegate:self
]; \
BOOL testPass = [integrationTestIosTest testIntegrationTest:&testResult]; \
XCTAssertTrue(testPass, @"%@", testResult); \
} \
\
- (void)didTakeScreenshot:(UIImage *)screenshot attachmentName:(NSString *)name { \
XCTAttachment *attachment = [XCTAttachment attachmentWithImage:screenshot]; \
attachment.lifetime = XCTAttachmentLifetimeKeepAlways; \
if (name != nil) { \
attachment.name = name; \
} \
[self addAttachment:attachment]; \
} \
\
@end
NS_ASSUME_NONNULL_END
packages/integration_test/ios/Classes/IntegrationTestIosTest.m
View file @
28dfb445
...
...
@@ -5,10 +5,26 @@
#import "IntegrationTestIosTest.h"
#import "IntegrationTestPlugin.h"
@interface
IntegrationTestIosTest
()
@property
(
nonatomic
)
IntegrationTestPlugin
*
integrationTestPlugin
;
@end
@implementation
IntegrationTestIosTest
-
(
instancetype
)
initWithScreenshotDelegate
:(
id
<
FLTIntegrationTestScreenshotDelegate
>
)
delegate
{
self
=
[
super
init
];
_integrationTestPlugin
=
[
IntegrationTestPlugin
instance
];
_integrationTestPlugin
.
screenshotDelegate
=
delegate
;
return
self
;
}
-
(
instancetype
)
init
{
return
[
self
initWithScreenshotDelegate
:
nil
];
}
-
(
BOOL
)
testIntegrationTest
:
(
NSString
**
)
testResult
{
IntegrationTestPlugin
*
integrationTestPlugin
=
[
IntegrationTestPlugin
instance
];
IntegrationTestPlugin
*
integrationTestPlugin
=
self
.
integrationTestPlugin
;
UIViewController
*
rootViewController
=
[[[[
UIApplication
sharedApplication
]
delegate
]
window
]
rootViewController
];
if
(
!
[
rootViewController
isKindOfClass
:[
FlutterViewController
class
]])
{
...
...
packages/integration_test/ios/Classes/IntegrationTestPlugin.h
View file @
28dfb445
...
...
@@ -6,14 +6,20 @@
NS_ASSUME_NONNULL_BEGIN
@protocol
FLTIntegrationTestScreenshotDelegate
/** This will be called when a dart integration test triggers a window screenshot with @c takeScreenshot. */
-
(
void
)
didTakeScreenshot
:(
UIImage
*
)
screenshot
attachmentName
:(
nullable
NSString
*
)
name
;
@end
/** A Flutter plugin that's responsible for communicating the test results back
* to iOS XCTest. */
@interface
IntegrationTestPlugin
:
NSObject
<
FlutterPlugin
>
/**
* Test results that are sent from Dart when integration test completes. Before the
* completion, it is
* @c nil.
* completion, it is @c nil.
*/
@property
(
nonatomic
,
readonly
,
nullable
)
NSDictionary
<
NSString
*
,
NSString
*>
*
testResults
;
...
...
@@ -24,6 +30,8 @@ NS_ASSUME_NONNULL_BEGIN
-
(
instancetype
)
init
NS_UNAVAILABLE
;
@property
(
weak
,
nonatomic
)
id
<
FLTIntegrationTestScreenshotDelegate
>
screenshotDelegate
;
@end
NS_ASSUME_NONNULL_END
packages/integration_test/ios/Classes/IntegrationTestPlugin.m
View file @
28dfb445
...
...
@@ -2,10 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@import
UIKit
;
#import "IntegrationTestPlugin.h"
static
NSString
*
const
kIntegrationTestPluginChannel
=
@"plugins.flutter.io/integration_test"
;
static
NSString
*
const
kMethodTestFinished
=
@"allTestsFinished"
;
static
NSString
*
const
kMethodScreenshot
=
@"captureScreenshot"
;
static
NSString
*
const
kMethodConvertSurfaceToImage
=
@"convertFlutterSurfaceToImage"
;
static
NSString
*
const
kMethodRevertImage
=
@"revertFlutterImage"
;
@interface
IntegrationTestPlugin
()
...
...
@@ -39,20 +44,55 @@ static NSString *const kMethodTestFinished = @"allTestsFinished";
-
(
void
)
setupChannels
:
(
id
<
FlutterBinaryMessenger
>
)
binaryMessenger
{
FlutterMethodChannel
*
channel
=
[
FlutterMethodChannel
methodChannelWithName
:
kIntegrationTestPluginChannel
binaryMessenger
:
binaryMessenger
];
[
FlutterMethodChannel
methodChannelWithName
:
kIntegrationTestPluginChannel
binaryMessenger
:
binaryMessenger
];
[
channel
setMethodCallHandler
:
^
(
FlutterMethodCall
*
call
,
FlutterResult
result
)
{
[
self
handleMethodCall
:
call
result
:
result
];
}];
}
-
(
void
)
handleMethodCall
:
(
FlutterMethodCall
*
)
call
result
:
(
FlutterResult
)
result
{
if
([
kMethodTestFinished
isEqual
:
call
.
metho
d
])
{
if
([
call
.
method
isEqualToString
:
kMethodTestFinishe
d
])
{
self
.
testResults
=
call
.
arguments
[
@"results"
];
result
(
nil
);
}
else
if
([
call
.
method
isEqualToString
:
kMethodScreenshot
])
{
// If running as a native Xcode test, attach to test.
UIImage
*
screenshot
=
[
self
capturePngScreenshot
];
NSString
*
name
=
call
.
arguments
[
@"name"
];
[
self
.
screenshotDelegate
didTakeScreenshot
:
screenshot
attachmentName
:
name
];
// Also pass back along the channel for the driver to handle.
NSData
*
pngData
=
UIImagePNGRepresentation
(
screenshot
);
result
([
FlutterStandardTypedData
typedDataWithBytes
:
pngData
]);
}
else
if
([
call
.
method
isEqualToString
:
kMethodConvertSurfaceToImage
]
||
[
call
.
method
isEqualToString
:
kMethodRevertImage
])
{
// Android only, no-op on iOS.
result
(
nil
);
}
else
{
result
(
FlutterMethodNotImplemented
);
}
}
-
(
UIImage
*
)
capturePngScreenshot
{
UIWindow
*
window
=
[
UIApplication
.
sharedApplication
.
windows
filteredArrayUsingPredicate
:[
NSPredicate
predicateWithFormat
:
@"keyWindow = YES"
]].
firstObject
;
CGRect
screenshotBounds
=
window
.
bounds
;
UIImage
*
image
;
if
(
@available
(
iOS
10
,
*
))
{
UIGraphicsImageRenderer
*
renderer
=
[[
UIGraphicsImageRenderer
alloc
]
initWithBounds
:
screenshotBounds
];
image
=
[
renderer
imageWithActions
:
^
(
UIGraphicsImageRendererContext
*
rendererContext
)
{
[
window
drawViewHierarchyInRect
:
screenshotBounds
afterScreenUpdates
:
YES
];
}];
}
else
{
UIGraphicsBeginImageContextWithOptions
(
screenshotBounds
.
size
,
NO
,
UIScreen
.
mainScreen
.
scale
);
[
window
drawViewHierarchyInRect
:
screenshotBounds
afterScreenUpdates
:
YES
];
image
=
UIGraphicsGetImageFromCurrentImageContext
();
UIGraphicsEndImageContext
();
}
return
image
;
}
@end
packages/integration_test/ios/integration_test.podspec
View file @
28dfb445
...
...
@@ -18,6 +18,8 @@ LICENSE
s
.
source_files
=
'Classes/**/*'
s
.
public_header_files
=
'Classes/**/*.h'
s
.
dependency
'Flutter'
s
.
ios
.
framework
=
'UIKit'
s
.
platform
=
:ios
,
'8.0'
s
.
pod_target_xcconfig
=
{
'DEFINES_MODULE'
=>
'YES'
,
'EXCLUDED_ARCHS[sdk=iphonesimulator*]'
=>
'i386'
}
end
packages/integration_test/lib/_callback_io.dart
View file @
28dfb445
...
...
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:io'
show
Platform
;
import
'dart:ui'
;
import
'package:flutter/services.dart'
;
...
...
@@ -60,37 +61,41 @@ class IOCallbackManager implements CallbackManager {
// comes up in the future. For example: `WebCallbackManager.cleanup`.
}
//
Whether the Flutter surface uses an Image
.
bool
_
usesFlutterImage
=
false
;
//
[convertFlutterSurfaceToImage] has been called and [takeScreenshot] is ready to capture the surface (Android only)
.
bool
_
isSurfaceRendered
=
false
;
@override
Future
<
void
>
convertFlutterSurfaceToImage
()
async
{
assert
(!
_usesFlutterImage
,
'Surface already converted to an image'
);
if
(!
Platform
.
isAndroid
)
{
// No-op on other platforms.
return
;
}
assert
(!
_isSurfaceRendered
,
'Surface already converted to an image'
);
await
integrationTestChannel
.
invokeMethod
<
void
>(
'convertFlutterSurfaceToImage'
,
null
,
);
_
usesFlutterImage
=
true
;
_
isSurfaceRendered
=
true
;
addTearDown
(()
async
{
assert
(
_
usesFlutterImage
,
'Surface is not an image'
);
assert
(
_
isSurfaceRendered
,
'Surface is not an image'
);
await
integrationTestChannel
.
invokeMethod
<
void
>(
'revertFlutterImage'
,
null
,
);
_
usesFlutterImage
=
false
;
_
isSurfaceRendered
=
false
;
});
}
@override
Future
<
Map
<
String
,
dynamic
>>
takeScreenshot
(
String
screenshot
)
async
{
if
(
!
_usesFlutterImage
)
{
if
(
Platform
.
isAndroid
&&
!
_isSurfaceRendered
)
{
throw
StateError
(
'Call convertFlutterSurfaceToImage() before taking a screenshot'
);
}
integrationTestChannel
.
setMethodCallHandler
(
_onMethodChannelCall
);
final
List
<
int
>?
rawBytes
=
await
integrationTestChannel
.
invokeMethod
<
List
<
int
>>(
'captureScreenshot'
,
null
,
<
String
,
dynamic
>{
'name'
:
screenshot
}
,
);
if
(
rawBytes
==
null
)
{
throw
StateError
(
'Expected a list of bytes, but instead captureScreenshot returned null'
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment