Repository navigation
Expand file tree
/
Copy pathstep.sh
More file actions
executable file
·811 lines (684 loc) · 25.1 KB
/
Copy pathstep.sh
File metadata and controls
executable file
·811 lines (684 loc) · 25.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
#!/bin/bash
#
# TestingBot App Automate - XCUITest
#
# Thin wrapper around `npx @testingbot/cli xcuitest`, which uploads the app and
# test bundle, runs the suite, streams progress and exits non-zero on failure.
# See https://github.com/testingbot/testingbotctl
#
# Step inputs arrive as environment variables set by Bitrise, so shellcheck
# cannot see where they are assigned; the inlined library also carries helpers
# this particular Step does not call.
# shellcheck disable=SC2154,SC2034,SC2329
set -eo pipefail
# >>> BEGIN inlined lib/testingbot.bash -- generated by scripts/build.sh; do not edit >>>
# shellcheck shell=bash
#
# Shared helpers for the TestingBot Bitrise Steps.
#
# This file is the single source of truth. It is inlined verbatim into each
# steps/*/step.sh by scripts/build.sh, because a published Step's repository has
# to stand on its own (Bitrise forbids submodules and on-demand downloads).
# Edit here, then run scripts/build.sh -- never edit the inlined copies.
#
# Written for bash 3.2 (the macOS system bash) -- no associative arrays,
# no `declare -A`, no `${var^^}`.
#
# SC2154: Step inputs reach us as environment variables set by Bitrise.
# SC2034: several variables here are read by the step.sh that inlines this file.
# SC2329: no single Step uses every helper.
# shellcheck disable=SC2154,SC2034,SC2329
TB_API_BASE="${TB_API_BASE:-https://api.testingbot.com/v1}"
# Seconds between polls when a Step waits on the API. Overridable so the offline
# suite doesn't have to sleep through real intervals.
TB_POLL_INTERVAL="${TB_POLL_INTERVAL:-3}"
# --- logging -----------------------------------------------------------------
# Bitrise renders ANSI colours in the build log.
if [ -t 1 ]; then
TB_RED='\033[31;1m'
TB_YELLOW='\033[33;1m'
TB_GREEN='\033[32;1m'
TB_BLUE='\033[34;1m'
TB_RESET='\033[0m'
else
TB_RED='' TB_YELLOW='' TB_GREEN='' TB_BLUE='' TB_RESET=''
fi
tb_info() { printf "${TB_BLUE}%s${TB_RESET}\n" "$*"; }
tb_done() { printf "${TB_GREEN}%s${TB_RESET}\n" "$*"; }
tb_warn() { printf "${TB_YELLOW}%s${TB_RESET}\n" "$*" >&2; }
tb_error() { printf "${TB_RED}%s${TB_RESET}\n" "$*" >&2; }
# tb_section <title> -- a visually separated block in the Bitrise log.
tb_section() {
echo
printf "${TB_BLUE}==> %s${TB_RESET}\n" "$*"
}
# --- failures ----------------------------------------------------------------
# Named errors, so a failing build tells the user exactly which upstream Step or
# setting is missing instead of dumping a raw curl/JSON error.
# tb_fail <CODE> [detail...]
tb_fail() {
local code="$1"
shift
echo >&2
tb_error "TestingBot: $(tb_error_message "$code")"
if [ "$#" -gt 0 ]; then
tb_error ""
tb_error "$*"
fi
exit 1
}
tb_error_message() {
case "$1" in
APP_NOT_FOUND)
cat <<'EOF'
Could not find the app binary to upload.
Set the `app_path` input, or add a build Step before this one that produces it:
Android Android Build -> $BITRISE_APK_PATH / $BITRISE_AAB_PATH
iOS Xcode Archive & Export -> $BITRISE_IPA_PATH
EOF
;;
APP_FILE_MISSING)
echo "The app path points at a file that does not exist."
;;
TEST_BUNDLE_NOT_FOUND)
cat <<'EOF'
Could not find the test bundle.
Set the `test_app_path` input, or add the matching build Step before this one:
Espresso Android Build (assembleAndroidTest) -> $BITRISE_TEST_APK_PATH
XCUITest Xcode Build for testing -> $BITRISE_TEST_BUNDLE_PATH
EOF
;;
RUNNER_APP_NOT_FOUND)
cat <<'EOF'
No *-Runner.app was found inside the test bundle directory.
`Xcode Build for testing` normally emits it under
<test bundle>/Debug-iphoneos/<Scheme>-Runner.app
Check that the Step ran, and that it built for a device destination.
EOF
;;
AUTH_FAILED)
cat <<'EOF'
TestingBot rejected the credentials (HTTP 401).
Check the `testingbot_key` / `testingbot_secret` inputs. Both should come from
Bitrise Secrets, not be typed into the Workflow. You can find your key and
secret at https://testingbot.com/members/user/api
EOF
;;
ACCOUNT_READ_ONLY)
cat <<'EOF'
Your TestingBot account is read-only (HTTP 403), so uploads are rejected.
This usually means the subscription lapsed or the team member lacks upload
rights. See https://testingbot.com/members
EOF
;;
NOT_FOUND)
echo "TestingBot returned HTTP 404 -- the app or resource does not exist."
;;
BAD_REQUEST)
echo "TestingBot rejected the request (HTTP 400)."
;;
NODE_MISSING)
cat <<'EOF'
Node.js is required by this Step but was not found on the PATH.
The TestingBot CLI needs Node.js 20 or newer. Add one of these Steps before
this one:
- `Install Node.js` / `nvm`
- or use a Bitrise Stack that ships Node 20+
EOF
;;
NODE_TOO_OLD)
cat <<'EOF'
The Node.js on this Stack is too old -- the TestingBot CLI needs Node.js 20+.
Add an `Install Node.js` Step before this one and pin it to 20 or newer.
EOF
;;
JAVA_MISSING)
cat <<'EOF'
Java is required to run the TestingBot Tunnel but was not found on the PATH.
The tunnel needs Java 11 or newer. Add an `Install Java` Step before this one,
or pick a Stack that ships a JDK.
EOF
;;
*)
echo "$1"
;;
esac
}
# --- inputs ------------------------------------------------------------------
# tb_require <input_name> <value>
tb_require() {
if [ -z "$2" ]; then
tb_fail "Required input \`$1\` is empty." \
"Set it in the Workflow Editor, or via a Secret if it holds a credential."
fi
}
# tb_export <KEY> <VALUE> -- publish an output for later Steps.
tb_export() {
if command -v envman >/dev/null 2>&1; then
envman add --key "$1" --value "$2"
else
# Outside a Bitrise build (local dev, unit tests) just report it.
tb_info " (envman unavailable) $1=$2"
fi
}
# tb_bool <value> -- normalise a step input to "true"/"false".
tb_bool() {
case "$1" in
true | yes | 1) echo "true" ;;
*) echo "false" ;;
esac
}
# --- artifact discovery ------------------------------------------------------
# tb_detect_app <explicit> <candidate...>
# Echoes the first non-empty candidate that exists on disk. An explicit value
# always wins, and is returned even if missing so the caller can report the
# path the user actually asked for.
tb_detect_app() {
local explicit="$1"
shift
if [ -n "$explicit" ]; then
echo "$explicit"
return 0
fi
local candidate
for candidate in "$@"; do
if [ -n "$candidate" ] && [ -e "$candidate" ]; then
echo "$candidate"
return 0
fi
done
return 1
}
# tb_assert_exists <path> <error_code>
tb_assert_exists() {
if [ ! -e "$1" ]; then
tb_fail "$2" "Looked for: $1"
fi
}
# --- HTTP --------------------------------------------------------------------
#
# tb_api_call writes the response body to $TB_RESPONSE_BODY and sets
# $TB_HTTP_STATUS. It deliberately does NOT use `curl --fail`, so that the API's
# own error body survives and we can map status codes to named errors.
TB_HTTP_STATUS=""
TB_RESPONSE_BODY=""
tb_api_call() {
local body_file status
body_file="$(mktemp)"
status="$(curl "$@" \
--silent --show-error \
--retry 3 --retry-delay 2 --connect-timeout 15 \
--output "$body_file" \
--write-out '%{http_code}')" || {
rm -f "$body_file"
tb_fail "Could not reach the TestingBot API." \
"Check the build machine's network access to ${TB_API_BASE}."
}
TB_HTTP_STATUS="$status"
TB_RESPONSE_BODY="$(cat "$body_file")"
rm -f "$body_file"
}
# tb_check_status -- turn a non-2xx response into a named failure.
tb_check_status() {
case "$TB_HTTP_STATUS" in
2*) return 0 ;;
400) tb_fail BAD_REQUEST "$(tb_api_error)" ;;
401) tb_fail AUTH_FAILED ;;
403) tb_fail ACCOUNT_READ_ONLY "$(tb_api_error)" ;;
404) tb_fail NOT_FOUND "$(tb_api_error)" ;;
*) tb_fail "TestingBot API returned HTTP ${TB_HTTP_STATUS}." "$(tb_api_error)" ;;
esac
}
# tb_api_error -- best-effort human message out of an error response body.
tb_api_error() {
local msg
msg="$(tb_json_get 'error')"
[ -z "$msg" ] && msg="$(tb_json_get 'message')"
[ -n "$msg" ] && echo "API said: $msg"
}
# tb_json_get <key> -- read a top-level scalar from $TB_RESPONSE_BODY.
# Uses jq when present (it is on every Bitrise stack) and falls back to sed so
# the Step still works on a minimal machine.
tb_json_get() {
if command -v jq >/dev/null 2>&1; then
printf '%s' "$TB_RESPONSE_BODY" | jq -r --arg k "$1" '.[$k] // empty' 2>/dev/null
else
printf '%s' "$TB_RESPONSE_BODY" |
sed -n "s/.*\"$1\"[[:space:]]*:[[:space:]]*\"\{0,1\}\([^,\"}]*\)\"\{0,1\}.*/\1/p" |
head -1
fi
}
# --- credentials -------------------------------------------------------------
# tb_export_credentials <key> <secret>
# TestingBot's own tooling reads two different spellings: the REST docs, tunnel
# jar and the Gradle/Fastlane/Buildkite plugins use TESTINGBOT_KEY/SECRET, while
# the CLI, GitHub Action, CircleCI orb and Jenkins plugin use TB_KEY/TB_SECRET.
# Export both so anything the user runs afterwards is authenticated.
tb_export_credentials() {
export TESTINGBOT_KEY="$1"
export TESTINGBOT_SECRET="$2"
export TB_KEY="$1"
export TB_SECRET="$2"
}
# --- CI metadata -------------------------------------------------------------
# tb_repo_owner <git-url> / tb_repo_name <git-url>
# Handles both git@host:owner/name.git and https://host/owner/name.git
tb_repo_owner() {
printf '%s' "$1" | sed -e 's/\.git$//' -e 's#^.*[:/]\([^/]*\)/[^/]*$#\1#'
}
tb_repo_name() {
printf '%s' "$1" | sed -e 's/\.git$//' -e 's#^.*/##'
}
# tb_default_build_name -- a stable, readable identifier for grouping the run in
# the TestingBot dashboard.
tb_default_build_name() {
if [ -n "$BITRISE_APP_TITLE" ] && [ -n "$BITRISE_BUILD_NUMBER" ]; then
echo "${BITRISE_APP_TITLE} #${BITRISE_BUILD_NUMBER}"
elif [ -n "$BITRISE_BUILD_NUMBER" ]; then
echo "bitrise-${BITRISE_BUILD_NUMBER}"
else
echo "bitrise-local"
fi
}
# --- TestingBot CLI ----------------------------------------------------------
#
# The Espresso/XCUITest/Maestro Steps drive https://github.com/testingbot/testingbotctl
# (`@testingbot/cli`) rather than reimplementing the App Automate API. It already
# uploads, triggers, polls, downloads JUnit reports and -- crucially -- exits
# non-zero when tests fail.
# tb_require_node -- the CLI needs Node.js 20+.
tb_require_node() {
command -v node >/dev/null 2>&1 || tb_fail NODE_MISSING
command -v npx >/dev/null 2>&1 || tb_fail NODE_MISSING
local major
major="$(node --version | sed -e 's/^v//' -e 's/\..*$//')"
case "$major" in
'' | *[!0-9]*) tb_warn "Could not parse the Node.js version ($(node --version)); continuing." ;;
*)
if [ "$major" -lt 20 ]; then
tb_fail NODE_TOO_OLD "Found Node.js $(node --version)."
fi
;;
esac
}
# The argument vector handed to the CLI. Built up with tb_flag / tb_switch /
# tb_arg so that empty inputs simply don't produce a flag, and every value stays
# a single argv entry however it is quoted.
TB_CLI_ARGS=()
tb_arg() {
# tb_arg <value...> -- append positional arguments verbatim.
local value
for value in "$@"; do
TB_CLI_ARGS[${#TB_CLI_ARGS[@]}]="$value"
done
}
tb_flag() {
# tb_flag <--flag> <value> -- append `--flag value`, or nothing if empty.
if [ -n "$2" ]; then
TB_CLI_ARGS[${#TB_CLI_ARGS[@]}]="$1"
TB_CLI_ARGS[${#TB_CLI_ARGS[@]}]="$2"
fi
}
tb_switch() {
# tb_switch <--flag> <value> -- append `--flag` when the input is truthy.
if [ "$(tb_bool "$2")" = "true" ]; then
TB_CLI_ARGS[${#TB_CLI_ARGS[@]}]="$1"
fi
}
# tb_cli_common_flags -- flags every framework Step passes.
# Expects: test_name, tunnel, tunnel_identifier, throttle_network, geo_country_code,
# timezone, run_async, quiet.
#
# `--build` is deliberately NOT here: the CLI accepts it for espresso and
# xcuitest but not for maestro, so each Step passes it itself. Verified against
# `npx @testingbot/cli@1.1.1 <subcommand> --help`.
tb_cli_common_flags() {
tb_flag --name "$test_name"
tb_flag --tunnel-identifier "$tunnel_identifier"
tb_flag --throttle-network "$throttle_network"
tb_flag --geo-country-code "$geo_country_code"
tb_flag --timezone "$timezone"
tb_switch --tunnel "$tunnel"
tb_switch --async "$run_async"
tb_switch --quiet "$quiet"
# CI metadata -- also drives TestingBot's GitHub PR checks.
tb_flag --commit-sha "$BITRISE_GIT_COMMIT"
if [ -n "$BITRISE_PULL_REQUEST" ]; then
tb_flag --pull-request-id "$BITRISE_PULL_REQUEST"
fi
if [ -n "$GIT_REPOSITORY_URL" ]; then
tb_flag --repo-owner "$(tb_repo_owner "$GIT_REPOSITORY_URL")"
tb_flag --repo-name "$(tb_repo_name "$GIT_REPOSITORY_URL")"
fi
}
# tb_append_additional_args <string> -- split an `additional_args` input on
# whitespace and append each word. Deliberately unquoted word-splitting: the
# input is a command-line fragment, which is the whole point of the escape hatch.
tb_append_additional_args() {
[ -z "$1" ] && return 0
local word
# shellcheck disable=SC2086
for word in $1; do
tb_arg "$word"
done
}
# tb_run_cli <subcommand> -- run the pinned CLI with the accumulated arguments.
# Sets TB_CLI_STATUS instead of exiting, so the caller decides what a test
# failure means for the build.
TB_CLI_STATUS=0
tb_run_cli() {
local subcommand="$1"
shift
tb_section "Running ${subcommand} tests on TestingBot"
tb_info "npx @testingbot/cli@${cli_version} ${subcommand} $(tb_redact "${TB_CLI_ARGS[@]}")"
echo
set +e
npx --yes "@testingbot/cli@${cli_version}" "$subcommand" "${TB_CLI_ARGS[@]}"
TB_CLI_STATUS=$?
set -e
}
# tb_redact <args...> -- args as a single line, for logging. Credentials are
# passed via the environment rather than argv, but keep this defensive.
tb_redact() {
local out="" arg
for arg in "$@"; do
case "$arg" in
*"$TB_KEY"*) arg="[REDACTED]" ;;
*"$TB_SECRET"*) arg="[REDACTED]" ;;
esac
out="${out}${out:+ }${arg}"
done
printf '%s' "$out"
}
# --- Bitrise Test Reports ----------------------------------------------------
# tb_export_test_report <test-name> <junit-xml-path>
#
# Lays the report out the way Bitrise's Test Reports add-on expects:
# $BITRISE_TEST_RESULT_DIR/<test-name>/<report>.xml
# $BITRISE_TEST_RESULT_DIR/<test-name>/test-info.json {"test-name": "..."}
# `Deploy to Bitrise.io` then picks it up. This is the same contract the
# `custom-test-results-export` Step implements.
#
# Note that Bitrise hands every Step its *own* $BITRISE_TEST_RESULT_DIR --
# .../test_results<build>/step_test_result<step> -- so a later Step cannot see
# what this one wrote. `Deploy to Bitrise.io` collects the shared parent.
tb_export_test_report() {
local test_name="$1" junit_path="$2" export_dir
if [ -z "$BITRISE_TEST_RESULT_DIR" ]; then
tb_warn "\$BITRISE_TEST_RESULT_DIR is not set -- skipping the Test Reports export."
tb_warn "It is set automatically inside a Bitrise build."
return 0
fi
if [ ! -f "$junit_path" ]; then
tb_warn "No JUnit report at ${junit_path} -- skipping the Test Reports export."
return 0
fi
export_dir="${BITRISE_TEST_RESULT_DIR}/${test_name}"
mkdir -p "$export_dir"
cp "$junit_path" "${export_dir}/"
printf '{"test-name":"%s"}\n' "$test_name" >"${export_dir}/test-info.json"
tb_done "Exported results to Test Reports as \"${test_name}\"."
tb_info "Add the \`Deploy to Bitrise.io\` Step to see them on the Test Reports tab."
}
# tb_find_junit <dir> -- first .xml file in the CLI's report output directory.
tb_find_junit() {
find "$1" -name '*.xml' -type f 2>/dev/null | head -1
}
# --- TestingBot Tunnel -------------------------------------------------------
TB_TUNNEL_DOWNLOAD_URL="https://testingbot.com/downloads/testingbot-tunnel.zip"
# tb_require_java -- the tunnel is a jar and needs Java 11+.
tb_require_java() {
command -v java >/dev/null 2>&1 || tb_fail JAVA_MISSING
}
# tb_download_tunnel <url> <dest-dir> <sha256-or-empty>
# Echoes the path to the tunnel jar.
tb_download_tunnel() {
local url="$1" dest="$2" expected="$3" archive jar actual
mkdir -p "$dest"
archive="${dest}/testingbot-tunnel.zip"
tb_section "Downloading the TestingBot Tunnel" >&2
tb_info "From: $url" >&2
curl --fail --location --silent --show-error \
--retry 3 --retry-delay 2 --connect-timeout 10 --max-time 300 \
--output "$archive" "$url" >&2 ||
tb_fail "Could not download the TestingBot Tunnel from ${url}."
if [ -n "$expected" ]; then
actual="$(tb_sha256 "$archive")"
if [ "$actual" != "$expected" ]; then
tb_fail "The downloaded tunnel archive does not match the expected checksum." \
"expected: ${expected}
actual: ${actual}"
fi
tb_done "Checksum verified." >&2
else
tb_warn "No \`download_sha256\` set -- the archive's integrity was not verified."
tb_warn "It was fetched over HTTPS from testingbot.com."
fi
(cd "$dest" && unzip -qo "testingbot-tunnel.zip") >&2 ||
tb_fail "Could not unpack the TestingBot Tunnel archive."
# The archive carries the version in the jar's name -- testingbot-tunnel-4.8.jar
# -- while the download URL is unversioned, so the name changes with every
# tunnel release. Match on the prefix rather than an exact name, and sort so
# the pick is deterministic if a download directory is reused across versions.
jar="$(find "$dest" -name 'testingbot-tunnel*.jar' -type f 2>/dev/null | sort | tail -1)"
[ -n "$jar" ] || tb_fail "The tunnel archive did not contain a testingbot-tunnel jar."
tb_done "Unpacked $jar" >&2
echo "$jar"
}
tb_sha256() {
if command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$1" | awk '{print $1}'
elif command -v sha256sum >/dev/null 2>&1; then
sha256sum "$1" | awk '{print $1}'
else
echo ""
fi
}
# tb_wait_for_tunnel <ready-file> <pid> <timeout-seconds> <log-file>
#
# The tunnel touches its --readyfile once it is actually usable, which is the
# documented way to wait for it. Watching the console output instead is what
# makes tunnel steps flaky.
tb_wait_for_tunnel() {
local ready_file="$1" pid="$2" timeout="$3" log_file="$4" waited=0
tb_section "Waiting for the tunnel to come up"
while [ ! -f "$ready_file" ]; do
if ! kill -0 "$pid" 2>/dev/null; then
tb_error "The tunnel process exited before it became ready."
tb_dump_tunnel_log "$log_file"
tb_fail "The TestingBot Tunnel failed to start."
fi
if [ "$waited" -ge "$timeout" ]; then
tb_error "The tunnel did not become ready within ${timeout}s."
tb_dump_tunnel_log "$log_file"
kill "$pid" 2>/dev/null
tb_fail "Timed out waiting for the TestingBot Tunnel." \
"Raise \`ready_timeout\` if your network is slow to establish the tunnel."
fi
sleep 2
waited=$((waited + 2))
done
tb_done "Tunnel ready after ${waited}s (pid ${pid})."
}
# tb_dump_tunnel_log <log-file> -- the most common support question in this
# area is "why didn't the tunnel start", so always surface the log on failure.
tb_dump_tunnel_log() {
if [ -f "$1" ]; then
echo >&2
tb_error "--- tunnel log ---"
cat "$1" >&2
tb_error "--- end of tunnel log ---"
else
tb_warn "No tunnel log at $1"
fi
}
# --- app bundles -------------------------------------------------------------
# tb_prepare_app_bundle <path>
#
# TestingBot's uploader only accepts zip-based archives -- it checks for the
# `PK\x03\x04` magic bytes and rejects anything else -- but an iOS Simulator
# build is a `.app` *directory*, which is exactly what Bitrise's
# `Xcode Build for Simulator` Step puts in $BITRISE_APP_DIR_PATH. Zip it.
#
# Echoes the path to use; progress goes to stderr so it stays out of the
# captured value.
tb_prepare_app_bundle() {
local given="$1" zipped
# Anything that is already a file (.apk/.aab/.ipa/.zip) is handed straight on.
[ -d "$given" ] || {
echo "$given"
return 0
}
tb_section "Packaging the app bundle" >&2
tb_info "Bundle: $given" >&2
zipped="${TMPDIR:-/tmp}/$(basename "$given").zip"
rm -f "$zipped"
(cd "$(dirname "$given")" && zip -qry "$zipped" "$(basename "$given")") >&2
tb_done "Zipped to $zipped" >&2
echo "$zipped"
}
# --- iOS test bundles --------------------------------------------------------
# tb_prepare_xcui_test_bundle <path>
#
# TestingBot wants the XCUITest suite as a zip containing the `*-Runner.app`.
# Bitrise's `Xcode Build for testing` Step instead exports a directory, usually
# <test bundle>/Debug-iphoneos/<Scheme>-Runner.app
# so find the runner and zip it. A path that is already a zip is passed through.
#
# Echoes the path to use; anything else goes to stderr so it stays out of the
# captured value.
tb_prepare_xcui_test_bundle() {
local given="$1" runner zipped
case "$given" in
*.zip)
echo "$given"
return 0
;;
esac
if [ -f "$given" ]; then
# A plain file that isn't a zip -- hand it over and let the API judge it.
echo "$given"
return 0
fi
if [ ! -d "$given" ]; then
tb_fail TEST_BUNDLE_NOT_FOUND "Looked for: $given"
fi
# The directory may itself be the runner app.
case "$given" in
*-Runner.app)
runner="$given"
;;
*)
runner="$(find "$given" -type d -name '*-Runner.app' 2>/dev/null | head -1)"
;;
esac
if [ -z "$runner" ]; then
tb_fail RUNNER_APP_NOT_FOUND "Searched under: $given"
fi
tb_section "Packaging the XCUITest runner" >&2
tb_info "Runner: $runner" >&2
zipped="${TMPDIR:-/tmp}/testingbot-xcui-test-suite-$$.zip"
rm -f "$zipped"
(cd "$(dirname "$runner")" && zip -qry "$zipped" "$(basename "$runner")") >&2
tb_done "Zipped to $zipped" >&2
echo "$zipped"
}
# tb_split_list <value> -- one item per line, from a newline- or pipe-separated
# input. Blank lines and surrounding whitespace are dropped. Bitrise's own
# convention is pipe-separated, but the Workflow Editor gives a multi-line box,
# so accept both rather than making the user guess.
tb_split_list() {
printf '%s\n' "$1" |
tr '|' '\n' |
sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' \
-e '/^$/d'
}
# <<< END inlined lib/testingbot.bash <<<
# --- inputs ------------------------------------------------------------------
tb_require testingbot_key "$testingbot_key"
tb_require testingbot_secret "$testingbot_secret"
tb_require device "$device"
tb_export_credentials "$testingbot_key" "$testingbot_secret"
cli_version="${cli_version:-latest}"
test_name="${test_name:-XCUITest}"
[ -z "$build_name" ] && build_name="$(tb_default_build_name)"
fail_on_test_failure="$(tb_bool "${fail_on_test_failure:-true}")"
export_to_test_reports="$(tb_bool "${export_to_test_reports:-true}")"
run_async="$(tb_bool "$run_async")"
tb_require_node
# --- locate the artifacts ----------------------------------------------------
app="$(tb_detect_app "$app_path" "$BITRISE_IPA_PATH" "$BITRISE_APP_DIR_PATH")" || tb_fail APP_NOT_FOUND
tb_assert_exists "$app" APP_FILE_MISSING
# A simulator build is a `.app` directory; the uploader only takes archives.
app="$(tb_prepare_app_bundle "$app")"
test_bundle="$(tb_detect_app "$test_app_path" "$BITRISE_TEST_BUNDLE_PATH")" || tb_fail TEST_BUNDLE_NOT_FOUND
# `Xcode Build for testing` exports a directory; TestingBot wants a zip of the
# *-Runner.app inside it.
test_app="$(tb_prepare_xcui_test_bundle "$test_bundle")"
tb_section "Test run"
tb_info "App: $app"
tb_info "Test bundle: $test_app"
tb_info "Device: $device"
tb_info "Build: $build_name"
# --- reports -----------------------------------------------------------------
report_dir=""
if [ "$run_async" != "true" ]; then
report_dir="${report_output_dir:-${TMPDIR:-/tmp}/testingbot-report-$$}"
mkdir -p "$report_dir"
fi
# --- build the command -------------------------------------------------------
tb_arg "$app" "$test_app"
tb_flag --device "$device"
tb_flag --platform-version "$platform_version"
tb_switch --real-device "$real_device"
tb_switch --tablet-only "$tablet_only"
tb_switch --phone-only "$phone_only"
tb_flag --orientation "$orientation"
tb_flag --locale "$locale"
tb_flag --language "$language"
# --build is espresso/xcuitest only; maestro rejects it.
tb_flag --build "$build_name"
tb_cli_common_flags
if [ -n "$report_dir" ]; then
tb_flag --report junit
tb_flag --report-output-dir "$report_dir"
fi
tb_append_additional_args "$additional_args"
# --- run ---------------------------------------------------------------------
tb_run_cli xcuitest
if [ "$TB_CLI_STATUS" -eq 0 ]; then
test_status="passed"
else
test_status="failed"
fi
# --- outputs -----------------------------------------------------------------
junit_path=""
if [ -n "$report_dir" ]; then
junit_path="$(tb_find_junit "$report_dir")"
fi
tb_section "Exporting outputs"
tb_export TESTINGBOT_TEST_STATUS "$test_status"
tb_export TESTINGBOT_BUILD_NAME "$build_name"
tb_export TESTINGBOT_JUNIT_REPORT_PATH "$junit_path"
if [ "$export_to_test_reports" = "true" ] && [ -n "$junit_path" ]; then
tb_export_test_report "$test_name" "$junit_path"
fi
# --- decide the build result -------------------------------------------------
if [ "$run_async" = "true" ]; then
echo
tb_done "Tests started. Results will appear at https://testingbot.com/members/builds"
exit 0
fi
echo
if [ "$test_status" = "passed" ]; then
tb_done "All tests passed."
exit 0
fi
if [ "$fail_on_test_failure" = "true" ]; then
tb_error "Tests failed."
tb_error "Set \`fail_on_test_failure\` to false if the build should stay green anyway."
exit "$TB_CLI_STATUS"
fi
tb_warn "Tests failed, but \`fail_on_test_failure\` is off -- keeping the build green."
tb_warn "The result is available as \$TESTINGBOT_TEST_STATUS."
exit 0