Skip to content

Commit

Permalink
Merge pull request mmcc007#88 from mmcc007/#66_landscape_mode
Browse files Browse the repository at this point in the history
Added landscape feature
  • Loading branch information
mmcc007 authored Jul 17, 2019
2 parents e0a5d13 + a603f5c commit 68758d1
Show file tree
Hide file tree
Showing 17 changed files with 515 additions and 159 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,30 +145,26 @@ _Screenshots_ uses a configuration file to configure a run.
The default config filename is `screenshots.yaml`:
````yaml
# A list of screen capture tests
# Note: flutter driver expects a pair of files eg, main1.dart and main1_test.dart
tests:
- test_driver/main1.dart
- test_driver/main2.dart

# Note: flutter driver expects a pair of files for testing
# For example:
# main1.dart is the test app (that calls your app)
# main1_test.dart is the matching test that flutter driver
# expects to find.

# Interim location of screenshots from tests
staging: /tmp/screenshots

# A list of locales supported by the app
locales:
- de-DE
- en-US
- de-DE

# A map of devices to emulate
devices:
ios:
iPhone XS Max:
frame: false
iPad Pro (12.9-inch) (3rd generation):
orientation: LandscapeRight
android:
Nexus 6P:

Expand All @@ -182,9 +178,13 @@ Individual devices can be configured in `screenshots.yaml` by specifying per dev
| Parameter | Values | Required | Description |
| --- | --- | --- | --- |
|frame|true/false|optional|Controls whether screenshots generated on the device should be placed in a frame. Overrides the global frame setting (see example `screenshots.yaml` above).
|orientation|Portrait \| LandscapeRight \| PortraitUpsideDown \| LandscapeLeft|optional|Controls orientation of device during test. Currently disables framing resulting in a raw screenshot. Ignored for real devices.


Note: images generated for those devices where framing is disabled are probably not suitable for upload, but can be used for local review.

Note: orientation on iOS simulators is implemented using an AppleScript script which requires granting permission on first use.

# Record/Compare Mode
_Screenshots_ can be used to monitor any unexpected changes to the UI by comparing the new screenshots to previously recorded screenshots. Any differences will be highlighted in a 'diff' image for review.

Expand Down
6 changes: 4 additions & 2 deletions example/screenshots.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ staging: /tmp/screenshots

# A list of locales supported in app
locales:
- fr-CA
- en-US
- fr-CA

# A list of devices to run tests on
devices:
ios:
iPhone XS Max:
iPad Pro (12.9-inch) (2nd generation):
frame: false
iPad Pro (12.9-inch) (2nd generation):
orientation: LandscapeRight
android:
Nexus 6P:
orientation: LandscapeRight

# Frame screenshots
frame: true
Expand Down
2 changes: 1 addition & 1 deletion example/screenshots_CI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ staging: /tmp/screenshots

# A list of locales supported in app
locales:
- fr-CA
- en-US
- fr-CA

# A list of devices to run tests on
devices:
Expand Down
31 changes: 31 additions & 0 deletions lib/resources/script/sim_orientation.scpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
on run argv
my do_submenu("Simulator", "Hardware", "Orientation", item 1 of argv)
return item 1 of argv
end run

on do_submenu(app_name, menu_name, menu_item, submenu_item)
try
-- bring the target application to the front
tell application app_name
activate
end tell
tell application "System Events"
tell process app_name
tell menu bar 1
tell menu bar item menu_name
tell menu menu_name
tell menu item menu_item
tell menu menu_item
click menu item submenu_item
end tell
end tell
end tell
end tell
end tell
end tell
end tell
return true
on error error_message
return false
end try
end do_submenu
5 changes: 1 addition & 4 deletions lib/src/archive.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@ import 'utils.dart' as utils;
class Archive {
static final _timeStamp = getTimestamp();

final _stagingTestDir;
final archiveDirPrefix;

Archive(String stagingDir, String archiveDir)
: _stagingTestDir = '$stagingDir/$kTestScreenshotsDir',
archiveDirPrefix = '$archiveDir/$_timeStamp';
Archive(String archiveDir) : archiveDirPrefix = '$archiveDir/$_timeStamp';

String dstDir(DeviceType deviceType, String locale) =>
'$archiveDirPrefix/${utils.getStringFromEnum(deviceType)}/$locale';
Expand Down
55 changes: 54 additions & 1 deletion lib/src/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'dart:io';
import 'package:meta/meta.dart';

import 'image_processor.dart';
import 'orientation.dart';
import 'screens.dart';
import 'package:yaml/yaml.dart';
import 'utils.dart' as utils;
Expand Down Expand Up @@ -40,7 +41,7 @@ class Config {
/// (called by screenshots)
@visibleForTesting
Future<void> storeEnv(Screens screens, String emulatorName, String locale,
String deviceType) async {
String deviceType, String orientation) async {
// store env for later use by tests
final screenProps = screens.screenProps(emulatorName);
final screenSize = screenProps == null ? null : screenProps['size'];
Expand All @@ -49,6 +50,7 @@ class Config {
'locale': locale,
'device_name': emulatorName,
'device_type': deviceType,
'orientation': orientation
};
await _envStore.writeAsString(json.encode(currentEnv));
}
Expand All @@ -62,6 +64,33 @@ class Config {
@visibleForTesting // config is exported in library
Future<bool> validate(
Screens screens, List allDevices, List allEmulators) async {
// validate params
final deviceNames = utils.getAllConfiguredDeviceNames(configInfo);
for (final devName in deviceNames) {
final deviceInfo = findDeviceInfo(configInfo, devName);
if (deviceInfo != null) {
final orientation = deviceInfo['orientation'];
if (orientation != null && !isValidOrientation(orientation)) {
stderr.writeln(
'Invalid value for \'orientation\' for device \'$devName\': $orientation');
stderr.writeln('Valid values:');
for (final orientation in Orientation.values) {
stderr.writeln(' ${utils.getStringFromEnum(orientation)}');
}
exit(1);
}
final frame = deviceInfo['frame'];
if (frame != null && !isValidFrame(frame)) {
stderr.writeln(
'Invalid value for \'frame\' for device \'$devName\': $frame');
stderr.writeln('Valid values:');
stderr.writeln(' true');
stderr.writeln(' false');
exit(1);
}
}
}

final isDeviceAttached = (device) => device != null;
final isEmulatorInstalled = (emulator) => emulator != null;

Expand Down Expand Up @@ -209,3 +238,27 @@ class Config {
simulators.forEach((simulator, _) => stdout.write(' $simulator\n'));
}
}

bool isValidOrientation(String orientation) {
return Orientation.values.firstWhere(
(o) => utils.getStringFromEnum(o) == orientation,
orElse: () => null) !=
null;
}

bool isValidFrame(dynamic frame) {
return frame != null && (frame == true || frame == false);
}

/// Find device info in config for device name.
Map findDeviceInfo(Map configInfo, String deviceName) {
Map deviceInfo;
configInfo['devices'].forEach((deviceType, devices) {
if (devices != null) {
devices.forEach((_deviceName, _deviceInfo) {
if (_deviceName == deviceName) deviceInfo = _deviceInfo;
});
}
});
return deviceInfo;
}
14 changes: 14 additions & 0 deletions lib/src/daemon_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,17 @@ List iosDevices() {
return device;
}).toList();
}

/// Wait for emulator or simulator to start
Future waitForEmulatorToStart(
DaemonClient daemonClient, String deviceId) async {
bool started = false;
while (!started) {
final devices = await daemonClient.devices;
final device = devices.firstWhere(
(device) => device['id'] == deviceId && device['emulator'],
orElse: () => null);
started = device != null;
await Future.delayed(Duration(milliseconds: 1000));
}
}
3 changes: 2 additions & 1 deletion lib/src/fastlane.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Future _clearFastlaneDir(Screens screens, String deviceName, String locale,
/// Generate fastlane dir path for ios or android.
String getDirPath(
DeviceType deviceType, String locale, String androidModelType) {
locale = locale.replaceAll('_', '-'); // in case canonicalized
const androidPrefix = 'android/fastlane/metadata/android';
const iosPrefix = 'ios/fastlane/screenshots';
String dirPath;
Expand Down Expand Up @@ -82,4 +83,4 @@ void deleteMatchingFiles(String dirPath, RegExp pattern) {
} else {
Directory(dirPath).createSync(recursive: true);
}
}
}
49 changes: 28 additions & 21 deletions lib/src/image_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,32 +40,36 @@ class ImageProcessor {
if (screenProps == null) {
print('Warning: \'$deviceName\' images will not be processed');
} else {
final Map screenResources = screenProps['resources'];
final staging = _config['staging'];
// add frame if required
if (isFrameRequired(_config, deviceType, deviceName)) {
final Map screenResources = screenProps['resources'];
final staging = _config['staging'];
// print('screenResources=$screenResources');
print('Processing screenshots from test...');
print('Processing screenshots from test...');

// unpack images for screen from package to local staging area
await resources.unpackImages(screenResources, staging);
// unpack images for screen from package to local staging area
await resources.unpackImages(screenResources, staging);

// add status and nav bar and frame for each screenshot
final screenshots = Directory('$staging/$kTestScreenshotsDir').listSync();
for (final screenshotPath in screenshots) {
// add status bar for each screenshot
// add status and nav bar and frame for each screenshot
final screenshots =
Directory('$staging/$kTestScreenshotsDir').listSync();
for (final screenshotPath in screenshots) {
// add status bar for each screenshot
// print('overlaying status bar over screenshot at $screenshotPath');
await overlay(_config, screenResources, screenshotPath.path);
await overlay(_config, screenResources, screenshotPath.path);

if (deviceType == DeviceType.android) {
// add nav bar for each screenshot
if (deviceType == DeviceType.android) {
// add nav bar for each screenshot
// print('appending navigation bar to screenshot at $screenshotPath');
await append(_config, screenResources, screenshotPath.path);
}
await append(_config, screenResources, screenshotPath.path);
}

// add frame if required
if (isFrameRequired(_config, deviceType, deviceName)) {
// print('placing $screenshotPath in frame');
await frame(_config, screenProps, screenshotPath.path, deviceType, runMode);
await frame(
_config, screenProps, screenshotPath.path, deviceType, runMode);
}
} else {
print('Warning: framing is not enabled');
}
}

Expand Down Expand Up @@ -190,9 +194,12 @@ class ImageProcessor {
bool isFrameRequired = config['frame'];
if (device != null) {
final isDeviceFrameRequired = device['frame'];
if (isDeviceFrameRequired != null) {
isFrameRequired = isDeviceFrameRequired;
}
// device frame over-rides global frame
isDeviceFrameRequired != null
? isFrameRequired = isDeviceFrameRequired
: null;
// orientation over-rides global and device frame setting
device['orientation'] != null ? isFrameRequired = false : null;
}
return isFrameRequired;
}
Expand All @@ -211,7 +218,7 @@ class ImageProcessor {

// set the default background color
String backgroundColor;
(deviceType == DeviceType.ios && runMode!=RunMode.archive)
(deviceType == DeviceType.ios && runMode != RunMode.archive)
? backgroundColor = kDefaultIosBackground
: backgroundColor = kDefaultAndroidBackground;

Expand Down
57 changes: 57 additions & 0 deletions lib/src/orientation.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'globals.dart';
import 'utils.dart' as utils;
import 'run.dart' as run;

enum Orientation { Portrait, LandscapeRight, PortraitUpsideDown, LandscapeLeft }

/// Change orientation of a running emulator or simulator.
/// (No known way of supporting real devices.)
void changeDeviceOrientation(DeviceType deviceType, Orientation orientation,
{String deviceId, String scriptDir}) {
final androidOrientations = {
'Portrait': '0',
'LandscapeRight': '1',
'PortraitUpsideDown': '2',
'LandscapeLeft': '3'
};
final iosOrientations = {
'Portrait': 'Portrait',
'LandscapeRight': 'Landscape Right',
'PortraitUpsideDown': 'Portrait Upside Down',
'LandscapeLeft': 'Landscape Left'
};
const sim_orientation_script = 'sim_orientation.scpt';
final _orientation = utils.getStringFromEnum(orientation);
print('Setting orientation to $_orientation');
switch (deviceType) {
case DeviceType.android:
run.cmd('adb', [
'-s',
deviceId,
'shell',
'settings',
'put',
'system',
'user_rotation',
androidOrientations[_orientation]
]);
break;
case DeviceType.ios:
// requires permission when run for first time
run.cmd(
'osascript',
['$scriptDir/$sim_orientation_script', iosOrientations[_orientation]],
'.',
true);
break;
}
}

Orientation getOrientationEnum(String orientation) {
final _orientation =
utils.getEnumFromString<Orientation>(Orientation.values, orientation);
_orientation == null
? throw 'Error: orientation \'$orientation\' not found'
: null;
return _orientation;
}
4 changes: 4 additions & 0 deletions lib/src/resources.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ Future<void> unpackScripts(String dstDir) async {
'resources/script/simulator-controller',
dstDir,
);
await unpackScript(
'resources/script/sim_orientation.scpt',
dstDir,
);
}

/// Read script from resources and install in staging area.
Expand Down
Loading

0 comments on commit 68758d1

Please sign in to comment.