fastlane-plugin-developer_device_platform 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +8 -22
- data/lib/fastlane/plugin/developer_device_platform/actions/developer_device_platform_action.rb +27 -6
- data/lib/fastlane/plugin/developer_device_platform/helper/device_run_client.rb +23 -0
- data/lib/fastlane/plugin/developer_device_platform/helper/gcloud.rb +1 -1
- data/lib/fastlane/plugin/developer_device_platform/helper/options.rb +7 -1
- data/lib/fastlane/plugin/developer_device_platform/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7d7beb8da7cf98d4f592a20ab3b957b2e5a059a6959f52b560beab245ae251a0
|
|
4
|
+
data.tar.gz: b526995df11c60771ff7653ff3a947c910ece216bce370a855ef3d67bc500ca5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7a7abb638d150564242a219cdaed48b953a3557e9521b1c58cc0a869a32d67366605f03f9fe20751473dc30337c3cbaec2c6808987996a625344758f56f042d7
|
|
7
|
+
data.tar.gz: 970889f67676b92e02a4103e370d95d3367b58f41b3698fe78c09d453e71b45f4d4fe7f23777690e46132f96329f6f9ff429a182b1fb8b6700b6d109e39051f2
|
data/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
Run Android instrumentation tests on [Google Cloud Developer Device Platform](https://docs.cloud.google.com/developer-device-platform) (Device Run) from fastlane.
|
|
7
7
|
|
|
8
|
-
Firebase Test Lab is [deprecated and shuts down on September 30, 2027](https://firebase.google.com/docs/test-lab/migrate-faq). Developer Device Platform is its successor on Google Cloud. This plugin
|
|
8
|
+
Firebase Test Lab is [deprecated and shuts down on September 30, 2027](https://firebase.google.com/docs/test-lab/migrate-faq). Developer Device Platform is its successor on Google Cloud. This plugin wraps `gcloud beta device-run` so Android instrumentation tests can keep running from a fastlane lane.
|
|
9
9
|
|
|
10
10
|
## What the action does
|
|
11
11
|
|
|
@@ -17,7 +17,7 @@ Firebase Test Lab is [deprecated and shuts down on September 30, 2027](https://f
|
|
|
17
17
|
|
|
18
18
|
## Prerequisites
|
|
19
19
|
|
|
20
|
-
- [Google Cloud CLI](https://cloud.google.com/sdk/docs/install) on the machine running fastlane (`gcloud components update`
|
|
20
|
+
- [Google Cloud CLI](https://cloud.google.com/sdk/docs/install) 580.0.0 or newer with the `beta` component on the machine running fastlane (`gcloud components update && gcloud components install beta`). On GitHub Actions, pin `version` in `google-github-actions/setup-gcloud`; its default `latest` can resolve to an older release that predates Device Run.
|
|
21
21
|
- A Google Cloud project with billing enabled and the APIs turned on:
|
|
22
22
|
|
|
23
23
|
```sh
|
|
@@ -71,7 +71,7 @@ developer_device_platform(
|
|
|
71
71
|
|
|
72
72
|
### Options
|
|
73
73
|
|
|
74
|
-
Options mirror the flags of `gcloud beta device-run sessions submit instrumentation`. Every option can also be set through the environment variable `DEVELOPER_DEVICE_PLATFORM_<OPTION
|
|
74
|
+
Options mirror the flags of `gcloud beta device-run sessions submit instrumentation`. Every option except the hash-valued ones (`additional_test_options`, `other_files_to_push`, `labels`) can also be set through the environment variable `DEVELOPER_DEVICE_PLATFORM_<OPTION>`; array values are comma-separated.
|
|
75
75
|
|
|
76
76
|
| Option | Type | Description |
|
|
77
77
|
| --- | --- | --- |
|
|
@@ -105,7 +105,7 @@ Options mirror the flags of `gcloud beta device-run sessions submit instrumentat
|
|
|
105
105
|
| `flaky_test_attempts` | Integer | Attempts per shard. Default `1`. |
|
|
106
106
|
| `flaky_test_parallel_retry` | Boolean | Retry in parallel instead of sequentially. |
|
|
107
107
|
| `flaky_test_retry_level` | String | `shard` or `test`. |
|
|
108
|
-
| `async` | Boolean | Return the session ID right after submission. Default `false`. |
|
|
108
|
+
| `async` | Boolean | Return the session ID right after submission; the output directory and console URL are still exposed. Default `false`. |
|
|
109
109
|
| `download_dir` | String | Local directory to download the session artifacts into. |
|
|
110
110
|
| `fail_on_failure` | Boolean | Fail the lane when the result is not `PASSED`. Default `true`. |
|
|
111
111
|
|
|
@@ -120,6 +120,7 @@ The action returns a `Fastlane::DeveloperDevicePlatform::Session` (or the sessio
|
|
|
120
120
|
| `DEVELOPER_DEVICE_PLATFORM_OUTPUT_DIRECTORY` | `gs://BUCKET/automation/sessions/SESSION_ID` |
|
|
121
121
|
| `DEVELOPER_DEVICE_PLATFORM_CONSOLE_URL` | Cloud Console link to the artifacts |
|
|
122
122
|
| `DEVELOPER_DEVICE_PLATFORM_DOWNLOAD_DIR` | Local artifact directory when `download_dir` is set |
|
|
123
|
+
| `DEVELOPER_DEVICE_PLATFORM_JUNIT_FILES` | Local `junit.xml` paths, one per job, when `download_dir` is set |
|
|
123
124
|
|
|
124
125
|
```ruby
|
|
125
126
|
session = developer_device_platform(devices: ["shiba-34"], test_apk: "test.apk", fail_on_failure: false)
|
|
@@ -128,7 +129,9 @@ session.jobs.each { |job| puts "#{job.display_name}: #{job.result} #{job.message
|
|
|
128
129
|
|
|
129
130
|
### Results
|
|
130
131
|
|
|
131
|
-
Artifacts land in `gs://BUCKET/automation/sessions/SESSION_ID/job-NNN/`: `
|
|
132
|
+
Artifacts land in `gs://BUCKET/automation/sessions/SESSION_ID/job-NNN/`: a `junit.xml` per job, and `test_result.xml`, `instrument.log`, `logcat.txt`, video recordings and pulled files under `execution-NNN/`. With `download_dir` the same tree is copied to `<download_dir>/<SESSION_ID>/` and the `junit.xml` paths are exposed in `DEVELOPER_DEVICE_PLATFORM_JUNIT_FILES` for JUnit reporters.
|
|
133
|
+
|
|
134
|
+
Interrupting fastlane while it waits (Ctrl-C, or a cancelled CI job) cancels the Device Run session before exiting, so no job keeps running unattended.
|
|
132
135
|
|
|
133
136
|
### Authentication
|
|
134
137
|
|
|
@@ -136,23 +139,6 @@ On CI, the simplest setup is a service account key passed through `service_accou
|
|
|
136
139
|
|
|
137
140
|
Device Run reads inputs and writes results through its own service agent (`service-PROJECT_NUMBER@gcp-sa-devicerun.iam.gserviceaccount.com`). When you point `bucket_name` at a bucket in another project, grant that agent `roles/storage.objectViewer` on it (see [Share storage](https://docs.cloud.google.com/developer-device-platform/device-run/share-storage)).
|
|
138
141
|
|
|
139
|
-
## Migrating from fastlane-plugin-firebase_test_lab_android
|
|
140
|
-
|
|
141
|
-
| firebase_test_lab_android | developer_device_platform |
|
|
142
|
-
| --- | --- |
|
|
143
|
-
| `project_id` | `project_id` |
|
|
144
|
-
| `gcloud_service_key_file` | `service_account_key_file` |
|
|
145
|
-
| `type: "instrumentation"` | Only instrumentation tests are supported by Device Run. Robo tests have no equivalent. |
|
|
146
|
-
| `devices: [{ model:, version:, locale:, orientation: }]` | `devices: ["<model>-<version>"]`, plus `locale` and `orientation` applied to every device |
|
|
147
|
-
| `app_apk` / `app_test_apk` | `apps: [app_apk]` / `test_apk` |
|
|
148
|
-
| `use_orchestrator: true` | `orchestrator_version: "auto"` |
|
|
149
|
-
| `timeout` | `instrumentation_timeout` |
|
|
150
|
-
| `firebase_test_lab_results_bucket` | `bucket_name` |
|
|
151
|
-
| `firebase_test_lab_results_dir` | Not supported. Results are grouped by session ID. |
|
|
152
|
-
| `download_dir` | `download_dir` |
|
|
153
|
-
| `extra_options` | The corresponding typed option (`test_targets`, `additional_test_options`, `video`, ...) |
|
|
154
|
-
| `slack_url`, `github_*` | Not included. Use `slack` or GitHub actions with the lane context values above. |
|
|
155
|
-
|
|
156
142
|
## Development
|
|
157
143
|
|
|
158
144
|
```sh
|
data/lib/fastlane/plugin/developer_device_platform/actions/developer_device_platform_action.rb
CHANGED
|
@@ -13,6 +13,7 @@ module Fastlane
|
|
|
13
13
|
DEVELOPER_DEVICE_PLATFORM_OUTPUT_DIRECTORY = :DEVELOPER_DEVICE_PLATFORM_OUTPUT_DIRECTORY
|
|
14
14
|
DEVELOPER_DEVICE_PLATFORM_CONSOLE_URL = :DEVELOPER_DEVICE_PLATFORM_CONSOLE_URL
|
|
15
15
|
DEVELOPER_DEVICE_PLATFORM_DOWNLOAD_DIR = :DEVELOPER_DEVICE_PLATFORM_DOWNLOAD_DIR
|
|
16
|
+
DEVELOPER_DEVICE_PLATFORM_JUNIT_FILES = :DEVELOPER_DEVICE_PLATFORM_JUNIT_FILES
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
class DeveloperDevicePlatformAction < Action
|
|
@@ -31,11 +32,12 @@ module Fastlane
|
|
|
31
32
|
session_id = client.submit(build_command(params)) { |line| UI.message(line) }
|
|
32
33
|
lane_context[SharedValues::DEVELOPER_DEVICE_PLATFORM_SESSION_ID] = session_id
|
|
33
34
|
UI.success("Created Device Run session #{session_id}")
|
|
34
|
-
|
|
35
|
+
if params[:async]
|
|
36
|
+
publish(client.describe(session_id, location: location))
|
|
37
|
+
return session_id
|
|
38
|
+
end
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
client.wait(session_id, location: location) { |line| UI.message(line) }
|
|
38
|
-
session = client.describe(session_id, location: location)
|
|
40
|
+
session = await(client, session_id, location)
|
|
39
41
|
publish(session)
|
|
40
42
|
download(client, session, params[:download_dir])
|
|
41
43
|
UI.message(DeveloperDevicePlatform::SessionSummary.new(session).to_s)
|
|
@@ -56,6 +58,23 @@ module Fastlane
|
|
|
56
58
|
DeveloperDevicePlatform::InstrumentationCommand.new(**COMMAND_OPTIONS.to_h { |key| [key, params[key]] })
|
|
57
59
|
end
|
|
58
60
|
|
|
61
|
+
def self.await(client, session_id, location)
|
|
62
|
+
UI.message("Waiting for session #{session_id} to complete...")
|
|
63
|
+
client.wait(session_id, location: location) { |line| UI.message(line) }
|
|
64
|
+
client.describe(session_id, location: location)
|
|
65
|
+
rescue SignalException
|
|
66
|
+
cancel(client, session_id, location)
|
|
67
|
+
raise
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.cancel(client, session_id, location)
|
|
71
|
+
UI.important("Interrupted. Cancelling Device Run session #{session_id}...")
|
|
72
|
+
client.cancel(session_id, location: location)
|
|
73
|
+
UI.important("Cancelled Device Run session #{session_id}")
|
|
74
|
+
rescue *RECOVERABLE_ERRORS => e
|
|
75
|
+
UI.error("Could not cancel Device Run session #{session_id}: #{e.message}")
|
|
76
|
+
end
|
|
77
|
+
|
|
59
78
|
def self.publish(session)
|
|
60
79
|
lane_context[SharedValues::DEVELOPER_DEVICE_PLATFORM_RESULT] = session.result
|
|
61
80
|
lane_context[SharedValues::DEVELOPER_DEVICE_PLATFORM_OUTPUT_DIRECTORY] = session.output_directory
|
|
@@ -68,6 +87,7 @@ module Fastlane
|
|
|
68
87
|
UI.message("Downloading results of session #{session.id} to #{destination}...")
|
|
69
88
|
local_path = client.download(session, destination) { |line| UI.verbose(line) }
|
|
70
89
|
lane_context[SharedValues::DEVELOPER_DEVICE_PLATFORM_DOWNLOAD_DIR] = local_path
|
|
90
|
+
lane_context[SharedValues::DEVELOPER_DEVICE_PLATFORM_JUNIT_FILES] = Dir.glob(File.join(local_path, "**", "junit.xml"))
|
|
71
91
|
UI.success("Results downloaded to #{local_path}")
|
|
72
92
|
end
|
|
73
93
|
|
|
@@ -110,7 +130,8 @@ module Fastlane
|
|
|
110
130
|
["DEVELOPER_DEVICE_PLATFORM_RESULT", "Overall result (PASSED, FAILED, ERROR, TIMED_OUT, CANCELLED)"],
|
|
111
131
|
["DEVELOPER_DEVICE_PLATFORM_OUTPUT_DIRECTORY", "Cloud Storage directory (gs://...) holding the session artifacts"],
|
|
112
132
|
["DEVELOPER_DEVICE_PLATFORM_CONSOLE_URL", "Cloud Console URL of the session artifacts"],
|
|
113
|
-
["DEVELOPER_DEVICE_PLATFORM_DOWNLOAD_DIR", "Local directory the artifacts were downloaded to, when download_dir is set"]
|
|
133
|
+
["DEVELOPER_DEVICE_PLATFORM_DOWNLOAD_DIR", "Local directory the artifacts were downloaded to, when download_dir is set"],
|
|
134
|
+
["DEVELOPER_DEVICE_PLATFORM_JUNIT_FILES", "Downloaded junit.xml paths, one per job, when download_dir is set"]
|
|
114
135
|
]
|
|
115
136
|
end
|
|
116
137
|
|
|
@@ -155,7 +176,7 @@ module Fastlane
|
|
|
155
176
|
DeveloperDevicePlatform::Options.all
|
|
156
177
|
end
|
|
157
178
|
|
|
158
|
-
private_class_method :build_gcloud, :build_command, :publish, :download, :conclude
|
|
179
|
+
private_class_method :build_gcloud, :build_command, :await, :cancel, :publish, :download, :conclude
|
|
159
180
|
end
|
|
160
181
|
end
|
|
161
182
|
end
|
|
@@ -8,6 +8,15 @@ module Fastlane
|
|
|
8
8
|
class Error < StandardError; end
|
|
9
9
|
|
|
10
10
|
SESSION_ANNOUNCEMENT = /Creating session \[([^\]]+)\]/
|
|
11
|
+
MINIMUM_GCLOUD_VERSION = "580.0.0".freeze
|
|
12
|
+
|
|
13
|
+
ENVIRONMENT_HINTS = {
|
|
14
|
+
/Invalid choice: 'device-run'/ =>
|
|
15
|
+
"The installed gcloud does not provide the device-run command group. " \
|
|
16
|
+
"Device Run requires Google Cloud CLI #{MINIMUM_GCLOUD_VERSION} or newer; run `gcloud components update`.",
|
|
17
|
+
/installation of components: \[beta\]/ =>
|
|
18
|
+
"The gcloud beta component is not installed; run `gcloud components install beta`."
|
|
19
|
+
}.freeze
|
|
11
20
|
|
|
12
21
|
attr_reader :gcloud
|
|
13
22
|
|
|
@@ -26,6 +35,11 @@ module Fastlane
|
|
|
26
35
|
nil
|
|
27
36
|
end
|
|
28
37
|
|
|
38
|
+
def cancel(session_id, location:)
|
|
39
|
+
device_run("sessions", "cancel", session_id, "--location=#{location}")
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
|
|
29
43
|
def describe(session_id, location:)
|
|
30
44
|
output = device_run("sessions", "describe", session_id, "--location=#{location}", "--full", "--format=json")
|
|
31
45
|
Session.parse(output.stdout)
|
|
@@ -44,6 +58,15 @@ module Fastlane
|
|
|
44
58
|
|
|
45
59
|
def device_run(*args, &progress)
|
|
46
60
|
gcloud.run("beta", "device-run", *args, &progress)
|
|
61
|
+
rescue Gcloud::CommandError => e
|
|
62
|
+
hint = environment_hint(e.stderr)
|
|
63
|
+
raise e unless hint
|
|
64
|
+
|
|
65
|
+
raise Error, "#{hint}\n#{e.message}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def environment_hint(stderr)
|
|
69
|
+
ENVIRONMENT_HINTS.find { |pattern, _| pattern.match?(stderr) }&.last
|
|
47
70
|
end
|
|
48
71
|
end
|
|
49
72
|
end
|
|
@@ -83,7 +83,7 @@ module Fastlane
|
|
|
83
83
|
def option(key, description, type: String, optional: true, default_value: nil, verify_block: nil)
|
|
84
84
|
FastlaneCore::ConfigItem.new(
|
|
85
85
|
key: key,
|
|
86
|
-
env_name:
|
|
86
|
+
env_name: env_name_for(key, type),
|
|
87
87
|
description: description,
|
|
88
88
|
type: type,
|
|
89
89
|
optional: optional,
|
|
@@ -92,6 +92,12 @@ module Fastlane
|
|
|
92
92
|
)
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
+
def env_name_for(key, type)
|
|
96
|
+
return nil if type == Hash
|
|
97
|
+
|
|
98
|
+
"DEVELOPER_DEVICE_PLATFORM_#{key.to_s.upcase}"
|
|
99
|
+
end
|
|
100
|
+
|
|
95
101
|
def enum_option(key, description, values)
|
|
96
102
|
option(key, "#{description} (#{values.join(' | ')})", verify_block: proc do |value|
|
|
97
103
|
UI.user_error!("#{key} must be one of #{values.join(', ')}, got '#{value}'") unless values.include?(value)
|