自動化 AWS Device Farm - AWS Device Farm

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

自動化 AWS Device Farm

以程式設計方式存取 Device Farm 是自動化您需要完成之常見任務的強大方式,例如排程執行或下載執行、套件或測試的成品。 AWS 開發套件和 AWS CLI 提供執行此操作的方法。

AWS 開發套件可讓您存取每項 AWS 服務,包括 Device Farm、Amazon S3 等。如需詳細資訊,請參閱

範例:使用 AWS CLI 或 SDK 將應用程式或測試上傳至 Device Farm

下列範例示範如何使用 CLI 或各種語言的 AWS SDK,在 Device Farm AWS 上建立上傳。上傳是在 Device Farm 上排程測試執行的核心建置區塊,並包含下列項目:

使用 CreateUpload API 建立上傳。此 API 會傳回 S3 預先簽章的 URL,您可以使用 HTTP PUT 請求將上傳推送至 。URL 會在 24 小時後過期。

AWS CLI

注意:此範例使用命令列工具curl將應用程式推送至 Device Farm。

首先,如果您尚未建立專案,請先建立專案。

$ aws devicefarm create-project --name MyProjectName

這會顯示如下所示的輸出:

{ "project": { "name": "MyProjectName", "arn": "arn:aws:devicefarm:us-west-2:123456789101:project:5e01a8c7-c861-4c0a-b1d5-12345EXAMPLE", "created": 1535675814.414 } }

然後,執行下列動作來建立您的上傳並將其推送至 Device Farm。在此範例中,我們將使用本機 APK 檔案建立 Android 應用程式上傳。如需更多上傳類型資訊,包括 iOS 應用程式上傳類型的詳細資訊,請參閱我們用於建立 的 API 文件Upload

$ export APP_PATH="/local/path/to/my_sample_app.apk" $ export APP_TYPE="ANDROID_APP"

首先,我們會在 Device Farm 中建立上傳:

$ aws devicefarm create-upload \ --project-arn "arn:aws:devicefarm:us-west-2:123456789101:project:5e01a8c7-c861-4c0a-b1d5-12345EXAMPLE" \ --name "$(basename "$APP_PATH")" \ --type "$APP_TYPE"

這會顯示如下所示的輸出:

{ "upload": { "arn": "arn:aws:devicefarm:us-west-2:385076942068:upload:490a6350-0ba3-43e5-83f5-d2896b069a34/a120e848-c57b-4e8d-a720-d750a0c4d936", "name": "my_sample_app.apk", "created": 1760747318.266, "type": "ANDROID_APP", "status": "INITIALIZED", "url": "https://prod-us-west-2-uploads.s3.dualstack.us-west-2.amazonaws.com/arn%3Aaws%3Adevicefarm%3Aus-west-2...", "category": "PRIVATE" } }

然後,使用 curl 執行 PUT 呼叫,將應用程式推送至 Device Farm 的 S3 儲存貯體:

$ curl -T "$APP_PATH" "https://prod-us-west-2-uploads.s3.dualstack.us-west-2.amazonaws.com/arn%3Aaws%3Adevicefarm%3Aus-west-2..."

最後,等待應用程式處於「成功」狀態:

$ aws devicefarm get-upload --arn "arn:aws:devicefarm:us-west-2:385076942068:upload:490a6350-0ba3-43e5-83f5-d2896b069a34/a120e848-c57b-4e8d-a720-d750a0c4d936"

這會顯示如下所示的輸出:

{ "upload": { "arn": "arn:aws:devicefarm:us-west-2:385076942068:upload:490a6350-0ba3-43e5-83f5-d2896b069a34/a120e848-c57b-4e8d-a720-d750a0c4d936", "name": "my_sample_app.apk", "created": 1760747318.266, "type": "ANDROID_APP", "status": "SUCCEEDED", "url": "https://prod-us-west-2-uploads.s3.dualstack.us-west-2.amazonaws.com/arn%3Aaws%3Adevicefarm%3Aus-west-2...", "metadata": "{\"activity_name\":\"com.amazonaws.devicefarm.android.referenceapp.Activities.MainActivity\",\"package_name\":\"com.amazonaws.devicefarm.android.referenceapp\",...}", "category": "PRIVATE" } }
Python

注意:此範例使用第三方requests套件將應用程式推送至 Device Farm,以及適用於 Python 的 AWS SDKboto3

首先,如果您尚未建立專案,請先建立專案。

import boto3 client = boto3.client("devicefarm", region_name="us-west-2") resp = client.create_project(name="MyProjectName") print(resp) # Response will be something like: # { # "project": { # "name": "MyProjectName", # "arn": "arn:aws:devicefarm:us-west-2:123456789101:project:5e01a8c7-c861-4c0a-b1d5-12345EXAMPLE", # "created": 1535675814.414 # } # }

然後,執行下列動作來建立您的上傳並將其推送至 Device Farm。在此範例中,我們將使用本機 APK 檔案建立 Android 應用程式上傳。如需上傳類型的詳細資訊,包括 iOS 應用程式上傳類型的詳細資訊,請參閱我們用於建立 的 API 文件Upload

import os import time import datetime import requests from pathlib import Path import boto3 def upload_device_farm_file(): project_arn = "arn:aws:devicefarm:us-west-2:123456789101:project:5e01a8c7-c861-4c0a-b1d5-12345EXAMPLE" app_path = Path("/local/path/to/my_sample_app.apk") file_type = "ANDROID_APP" if not app_path.is_file(): raise RuntimeError(f"{app_path} is not a valid app file path") client = boto3.client("devicefarm", region_name="us-west-2") # 1) Create the upload in Device Farm create = client.create_upload( projectArn=project_arn, name=app_path.name, type=file_type, contentType="application/octet-stream", ) upload = create["upload"] upload_arn = upload["arn"] upload_url = upload["url"] # This will show output such as the following: # { "upload": { "arn": "...", "name": "my_sample_app.apk", "type": "ANDROID_APP", "status": "INITIALIZED", "url": "https://..." } } # 2) Do an HTTP PUT command to push the file to the pre-signed S3 URL with app_path.open("rb") as fh: print(f"Uploading {app_path.name} to Device Farm...") put_resp = requests.put(upload_url, data=fh, headers={"Content-Type": "application/octet-stream"}) put_resp.raise_for_status() # 3) Wait for the app to be in "SUCCEEDED" status (or fail/timeout) timeout_seconds = 30 start = time.time() while True: get_resp = client.get_upload(arn=upload_arn) status = get_resp["upload"]["status"] msg = get_resp["upload"].get("message") or get_resp["upload"].get("metadata") or "" elapsed = datetime.timedelta(seconds=int(time.time() - start)) print(f"[{elapsed}] status={status}{' - ' + msg if msg else ''}") if status == "SUCCEEDED": print(f"Upload complete: {upload_arn}") return upload_arn if status == "FAILED": raise RuntimeError(f"Device Farm failed to process upload: {msg}") if (time.time() - start) > timeout_seconds: raise RuntimeError(f"Timed out after {timeout_seconds}s waiting for upload to process (last status={status}).") time.sleep(1) upload_device_farm_file()
Java

注意:此範例使用適用於 Java v2 的 AWS 開發套件HttpClient,並將應用程式推送至 Device Farm,並與 JDK 第 11 版及更新版本相容。

首先,如果您尚未建立專案,請先建立專案。

import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.devicefarm.DeviceFarmClient; import software.amazon.awssdk.services.devicefarm.model.CreateProjectRequest; import software.amazon.awssdk.services.devicefarm.model.CreateProjectResponse; try (DeviceFarmClient client = DeviceFarmClient.builder() .region(Region.US_WEST_2) .build()) { CreateProjectResponse resp = client.createProject( CreateProjectRequest.builder().name("MyProjectName").build()); System.out.println(resp.project()); // Response will be something like: // Project{name=MyProjectName, arn=arn:aws:devicefarm:us-west-2:123456789101:project:5e01a8c7-..., created=...} }

然後,執行下列動作來建立您的上傳並將其推送至 Device Farm。在此範例中,我們將使用本機 APK 檔案建立 Android 應用程式上傳。如需更多上傳類型資訊,包括 iOS 應用程式上傳類型的詳細資訊,請參閱我們用於建立 的 API 文件Upload

import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; import java.time.Instant; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.devicefarm.DeviceFarmClient; import software.amazon.awssdk.services.devicefarm.model.CreateUploadRequest; import software.amazon.awssdk.services.devicefarm.model.CreateUploadResponse; import software.amazon.awssdk.services.devicefarm.model.GetUploadRequest; import software.amazon.awssdk.services.devicefarm.model.GetUploadResponse; import software.amazon.awssdk.services.devicefarm.model.Upload; import software.amazon.awssdk.services.devicefarm.model.UploadType; public class DeviceFarmUploader { public static String upload(String projectArn, Path appPath) throws Exception { if (projectArn == null || projectArn.isEmpty()) { throw new IllegalArgumentException("Missing projectArn"); } if (!Files.isRegularFile(appPath)) { throw new IllegalArgumentException("Invalid app path: " + appPath); } String fileName = appPath.getFileName().toString().trim(); UploadType type = UploadType.ANDROID_APP; // Build a reusable HttpClient HttpClient http = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_1_1) .connectTimeout(Duration.ofSeconds(10)) .build(); try (DeviceFarmClient client = DeviceFarmClient.builder() .region(Region.US_WEST_2) .build()) { // 1) Create the upload in Device Farm CreateUploadResponse create = client.createUpload(CreateUploadRequest.builder() .projectArn(projectArn) .name(fileName) .type(type) .contentType("application/octet-stream") .build()); Upload upload = create.upload(); String uploadArn = upload.arn(); String url = upload.url(); // This will show output such as the following: // { "upload": { "arn": "...", "name": "my_sample_app.apk", "type": "ANDROID_APP", "status": "INITIALIZED", "url": "https://..." } } // 2) PUT file to pre-signed URL using HttpClient HttpRequest put = HttpRequest.newBuilder(URI.create(url)) .timeout(Duration.ofMinutes(15)) .header("Content-Type", "application/octet-stream") .PUT(HttpRequest.BodyPublishers.ofFile(appPath)) .build(); HttpResponse<Void> resp = http.send(put, HttpResponse.BodyHandlers.discarding()); int code = resp.statusCode(); if (code / 100 != 2) { throw new IOException("Failed PUT to S3 pre-signed URL, HTTP " + code); } // 3) Wait for the app to be in "SUCCEEDED" status (or fail/timeout) Instant deadline = Instant.now().plusSeconds(30); // 30-second timeout while (true) { GetUploadResponse got = client.getUpload(GetUploadRequest.builder() .arn(uploadArn) .build()); String status = got.upload().statusAsString(); String msg = got.upload().metadata(); System.out.println("status=" + status + (msg != null ? " - " + msg : "")); if ("SUCCEEDED".equals(status)) return uploadArn; if ("FAILED".equals(status)) throw new RuntimeException("Upload failed: " + msg); if (Instant.now().isAfter(deadline)) { throw new RuntimeException("Timeout waiting for processing, last status=" + status); } Thread.sleep(2000); } } } public static void main(String[] args) throws Exception { String projectArn = "arn:aws:devicefarm:us-west-2:123456789101:project:5e01a8c7-c861-4c0a-b1d5-12345EXAMPLE"; Path appPath = Paths.get("/local/path/to/my_sample_app.apk"); String result = upload(projectArn, appPath); System.out.println("Upload ARN: " + result); } }
JavaScript

注意:此範例使用適用於 JavaScript (v3) 和 Node 18+ 的 AWS SDK 將應用程式fetch推送至 Device Farm。

首先,如果您尚未建立專案,請先建立專案。

import { DeviceFarmClient, CreateProjectCommand } from "@aws-sdk/client-device-farm"; const df = new DeviceFarmClient({ region: "us-west-2" }); const resp = await df.send(new CreateProjectCommand({ name: "MyProjectName" })); console.log(resp); // Response will be something like: // { project: { name: 'MyProjectName', arn: 'arn:aws:devicefarm:us-west-2:123456789101:project:5e01a8c7-...', created: 1535675814.414 } }

然後,執行下列動作來建立您的上傳並將其推送至 Device Farm。在此範例中,我們將使用本機 APK 檔案建立 Android 應用程式上傳。如需上傳類型的詳細資訊,包括 iOS 應用程式上傳類型的詳細資訊,請參閱我們用於建立 的 API 文件Upload

import { DeviceFarmClient, CreateUploadCommand, GetUploadCommand } from "@aws-sdk/client-device-farm"; import { createReadStream } from "fs"; import { basename } from "path"; const projectArn = "arn:aws:devicefarm:us-west-2:123456789101:project:5e01a8c7-c861-4c0a-b1d5-12345EXAMPLE"; const appPath = "/local/path/to/my_sample_app.apk"; const name = basename(appPath).trim(); const type = "ANDROID_APP"; const client = new DeviceFarmClient({ region: "us-west-2" }); // 1) Create the upload in Device Farm const create = await client.send(new CreateUploadCommand({ projectArn, name, type, contentType: "application/octet-stream", })); const uploadArn = create.upload.arn; const url = create.upload.url; // This will show output such as the following: // { upload: { arn: '...', name: 'my_sample_app.apk', type: 'ANDROID_APP', status: 'INITIALIZED', url: 'https://...' } } // 2) PUT to pre-signed URL const putResp = await fetch(url, { method: "PUT", headers: { "Content-Type": "application/octet-stream" }, body: createReadStream(appPath), }); if (!putResp.ok) { throw new Error(`Failed PUT to pre-signed URL: ${putResp.status} ${await putResp.text().catch(()=>"")}`); } // 3) Wait for the app to be in "SUCCEEDED" status (or fail/timeout) const deadline = Date.now() + (30 * 1000); // 30-second timeout while (true) { const response = await client.send(new GetUploadCommand({ arn: uploadArn })); const { status, message, metadata } = response.upload; console.log(`status=${status}${message ? " - " + message : metadata ? " - " + metadata : ""}`); if (status === "SUCCEEDED") { console.log("Upload complete:", uploadArn); break; } if (status === "FAILED") { throw new Error(`Upload failed: ${message || metadata || "unknown"}`); } if (Date.now() > deadline) throw new Error(`Timeout waiting for processing (last status=${status})`); await new Promise(r => setTimeout(r, 2000)); }
C#

注意:此範例使用適用於 .NET 的 AWS 開發套件HttpClient,並將應用程式推送至 Device Farm。

首先,如果您尚未建立專案,請先建立專案。

using System; using Amazon; using Amazon.DeviceFarm; using Amazon.DeviceFarm.Model; using var client = new AmazonDeviceFarmClient(RegionEndpoint.USWest2); var resp = await client.CreateProjectAsync(new CreateProjectRequest { Name = "MyProjectName" }); Console.WriteLine(resp.Project); // Response will be something like: // { Name = MyProjectName, Arn = arn:aws:devicefarm:us-west-2:123456789101:project:5e01a8c7-..., Created = ... }

然後,執行下列動作來建立您的上傳並將其推送至 Device Farm。在此範例中,我們將使用本機 APK 檔案建立 Android 應用程式上傳。如需更多上傳類型資訊,包括 iOS 應用程式上傳類型的詳細資訊,請參閱我們用於建立 的 API 文件Upload

using System; using System.IO; using System.Net.Http; using System.Threading.Tasks; using System.Net.Http.Headers; using Amazon; using Amazon.DeviceFarm; using Amazon.DeviceFarm.Model; class DeviceFarmUploader { public static async Task<string> UploadAsync(string projectArn, string appPath) { if (string.IsNullOrWhiteSpace(projectArn)) throw new ArgumentException("Missing projectArn"); if (!File.Exists(appPath)) throw new ArgumentException($"Invalid app path: {appPath}"); var type = UploadType.ANDROID_APP; using var client = new AmazonDeviceFarmClient(RegionEndpoint.USWest2); // 1) Create the upload in Device Farm var create = await client.CreateUploadAsync(new CreateUploadRequest { ProjectArn = projectArn, Name = Path.GetFileName(appPath), Type = type, ContentType = "application/octet-stream" }); var uploadArn = create.Upload.Arn; var url = create.Upload.Url; // This will show output such as the following: // { Upload: { Arn = ..., Name = my_sample_app.apk, Type = ANDROID_APP, Status = INITIALIZED, Url = https://... } } // 2) PUT file to pre-signed URL using (var http = new HttpClient()) using (var fs = File.OpenRead(appPath)) using (var content = new StreamContent(fs)) { content.Headers.Add("Content-Type", "application/octet-stream"); var resp = await http.PutAsync(url, content); if (!resp.IsSuccessStatusCode) throw new Exception($"Failed PUT to pre-signed URL: {(int)resp.StatusCode} {await resp.Content.ReadAsStringAsync()}"); } // 3) Wait for the app to be in "SUCCEEDED" status (or fail/timeout) var deadline = DateTime.UtcNow.AddSeconds(30); // 30-second timeout while (true) { var got = await client.GetUploadAsync(new GetUploadRequest { Arn = uploadArn }); var status = got.Upload.Status.Value; var msg = got.Upload.Message ?? got.Upload.Metadata; Console.WriteLine($"status={status}{(string.IsNullOrEmpty(msg) ? "" : " - " + msg)}"); if (status == UploadStatus.SUCCEEDED.Value) return uploadArn; if (status == UploadStatus.FAILED.Value) throw new Exception($"Upload failed: {msg}"); if (DateTime.UtcNow > deadline) throw new TimeoutException($"Timeout waiting for processing (last status={status})"); await Task.Delay(2000); } } static async Task Main() { var projectArn = "arn:aws:devicefarm:us-west-2:123456789101:project:5e01a8c7-c861-4c0a-b1d5-12345EXAMPLE"; var appPath = "/local/path/to/my_sample_app.apk"; var result = await UploadAsync(projectArn!, appPath!); Console.WriteLine("Upload ARN: " + result); } }
Ruby

注意:此範例使用適用於 Ruby 的 AWS 開發套件Net::HTTP,並將應用程式推送至 Device Farm。

首先,如果您尚未建立專案,請先建立專案。

require "aws-sdk-devicefarm" client = Aws::DeviceFarm::Client.new(region: "us-west-2") resp = client.create_project(name: "MyProjectName") puts resp.project.inspect # Response will be something like: # #<struct Aws::DeviceFarm::Types::Project name="MyProjectName", arn="arn:aws:devicefarm:us-west-2:123456789101:project:5e01a8c7-...", created=1535675814.414>

然後,執行下列動作來建立您的上傳並將其推送至 Device Farm。在此範例中,我們將使用本機 APK 檔案建立 Android 應用程式上傳。如需更多上傳類型資訊,包括 iOS 應用程式上傳類型的詳細資訊,請參閱我們用於建立 的 API 文件Upload

require "aws-sdk-devicefarm" require "net/http" require "uri" project_arn = "arn:aws:devicefarm:us-west-2:123456789101:project:5e01a8c7-c861-4c0a-b1d5-12345EXAMPLE" app_path = "/local/path/to/my_sample_app.apk" raise "Invalid APP_PATH: #{app_path}" unless File.file?(app_path) type = "ANDROID_APP" client = Aws::DeviceFarm::Client.new(region: "us-west-2") # 1) Create the upload in Device Farm create = client.create_upload( project_arn: project_arn, name: File.basename(app_path), type: type, content_type: "application/octet-stream" ) upload_arn = create.upload.arn url = create.upload.url # This will show output such as the following: # #<Upload arn="...", name="my_sample_app.apk", type="ANDROID_APP", status="INITIALIZED", url="https://..."> # 2) PUT the file to the pre-signed URL uri = URI.parse(url) Net::HTTP.start(uri.host, uri.port, use_ssl: (uri.scheme == "https")) do |http| req = Net::HTTP::Put.new(uri) req["Content-Type"] = "application/octet-stream" req.body_stream = File.open(app_path, "rb") req.content_length = File.size(app_path) resp = http.request(req) raise "Failed PUT: #{resp.code} #{resp.body}" unless resp.code.to_i / 100 == 2 end # 3) Wait for the app to be in "SUCCEEDED" status (or fail/timeout) deadline = Time.now + 30 # 30-second timeout loop do got = client.get_upload(arn: upload_arn) status = got.upload.status msg = got.upload.message || got.upload.metadata puts "status=#{status}#{msg ? " - #{msg}" : ""}" case status when "SUCCEEDED" then puts "Upload complete: #{upload_arn}"; break when "FAILED" then raise "Upload failed: #{msg}" end raise "Timeout waiting for processing (last status=#{status})" if Time.now > deadline sleep 2 end

範例:使用 AWS SDK 啟動 Device Farm 執行並收集成品

下列範例提供如何使用 AWS SDK 搭配 Device Farm beginning-to-end示範。此範例會執行下列操作:

  • 將測試和應用程式套件上傳至 Device Farm

  • 開始測試執行並等待其完成 (或失敗)

  • 下載測試套件產生的所有成品

此範例取決於與 HTTP 互動的第三方 requests 套件。

import boto3 import os import requests import string import random import time import datetime import time import json # The following script runs a test through Device Farm # # Things you have to change: config = { # This is our app under test. "appFilePath":"app-debug.apk", "projectArn": "arn:aws:devicefarm:us-west-2:111122223333:project:1b99bcff-1111-2222-ab2f-8c3c733c55ed", # Since we care about the most popular devices, we'll use a curated pool. "testSpecArn":"arn:aws:devicefarm:us-west-2::upload:101e31e8-12ac-11e9-ab14-d663bd873e83", "poolArn":"arn:aws:devicefarm:us-west-2::devicepool:082d10e5-d7d7-48a5-ba5c-b33d66efa1f5", "namePrefix":"MyAppTest", # This is our test package. This tutorial won't go into how to make these. "testPackage":"tests.zip" } client = boto3.client('devicefarm') unique = config['namePrefix']+"-"+(datetime.date.today().isoformat())+(''.join(random.sample(string.ascii_letters,8))) print(f"The unique identifier for this run is going to be {unique} -- all uploads will be prefixed with this.") def upload_df_file(filename, type_, mime='application/octet-stream'): response = client.create_upload(projectArn=config['projectArn'], name = (unique)+"_"+os.path.basename(filename), type=type_, contentType=mime ) # Get the upload ARN, which we'll return later. upload_arn = response['upload']['arn'] # We're going to extract the URL of the upload and use Requests to upload it upload_url = response['upload']['url'] with open(filename, 'rb') as file_stream: print(f"Uploading {filename} to Device Farm as {response['upload']['name']}... ",end='') put_req = requests.put(upload_url, data=file_stream, headers={"content-type":mime}) print(' done') if not put_req.ok: raise Exception("Couldn't upload, requests said we're not ok. Requests says: "+put_req.reason) started = datetime.datetime.now() while True: print(f"Upload of {filename} in state {response['upload']['status']} after "+str(datetime.datetime.now() - started)) if response['upload']['status'] == 'FAILED': raise Exception("The upload failed processing. DeviceFarm says reason is: \n"+(response['upload']['message'] if 'message' in response['upload'] else response['upload']['metadata'])) if response['upload']['status'] == 'SUCCEEDED': break time.sleep(5) response = client.get_upload(arn=upload_arn) print("") return upload_arn our_upload_arn = upload_df_file(config['appFilePath'], "ANDROID_APP") our_test_package_arn = upload_df_file(config['testPackage'], 'APPIUM_PYTHON_TEST_PACKAGE') print(our_upload_arn, our_test_package_arn) # Now that we have those out of the way, we can start the test run... response = client.schedule_run( projectArn = config["projectArn"], appArn = our_upload_arn, devicePoolArn = config["poolArn"], name=unique, test = { "type":"APPIUM_PYTHON", "testSpecArn": config["testSpecArn"], "testPackageArn": our_test_package_arn } ) run_arn = response['run']['arn'] start_time = datetime.datetime.now() print(f"Run {unique} is scheduled as arn {run_arn} ") try: while True: response = client.get_run(arn=run_arn) state = response['run']['status'] if state == 'COMPLETED' or state == 'ERRORED': break else: print(f" Run {unique} in state {state}, total time "+str(datetime.datetime.now()-start_time)) time.sleep(10) except: # If something goes wrong in this process, we stop the run and exit. client.stop_run(arn=run_arn) exit(1) print(f"Tests finished in state {state} after "+str(datetime.datetime.now() - start_time)) # now, we pull all the logs. jobs_response = client.list_jobs(arn=run_arn) # Save the output somewhere. We're using the unique value, but you could use something else save_path = os.path.join(os.getcwd(), unique) os.mkdir(save_path) # Save the last run information for job in jobs_response['jobs'] : # Make a directory for our information job_name = job['name'] os.makedirs(os.path.join(save_path, job_name), exist_ok=True) # Get each suite within the job suites = client.list_suites(arn=job['arn'])['suites'] for suite in suites: for test in client.list_tests(arn=suite['arn'])['tests']: # Get the artifacts for artifact_type in ['FILE','SCREENSHOT','LOG']: artifacts = client.list_artifacts( type=artifact_type, arn = test['arn'] )['artifacts'] for artifact in artifacts: # We replace : because it has a special meaning in Windows & macos path_to = os.path.join(save_path, job_name, suite['name'], test['name'].replace(':','_') ) os.makedirs(path_to, exist_ok=True) filename = artifact['type']+"_"+artifact['name']+"."+artifact['extension'] artifact_save_path = os.path.join(path_to, filename) print("Downloading "+artifact_save_path) with open(artifact_save_path, 'wb') as fn, requests.get(artifact['url'],allow_redirects=True) as request: fn.write(request.content) #/for artifact in artifacts #/for artifact type in [] #/ for test in ()[] #/ for suite in suites #/ for job in _[] # done print("Finished")