Skip to content

Commit

Permalink
Merge pull request mmcc007#84 from mmcc007/#77_output_dir
Browse files Browse the repository at this point in the history
Added archive run mode to generate screenshots for local use only
  • Loading branch information
mmcc007 authored Jul 15, 2019
2 parents 2b971b5 + fb55114 commit 5346c65
Show file tree
Hide file tree
Showing 15 changed files with 214 additions and 88 deletions.
29 changes: 22 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,18 @@ $ screenshots -c <path to config file>
Other options:
```
$ screenshots -h
usage: screenshots [--help] [--config <config file>] [--mode <normal|recording|comparison>]
usage: screenshots [-h] [-c <config file>] [-m <normal|recording|comparison|archive>]
sample usage: screenshots
-c, --config=<screenshots.yaml> Path to config file.
(defaults to "screenshots.yaml")
-c, --config=<screenshots.yaml> Path to config file.
(defaults to "screenshots.yaml")
-h, --help Display this help information.
-m, --mode If mode is recording, screenshots will be saved for later comparison.
[normal (default), recording, comparison]
-m, --mode=<normal|recording|comparison|archive> If mode is recording, screenshots will be saved for later comparison.
If mode is archive, screenshots will be archived and cannot be uploaded via fastlane.
[normal (default), recording, comparison, archive]
-h, --help Display this help information.
```

# Modifying your tests for _Screenshots_
Expand Down Expand Up @@ -200,7 +202,20 @@ To use this feature:
screenshots -m comparison
```
_Screenshots_ will compare the new screenshots with the recorded screenshots and generate a 'diff' image for each screenshot that does not compare. The diff image highlights the differences in red.


# Archive Mode
To generate screenshots for local use, such as generating reports of changes to UI over time, etc... use 'archive' mode.

To enable this mode:
1. Add the location of your archive directory to screenshots.yaml:
```yaml
archive: /tmp/screenshots_archive
```
1. Run _Screenshots_ with:
````
$ screenshots -m archive
````

# Integration with Fastlane
Since _Screenshots_ is intended to be used with Fastlane, after _Screenshots_ completes, the images can be found in your project at:
````
Expand Down
18 changes: 13 additions & 5 deletions bin/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import 'dart:io';
import 'package:args/args.dart';
import 'package:screenshots/screenshots.dart';

const usage = 'usage: screenshots [--help] [--config <config file>]';
const usage =
'usage: screenshots [-h] [-c <config file>] [-m <normal|recording|comparison|archive>]';
const sampleUsage = 'sample usage: screenshots';

void main(List<String> arguments) async {
Expand All @@ -18,14 +19,15 @@ void main(List<String> arguments) async {
defaultsTo: 'screenshots.yaml',
help: 'Path to config file.',
valueHelp: 'screenshots.yaml')
..addFlag(helpArg,
abbr: 'h', help: 'Display this help information.', negatable: false)
..addOption(modeArg,
abbr: 'm',
defaultsTo: 'normal',
help:
'If mode is recording, screenshots will be saved for later comparison.',
allowed: ['normal', 'recording', 'comparison']);
'If mode is recording, screenshots will be saved for later comparison. \nIf mode is archive, screenshots will be archived and cannot be uploaded via fastlane.',
allowed: ['normal', 'recording', 'comparison', 'archive'],
valueHelp: 'normal|recording|comparison|archive')
..addFlag(helpArg,
abbr: 'h', help: 'Display this help information.', negatable: false);
try {
argResults = argParser.parse(arguments);
} on ArgParserException catch (e) {
Expand Down Expand Up @@ -77,6 +79,12 @@ void main(List<String> arguments) async {
exit(1);
}

// show help
if (argResults[helpArg]) {
_showUsage(argParser);
exit(0);
}

// validate args
final file = File(argResults[configArg]);
if (!await file.exists()) {
Expand Down
12 changes: 9 additions & 3 deletions example/screenshots.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ devices:
# Frame screenshots
frame: true

# Run mode can be one of 'normal' (default), 'recording' or 'comparison'
# If not 'normal' mode, a directory is required for recorded images
recording: /tmp/screenshots_record
# Run mode can be one of 'normal' (default), 'recording', 'comparison' or 'archive'.

# If run mode is 'recording' or 'comparison', a directory is required for recorded images.
recording: /tmp/screenshots_record

# If not intending to upload screenshots, images can be stored in an archive dir.
# This over-rides output to fastlane dirs.
# If run mode is 'archive', a directory is required for archived images.
archive: /tmp/screenshots_archive
24 changes: 24 additions & 0 deletions lib/src/archive.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'globals.dart';
import 'utils.dart' as utils;

/// Archive screenshots generated by a run.
class Archive {
static final _timeStamp = getTimestamp();

final _stagingTestDir;
final archiveDirPrefix;

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

String dstDir(DeviceType deviceType, String locale) =>
'$archiveDirPrefix/${utils.getStringFromEnum(deviceType)}/$locale';
}

/// Generates timestamp as [DateTime] in milliseconds
DateTime getTimestamp() {
final timestamp = DateTime.fromMillisecondsSinceEpoch(
DateTime.now().millisecondsSinceEpoch);
return timestamp;
}
14 changes: 7 additions & 7 deletions lib/src/capture_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import 'dart:io';

import 'globals.dart';

///
/// Called by integration test to capture images.
///
Future screenshot(final driver, Map config, String name,
{Duration timeout = const Duration(seconds: 30)}) async {
{Duration timeout = const Duration(seconds: 30),
bool silent = false}) async {
// todo: auto-naming scheme
await driver.waitUntilNoTransientCallbacks(timeout: timeout);
final List<int> pixels = await driver.screenshot();
final stagingDir = '${config['staging']}/test';
final File file = await File('$stagingDir/$name.$kImageExtension').create(recursive: true);
final pixels = await driver.screenshot();
final testDir = '${config['staging']}/$kTestScreenshotsDir';
final file =
await File('$testDir/$name.$kImageExtension').create(recursive: true);
await file.writeAsBytes(pixels);
print('Screenshot $name created');
if (!silent) print('Screenshot $name created');
}
13 changes: 6 additions & 7 deletions lib/src/fastlane.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Future _clearFastlaneDir(Screens screens, String deviceName, String locale,
// delete images ending with .kImageExtension
// for compatibility with FrameIt
// (see https://github.com/mmcc007/screenshots/issues/61)
clearFilesWithExt(dirPath, kImageExtension);
deleteMatchingFiles(dirPath, RegExp('$deviceName.*.$kImageExtension'));
if (runMode == RunMode.normal) {
im.deleteDiffs(dirPath);
}
Expand Down Expand Up @@ -70,17 +70,16 @@ String getAndroidModelType(Map screenProps) {
return androidDeviceType;
}

/// Clear files in a directory [dirPath] ending in [ext]
/// Create directory if none exists.
void clearFilesWithExt(String dirPath, String ext) {
// delete files with ext
/// Clears files matching a pattern in a directory.
/// Creates directory if none exists.
void deleteMatchingFiles(String dirPath, RegExp pattern) {
if (Directory(dirPath).existsSync()) {
Directory(dirPath).listSync().toList().forEach((e) {
if (p.extension(e.path) == ext) {
if (pattern.hasMatch(p.basename(e.path))) {
File(e.path).delete();
}
});
} else {
Directory(dirPath).createSync(recursive: true);
}
}
}
10 changes: 7 additions & 3 deletions lib/src/globals.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ const String kConfigFileName = 'screenshots.yaml';
/// screenshots environment file name
const String kEnvFileName = 'env.json';

/// Image extension
const kImageExtension = 'png';

/// Directory for capturing screenshots during a test
const kTestScreenshotsDir = 'test';

/// Distinguish device OS.
enum DeviceType { android, ios }

/// Run mode
enum RunMode { normal, recording, comparison }
enum RunMode { normal, recording, comparison, archive }

// singleton
ImageMagick get im => ImageMagick();

const kImageExtension = 'png';
20 changes: 12 additions & 8 deletions lib/src/image_processor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:io';

import 'package:meta/meta.dart';

import 'archive.dart';
import 'screens.dart';
import 'fastlane.dart' as fastlane;
import 'resources.dart' as resources;
Expand Down Expand Up @@ -34,7 +35,7 @@ class ImageProcessor {
///
/// After processing, screenshots are handed off for upload via fastlane.
Future<void> process(DeviceType deviceType, String deviceName, String locale,
RunMode runMode) async {
RunMode runMode, Archive archive) async {
final Map screenProps = _screens.screenProps(deviceName);
if (screenProps == null) {
print('Warning: \'$deviceName\' images will not be processed');
Expand All @@ -48,7 +49,7 @@ class ImageProcessor {
await resources.unpackImages(screenResources, staging);

// add status and nav bar and frame for each screenshot
final screenshots = Directory('$staging/test').listSync();
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');
Expand All @@ -63,18 +64,21 @@ class ImageProcessor {
// add frame if required
if (isFrameRequired(_config, deviceType, deviceName)) {
// print('placing $screenshotPath in frame');
await frame(_config, screenProps, screenshotPath.path, deviceType);
await frame(_config, screenProps, screenshotPath.path, deviceType, runMode);
}
}
}

// move to final destination for upload to stores via fastlane
final srcDir = '${_config['staging']}/test';
final srcDir = '${_config['staging']}/$kTestScreenshotsDir';
final androidModelType = fastlane.getAndroidModelType(screenProps);
String dstDir = fastlane.getDirPath(deviceType, locale, androidModelType);
runMode == RunMode.recording
? dstDir = '${_config['recording']}/$dstDir'
: null;
runMode == RunMode.archive
? dstDir = archive.dstDir(deviceType, locale)
: null;
// prefix screenshots with name of device before moving
// (useful for uploading to apple via fastlane)
await utils.prefixFilesInDir(srcDir, '$deviceName-');
Expand Down Expand Up @@ -110,8 +114,8 @@ class ImageProcessor {
Future<Map> compareImages(
String deviceName, String recordingDir, String comparisonDir) async {
Map failedCompare = {};
final recordedImages = await Directory(recordingDir).listSync();
await Directory(comparisonDir)
final recordedImages = Directory(recordingDir).listSync();
Directory(comparisonDir)
.listSync()
.where((screenshot) =>
p.basename(screenshot.path).contains(deviceName) &&
Expand Down Expand Up @@ -197,7 +201,7 @@ class ImageProcessor {
///
/// Resulting image is scaled to fit dimensions required by stores.
void frame(Map config, Map screen, String screenshotPath,
DeviceType deviceType) async {
DeviceType deviceType, RunMode runMode) async {
final Map resources = screen['resources'];

final framePath = config['staging'] + '/' + resources['frame'];
Expand All @@ -207,7 +211,7 @@ class ImageProcessor {

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

Expand Down
Loading

0 comments on commit 5346c65

Please sign in to comment.