diff --git a/lib/sylph.dart b/lib/sylph.dart index 6f4d73c3..19d5112e 100644 --- a/lib/sylph.dart +++ b/lib/sylph.dart @@ -8,9 +8,9 @@ import 'package:yaml/yaml.dart'; enum DeviceType { ios, android } -const resourcesUri = 'package:sylph/resources'; -const appiumTemplate = 'appium_template.zip'; -const testBundle = 'test_bundle.zip'; +const kResourcesUri = 'package:sylph/resources'; +const kAppiumTemplate = 'appium_template.zip'; +const kTestBundle = 'test_bundle.zip'; /// parse a yaml file to a map Future parseYaml(String filePath) async { @@ -23,24 +23,20 @@ Future parseYaml(String filePath) async { /// Creates new project if none exists. String setupProject(String projectName, int jobTimeoutMinutes) { // check for existing project - final List projectList = - jsonDecode(cmd('aws', ['devicefarm', 'list-projects']))['projects']; - Map result = projectList.firstWhere( - (project) => project['name'] == projectName, + final projects = deviceFarmCmd(['list-projects'])['projects']; + Map result = projects.firstWhere((project) => project['name'] == projectName, orElse: () => null); if (result == null) { // create project print('Creating project for $projectName ...'); - result = jsonDecode(cmd('aws', [ - 'devicefarm', + return deviceFarmCmd([ 'create-project', '--name', projectName, '--default-job-timeout-minutes', '$jobTimeoutMinutes' - ])); - return result['project']['arn']; + ])['project']['arn']; } else return result['arn']; } @@ -48,25 +44,23 @@ String setupProject(String projectName, int jobTimeoutMinutes) { /// Set up a device pool if named pool does not exist. String setupDevicePool(String projectArn, String poolName, List devices) { // check for existing pool - final List poolList = jsonDecode(cmd('aws', [ - 'devicefarm', + final pools = deviceFarmCmd([ 'list-device-pools', '--arn', projectArn, '--type', 'PRIVATE' - ]))['devicePools']; - Map result = poolList.firstWhere((pool) => pool['name'] == poolName, - orElse: () => null); + ])['devicePools']; + final pool = + pools.firstWhere((pool) => pool['name'] == poolName, orElse: () => null); - if (result == null) { - // create device pool + if (pool == null) { + // create new device pool print('Creating device pool $poolName ...'); // convert devices to rules List rules = deviceSpecToRules(devices); - result = jsonDecode(cmd('aws', [ - 'devicefarm', + final newPool = deviceFarmCmd([ 'create-device-pool', '--name', poolName, @@ -76,10 +70,10 @@ String setupDevicePool(String projectArn, String poolName, List devices) { jsonEncode(rules), // number of devices in pool should not exceed number of devices requested // '--max-devices', '${devices.length}' - ])); - return result['devicePool']['arn']; + ])['devicePool']; + return newPool['arn']; } else - return result['arn']; + return pool['arn']; } /// Schedules a run. @@ -87,8 +81,7 @@ String scheduleRun(String runName, String projectArn, String appArn, String devicePoolArn, String testSpecArn, String testPackageArn) { // Schedule run print('Starting $runName on AWS Device Farms'); - String runArn = jsonDecode(cmd('aws', [ - 'devicefarm', + return deviceFarmCmd([ 'schedule-run', '--project-arn', projectArn, @@ -102,37 +95,34 @@ String scheduleRun(String runName, String projectArn, String appArn, 'testSpecArn=$testSpecArn,type=APPIUM_PYTHON,testPackageArn=$testPackageArn', // '--execution-configuration', // 'jobTimeoutMinutes=5,accountsCleanup=false,appPackagesCleanup=false,videoCapture=true,skipAppResign=true' - ]))['run']['arn']; - return runArn; + ])['run']['arn']; } -/// Tracks run status. +/// Tracks run status and returns final run. Map runStatus(String runArn, int timeout) { - Map result; + Map run; for (int i = 0; i < timeout; i++) { - result = jsonDecode(cmd('aws', [ - 'devicefarm', + final run = deviceFarmCmd([ 'get-run', '--arn', runArn, - ])); - sleep(Duration(seconds: 2)); - final status = result['run']['status']; + ])['run']; + final runStatus = run['status']; // print run status - print('Run status: $status'); + print('Run status: $runStatus'); - if (status == 'COMPLETED') + if (runStatus == 'COMPLETED') break; else if (i == timeout - 2) throw 'Error: run timed-out'; + sleep(Duration(seconds: 2)); } - return result; + return run; } /// Run report. -void runReport(Map result) { +void runReport(Map run) { // generate report - final run = result['run']; print('run=$run'); print( 'Run \'${run['name']}\' completed in ${run['deviceMinutes']['total']} minutes.'); @@ -149,17 +139,17 @@ void runReport(Map result) { /// Finds the ARN of a device. String findDeviceArn(String name, String model, String os) { assert(name != null && model != null && os != null); - final List deviceList = jsonDecode(cmd('aws', [ + final devices = deviceFarmCmd([ 'devicefarm', 'list-devices', - ]))['devices']; - Map result = deviceList.firstWhere( + ])['devices']; + Map device = devices.firstWhere( (device) => (device['name'] == name && device['modelId'] == model && device['os'] == os), orElse: () => throw 'Error: device does not exist: name=$name, model=$model, os=$os'); - return result['arn']; + return device['arn']; } /// Converts a list of devices to a list of rules. @@ -183,8 +173,7 @@ List deviceSpecToRules(List devices) { /// Returns file ARN. String uploadFile(String projectArn, String filePath, String fileType) { // 1. Create upload - String result = cmd('aws', [ - 'devicefarm', + final uploadRequested = deviceFarmCmd([ 'create-upload', '--project-arn', projectArn, @@ -193,19 +182,18 @@ String uploadFile(String projectArn, String filePath, String fileType) { '--type', fileType ]); - Map resultMap = jsonDecode(result); - final fileUploadUrl = resultMap['upload']['url']; - final fileUploadArn = resultMap['upload']['arn']; + final fileUploadUrl = uploadRequested['upload']['url']; + final fileUploadArn = uploadRequested['upload']['arn']; // 2. Upload file - result = cmd('curl', ['-T', filePath, fileUploadUrl]); + cmd('curl', ['-T', filePath, fileUploadUrl]); // 3. Wait until file upload complete for (int i = 0; i < 5; i++) { - result = cmd('aws', ['devicefarm', 'get-upload', '--arn', fileUploadArn]); + final upload = + deviceFarmCmd(['get-upload', '--arn', fileUploadArn])['upload']; sleep(Duration(seconds: 1)); - resultMap = jsonDecode(result); - final status = resultMap['upload']['status']; + final status = upload['status']; if (status == 'SUCCEEDED') break; else if (i == 4) @@ -219,7 +207,7 @@ String uploadFile(String projectArn, String filePath, String fileType) { Future bundleFlutterTests(Map config) async { final tmpDir = config['tmp_dir']; clearDirectory(tmpDir); - final testBundlePath = '$tmpDir/$testBundle'; + final testBundlePath = '$tmpDir/$kTestBundle'; await unpackResources(tmpDir); // final testSuite = config['test_suites'][0]; @@ -248,18 +236,13 @@ Future bundleFlutterTests(Map config) async { /// Downloads artifacts generated by a run. void downloadArtifacts(String runArn, String downloadDir) { clearDirectory(downloadDir); - var raw = cmd('aws', - ['devicefarm', 'list-artifacts', '--arn', runArn, '--type', 'FILE']); -// print('raw=$raw'); - var artifacts = jsonDecode(raw)['artifacts']; + var artifacts = deviceFarmCmd( + ['list-artifacts', '--arn', runArn, '--type', 'FILE'])['artifacts']; for (var artifact in artifacts) { - // print('artifact=$artifact'); final name = artifact['name']; final extension = artifact['extension']; -// final type = artifact['type']; final fileUrl = artifact['url']; - final fileName = name.replaceAll(' ', '_') + '.' + Uuid().v1() + '.' + extension; final filePath = downloadDir + '/' + fileName; diff --git a/lib/utils.dart b/lib/utils.dart index 322bebc3..480f8d2a 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:sylph/sylph.dart'; import 'dart:io'; @@ -16,7 +17,7 @@ void clearDirectory(String dir) { /// Read a file image from resources. Future> readResourceImage(String fileImageName) async { - final resource = Resource('$resourcesUri/$fileImageName'); + final resource = Resource('$kResourcesUri/$fileImageName'); return resource.readAsBytes(); } @@ -41,6 +42,13 @@ String cmd(String cmd, List arguments, return result.stdout; } +/// Runs a device farm command and returns as Map +Map deviceFarmCmd(List arguments, + [String workingDir = '.', bool silent = true]) { + return jsonDecode( + cmd('aws', ['devicefarm']..addAll(arguments), workingDir, silent)); +} + /// Converts [DeviceType] to [String] String deviceTypeStr(DeviceType deviceType) { return DeviceType.ios.toString().split('.')[1]; @@ -57,10 +65,11 @@ Map getDevicePoolInfo(Map config, String poolName) { } Future unpackResources(String tmpDir) async { - final testBundlePath = '$tmpDir/$testBundle'; + final testBundlePath = '$tmpDir/$kTestBundle'; // unpack Appium template - await writeFileImage(await readResourceImage(appiumTemplate), testBundlePath); + await writeFileImage( + await readResourceImage(kAppiumTemplate), testBundlePath); // unpack scripts final appPath = Directory.current.path; @@ -83,7 +92,7 @@ Future unpackScripts(String dstDir) async { /// Read script from resources and install in staging area. Future unpackScript(String srcPath, String dstDir) async { - final resource = Resource('$resourcesUri/$srcPath'); + final resource = Resource('$kResourcesUri/$srcPath'); final String script = await resource.readAsString(); final file = await File('$dstDir/$srcPath').create(recursive: true); await file.writeAsString(script, flush: true); diff --git a/test/sylph_test.dart b/test/sylph_test.dart index be184211..1c6d3246 100644 --- a/test/sylph_test.dart +++ b/test/sylph_test.dart @@ -189,4 +189,15 @@ void main() { // list artifacts downloadArtifacts(runArn, downloadDir); }); + + test('run device farm command', () { + final projectName = 'flutter tests'; + var projectInfo = deviceFarmCmd(['list-projects']); + final projects = projectInfo['projects']; + final project = projects.firstWhere( + (project) => project['name'] == projectName, + orElse: () => null); + print(project); + expect(project['name'], projectName); + }); }