@testingbot/mobilewright-driver 0.1.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.
- package/LICENSE +202 -0
- package/README.md +211 -0
- package/dist/actions.d.ts +25 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +76 -0
- package/dist/actions.js.map +1 -0
- package/dist/app-storage.d.ts +20 -0
- package/dist/app-storage.d.ts.map +1 -0
- package/dist/app-storage.js +68 -0
- package/dist/app-storage.js.map +1 -0
- package/dist/capabilities.d.ts +27 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +120 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/ci.d.ts +8 -0
- package/dist/ci.d.ts.map +1 -0
- package/dist/ci.js +46 -0
- package/dist/ci.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +43 -0
- package/dist/cli.js.map +1 -0
- package/dist/device-catalog.d.ts +37 -0
- package/dist/device-catalog.d.ts.map +1 -0
- package/dist/device-catalog.js +159 -0
- package/dist/device-catalog.js.map +1 -0
- package/dist/driver.d.ts +80 -0
- package/dist/driver.d.ts.map +1 -0
- package/dist/driver.js +646 -0
- package/dist/driver.js.map +1 -0
- package/dist/errors.d.ts +41 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +104 -0
- package/dist/errors.js.map +1 -0
- package/dist/hierarchy.d.ts +10 -0
- package/dist/hierarchy.d.ts.map +1 -0
- package/dist/hierarchy.js +103 -0
- package/dist/hierarchy.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +19 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +108 -0
- package/dist/init.js.map +1 -0
- package/dist/keepalive.d.ts +16 -0
- package/dist/keepalive.d.ts.map +1 -0
- package/dist/keepalive.js +56 -0
- package/dist/keepalive.js.map +1 -0
- package/dist/keys.d.ts +32 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +117 -0
- package/dist/keys.js.map +1 -0
- package/dist/observer.d.ts +55 -0
- package/dist/observer.d.ts.map +1 -0
- package/dist/observer.js +148 -0
- package/dist/observer.js.map +1 -0
- package/dist/options.d.ts +154 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +49 -0
- package/dist/options.js.map +1 -0
- package/dist/rest-api.d.ts +67 -0
- package/dist/rest-api.d.ts.map +1 -0
- package/dist/rest-api.js +117 -0
- package/dist/rest-api.js.map +1 -0
- package/dist/run-log.d.ts +56 -0
- package/dist/run-log.d.ts.map +1 -0
- package/dist/run-log.js +72 -0
- package/dist/run-log.js.map +1 -0
- package/dist/tunnel.d.ts +13 -0
- package/dist/tunnel.d.ts.map +1 -0
- package/dist/tunnel.js +47 -0
- package/dist/tunnel.js.map +1 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/dist/webdriver-client.d.ts +43 -0
- package/dist/webdriver-client.d.ts.map +1 -0
- package/dist/webdriver-client.js +109 -0
- package/dist/webdriver-client.js.map +1 -0
- package/dist/webview.d.ts +16 -0
- package/dist/webview.d.ts.map +1 -0
- package/dist/webview.js +109 -0
- package/dist/webview.js.map +1 -0
- package/package.json +69 -0
package/dist/keys.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key-chord handling for pressKeys()/clearText(). Chords look like
|
|
3
|
+
* "ctrl+a", "backspace", "meta+shift+z".
|
|
4
|
+
*
|
|
5
|
+
* Android: translated to `mobile: pressKey` keycodes + metastate.
|
|
6
|
+
* iOS: translated to W3C key action sequences with WebDriver unicode keys.
|
|
7
|
+
*/
|
|
8
|
+
export function parseChord(chord) {
|
|
9
|
+
// A trailing '+' segment means the literal plus key ("ctrl++", "+").
|
|
10
|
+
const literalPlus = chord === '+' || chord.endsWith('++') || chord.endsWith('+ ');
|
|
11
|
+
const parts = chord.split('+').map((p) => p.trim()).filter(Boolean);
|
|
12
|
+
const key = literalPlus ? '+' : (parts.pop() ?? '');
|
|
13
|
+
return { modifiers: parts.map((m) => normalizeModifier(m)), key: key.toLowerCase() };
|
|
14
|
+
}
|
|
15
|
+
function normalizeModifier(mod) {
|
|
16
|
+
const m = mod.toLowerCase();
|
|
17
|
+
if (m === 'cmd' || m === 'command' || m === 'meta')
|
|
18
|
+
return 'meta';
|
|
19
|
+
if (m === 'ctrl' || m === 'control')
|
|
20
|
+
return 'ctrl';
|
|
21
|
+
if (m === 'alt' || m === 'option')
|
|
22
|
+
return 'alt';
|
|
23
|
+
return m;
|
|
24
|
+
}
|
|
25
|
+
// ─── Android (mobile: pressKey) ─────────────────────────────────
|
|
26
|
+
const ANDROID_KEYCODES = {
|
|
27
|
+
home: 3,
|
|
28
|
+
back: 4,
|
|
29
|
+
call: 5,
|
|
30
|
+
endcall: 6,
|
|
31
|
+
up: 19, down: 20, left: 21, right: 22,
|
|
32
|
+
volumeup: 24, volumedown: 25,
|
|
33
|
+
power: 26,
|
|
34
|
+
camera: 27,
|
|
35
|
+
tab: 61,
|
|
36
|
+
space: 62,
|
|
37
|
+
enter: 66,
|
|
38
|
+
backspace: 67,
|
|
39
|
+
delete: 112,
|
|
40
|
+
escape: 111,
|
|
41
|
+
menu: 82,
|
|
42
|
+
search: 84,
|
|
43
|
+
pageup: 92, pagedown: 93,
|
|
44
|
+
appswitch: 187,
|
|
45
|
+
};
|
|
46
|
+
const ANDROID_META = {
|
|
47
|
+
shift: 1, // META_SHIFT_ON
|
|
48
|
+
alt: 2, // META_ALT_ON
|
|
49
|
+
ctrl: 4096, // META_CTRL_ON
|
|
50
|
+
meta: 65536, // META_META_ON
|
|
51
|
+
};
|
|
52
|
+
export function toAndroidKeyPress(chord) {
|
|
53
|
+
let keycode = ANDROID_KEYCODES[chord.key];
|
|
54
|
+
if (keycode === undefined && /^[a-z]$/.test(chord.key)) {
|
|
55
|
+
keycode = 29 + (chord.key.charCodeAt(0) - 97); // KEYCODE_A = 29
|
|
56
|
+
}
|
|
57
|
+
if (keycode === undefined && /^[0-9]$/.test(chord.key)) {
|
|
58
|
+
keycode = 7 + (chord.key.charCodeAt(0) - 48); // KEYCODE_0 = 7
|
|
59
|
+
}
|
|
60
|
+
if (keycode === undefined) {
|
|
61
|
+
throw new Error(`TestingBotDriver: unsupported key "${chord.key}" on Android`);
|
|
62
|
+
}
|
|
63
|
+
const metastate = chord.modifiers.reduce((acc, mod) => {
|
|
64
|
+
const bit = ANDROID_META[mod];
|
|
65
|
+
if (bit === undefined)
|
|
66
|
+
throw new Error(`TestingBotDriver: unsupported modifier "${mod}" on Android`);
|
|
67
|
+
return acc | bit;
|
|
68
|
+
}, 0);
|
|
69
|
+
return metastate ? { keycode, metastate } : { keycode };
|
|
70
|
+
}
|
|
71
|
+
// ─── iOS (W3C key actions) ──────────────────────────────────────
|
|
72
|
+
const W3C_KEYS = {
|
|
73
|
+
shift: '\uE008',
|
|
74
|
+
ctrl: '\uE009',
|
|
75
|
+
alt: '\uE00A',
|
|
76
|
+
meta: '\uE03D',
|
|
77
|
+
enter: '\uE007',
|
|
78
|
+
tab: '\uE004',
|
|
79
|
+
space: ' ',
|
|
80
|
+
backspace: '\uE003',
|
|
81
|
+
delete: '\uE017',
|
|
82
|
+
escape: '\uE00C',
|
|
83
|
+
up: '\uE013', down: '\uE015', left: '\uE012', right: '\uE014',
|
|
84
|
+
pageup: '\uE00E', pagedown: '\uE00F',
|
|
85
|
+
home: '\uE011', end: '\uE010',
|
|
86
|
+
};
|
|
87
|
+
/** Build one W3C key-input source pressing the given chords in order. */
|
|
88
|
+
export function toW3CKeyActions(chords) {
|
|
89
|
+
const actions = [];
|
|
90
|
+
for (const chord of chords) {
|
|
91
|
+
const modifierKeys = chord.modifiers.map((mod) => {
|
|
92
|
+
const key = W3C_KEYS[mod];
|
|
93
|
+
if (!key)
|
|
94
|
+
throw new Error(`TestingBotDriver: unsupported modifier "${mod}"`);
|
|
95
|
+
return key;
|
|
96
|
+
});
|
|
97
|
+
const key = W3C_KEYS[chord.key] ?? (chord.key.length === 1 ? chord.key : undefined);
|
|
98
|
+
if (key === undefined) {
|
|
99
|
+
throw new Error(`TestingBotDriver: unsupported key "${chord.key}"`);
|
|
100
|
+
}
|
|
101
|
+
for (const mod of modifierKeys)
|
|
102
|
+
actions.push({ type: 'keyDown', value: mod });
|
|
103
|
+
actions.push({ type: 'keyDown', value: key }, { type: 'keyUp', value: key });
|
|
104
|
+
for (const mod of [...modifierKeys].reverse())
|
|
105
|
+
actions.push({ type: 'keyUp', value: mod });
|
|
106
|
+
}
|
|
107
|
+
return [{ type: 'key', id: 'keyboard', actions }];
|
|
108
|
+
}
|
|
109
|
+
/** Type plain text as a W3C key-input source (per-character down/up). */
|
|
110
|
+
export function typeTextActions(text) {
|
|
111
|
+
const actions = [];
|
|
112
|
+
for (const ch of text) {
|
|
113
|
+
actions.push({ type: 'keyDown', value: ch }, { type: 'keyUp', value: ch });
|
|
114
|
+
}
|
|
115
|
+
return [{ type: 'key', id: 'keyboard', actions }];
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=keys.js.map
|
package/dist/keys.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { RunResultInfo, TestInfo, TestObserver, TestResultInfo, TestRunInfo } from '@mobilewright/protocol';
|
|
2
|
+
import type { RestApiClient } from './rest-api.js';
|
|
3
|
+
export interface ReportableSession {
|
|
4
|
+
sessionId: string;
|
|
5
|
+
/** Connect/disconnect cycles (= tests) recorded for this session. */
|
|
6
|
+
spans: number;
|
|
7
|
+
/** Wall-clock bounds of the recorded cycles, when any exist. */
|
|
8
|
+
startedAt?: number;
|
|
9
|
+
endedAt?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface ObserverContext {
|
|
12
|
+
/** Every TestingBot session of this run (allocated + worker-rotated). */
|
|
13
|
+
sessions(): ReportableSession[];
|
|
14
|
+
build?: string;
|
|
15
|
+
/** Local-video requests made via startRecording({ output }). */
|
|
16
|
+
recordings?(): {
|
|
17
|
+
sessionId: string;
|
|
18
|
+
output: string;
|
|
19
|
+
}[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Reports outcomes to TestingBot. Sessions whose wall-clock interval maps to
|
|
23
|
+
* exactly one test (sessionPerTest mode) get that test's name and verdict;
|
|
24
|
+
* everything else — reused pool sessions hosting many tests, or ambiguous
|
|
25
|
+
* overlaps under high concurrency — gets the run-level verdict. Strictly
|
|
26
|
+
* best-effort: reporting failures never fail the test run.
|
|
27
|
+
*/
|
|
28
|
+
export declare class TestingBotTestObserver implements TestObserver {
|
|
29
|
+
private readonly api;
|
|
30
|
+
private readonly context;
|
|
31
|
+
private totalTests;
|
|
32
|
+
/** Final outcome per test id — onTestEnd fires once per ATTEMPT, and a
|
|
33
|
+
* later retry overwrites the earlier attempt's result. */
|
|
34
|
+
private readonly outcomes;
|
|
35
|
+
constructor(api: RestApiClient, context: ObserverContext);
|
|
36
|
+
onRunStart(run: TestRunInfo): void;
|
|
37
|
+
onTestEnd(test: TestInfo, result: TestResultInfo): void;
|
|
38
|
+
onRunEnd(result: RunResultInfo): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Fetch session videos requested via startRecording({ output }). The MP4
|
|
41
|
+
* finalizes only after the session ends, so this waits (bounded) for the
|
|
42
|
+
* asset to appear. Best-effort like all reporting.
|
|
43
|
+
*/
|
|
44
|
+
private downloadRecordings;
|
|
45
|
+
/**
|
|
46
|
+
* The single test that ran inside this session, when unambiguous.
|
|
47
|
+
* Direction matters: connect() and disconnect() happen INSIDE the test, so
|
|
48
|
+
* the session interval is a sub-interval of its test's — but a test's
|
|
49
|
+
* reported duration also covers fixture setup (device allocation), so the
|
|
50
|
+
* test interval is much wider than the session's. The session midpoint must
|
|
51
|
+
* therefore be looked up in the test intervals, never the other way around.
|
|
52
|
+
*/
|
|
53
|
+
private testForSession;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=observer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observer.d.ts","sourceRoot":"","sources":["../src/observer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,WAAW,EACZ,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAInD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,QAAQ,IAAI,iBAAiB,EAAE,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,UAAU,CAAC,IAAI;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACxD;AAWD;;;;;;GAMG;AACH,qBAAa,sBAAuB,YAAW,YAAY;IACzD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAgB;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,UAAU,CAAK;IACvB;+DAC2D;IAC3D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;gBAE/C,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe;IAKxD,UAAU,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI;IAKlC,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI;IAajD,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IA2CpD;;;;OAIG;YACW,kBAAkB;IAoChC;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;CAgBvB"}
|
package/dist/observer.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import createDebug from 'debug';
|
|
3
|
+
const debug = createDebug('testingbot:observer');
|
|
4
|
+
/**
|
|
5
|
+
* Reports outcomes to TestingBot. Sessions whose wall-clock interval maps to
|
|
6
|
+
* exactly one test (sessionPerTest mode) get that test's name and verdict;
|
|
7
|
+
* everything else — reused pool sessions hosting many tests, or ambiguous
|
|
8
|
+
* overlaps under high concurrency — gets the run-level verdict. Strictly
|
|
9
|
+
* best-effort: reporting failures never fail the test run.
|
|
10
|
+
*/
|
|
11
|
+
export class TestingBotTestObserver {
|
|
12
|
+
api;
|
|
13
|
+
context;
|
|
14
|
+
totalTests = 0;
|
|
15
|
+
/** Final outcome per test id — onTestEnd fires once per ATTEMPT, and a
|
|
16
|
+
* later retry overwrites the earlier attempt's result. */
|
|
17
|
+
outcomes = new Map();
|
|
18
|
+
constructor(api, context) {
|
|
19
|
+
this.api = api;
|
|
20
|
+
this.context = context;
|
|
21
|
+
}
|
|
22
|
+
onRunStart(run) {
|
|
23
|
+
this.totalTests = run.totalTests;
|
|
24
|
+
debug('run started: %d tests', run.totalTests);
|
|
25
|
+
}
|
|
26
|
+
onTestEnd(test, result) {
|
|
27
|
+
const failed = result.status === 'failed' || result.status === 'timedOut' || result.status === 'interrupted';
|
|
28
|
+
const error = result.errors[0]?.split('\n')[0] ?? result.status;
|
|
29
|
+
const endedAt = Date.now();
|
|
30
|
+
this.outcomes.set(test.id, {
|
|
31
|
+
title: test.title,
|
|
32
|
+
failed,
|
|
33
|
+
summary: failed ? `${test.title}: ${error}`.slice(0, 200) : undefined,
|
|
34
|
+
startedAt: endedAt - result.duration,
|
|
35
|
+
endedAt,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async onRunEnd(result) {
|
|
39
|
+
if (this.outcomes.size === 0) {
|
|
40
|
+
// No test finished (e.g. "mobilewright test --list" fires onRunStart
|
|
41
|
+
// and onRunEnd without any tests running) — don't report, especially
|
|
42
|
+
// not to stale run-file sessions. Skipped tests still produce outcomes.
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const success = result.status === 'passed';
|
|
46
|
+
const outcomes = [...this.outcomes.values()];
|
|
47
|
+
const failures = outcomes.filter((o) => o.failed).map((o) => o.summary).filter(Boolean).slice(0, 10);
|
|
48
|
+
const runMessage = success
|
|
49
|
+
? `${outcomes.length}/${this.totalTests} tests passed`
|
|
50
|
+
: `${failures.length} of ${outcomes.length} tests failed. ${failures.join(' | ')}`.slice(0, 500);
|
|
51
|
+
const sessions = this.context.sessions();
|
|
52
|
+
debug('run ended (%s), reporting to %d session(s)', result.status, sessions.length);
|
|
53
|
+
for (const session of sessions) {
|
|
54
|
+
const test = this.testForSession(session, outcomes);
|
|
55
|
+
try {
|
|
56
|
+
await this.api.updateTest(session.sessionId, test
|
|
57
|
+
? {
|
|
58
|
+
success: !test.failed,
|
|
59
|
+
name: test.title,
|
|
60
|
+
statusMessage: test.failed ? (test.summary ?? 'failed') : 'passed',
|
|
61
|
+
build: this.context.build,
|
|
62
|
+
groups: ['mobilewright'],
|
|
63
|
+
}
|
|
64
|
+
: {
|
|
65
|
+
success,
|
|
66
|
+
statusMessage: runMessage,
|
|
67
|
+
build: this.context.build,
|
|
68
|
+
groups: ['mobilewright'],
|
|
69
|
+
});
|
|
70
|
+
debug('reported %s -> %s', session.sessionId, test ? `test "${test.title}"` : `run success=${success}`);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
// Never fail the run over reporting.
|
|
74
|
+
console.warn(`TestingBotDriver: could not report result for session ${session.sessionId}: ${String(err)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
await this.downloadRecordings();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Fetch session videos requested via startRecording({ output }). The MP4
|
|
81
|
+
* finalizes only after the session ends, so this waits (bounded) for the
|
|
82
|
+
* asset to appear. Best-effort like all reporting.
|
|
83
|
+
*/
|
|
84
|
+
async downloadRecordings() {
|
|
85
|
+
const requests = this.context.recordings?.() ?? [];
|
|
86
|
+
const deadline = Date.now() + 120_000;
|
|
87
|
+
for (const request of requests) {
|
|
88
|
+
try {
|
|
89
|
+
let videoUrl;
|
|
90
|
+
for (;;) {
|
|
91
|
+
const test = await this.api.getTest(request.sessionId);
|
|
92
|
+
if (test.video === false) {
|
|
93
|
+
// Video was disabled for this session — waiting will not help.
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
// assets_available is the API's "processing finished" signal; a
|
|
97
|
+
// populated video URL alone also suffices (the URL is presigned,
|
|
98
|
+
// so it is fetched right away below).
|
|
99
|
+
if ((test.assets_available ?? true) && typeof test.video === 'string' && test.video) {
|
|
100
|
+
videoUrl = test.video;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
if (Date.now() >= deadline)
|
|
104
|
+
break;
|
|
105
|
+
await new Promise((resolve) => setTimeout(resolve, 3_000));
|
|
106
|
+
}
|
|
107
|
+
if (!videoUrl) {
|
|
108
|
+
console.warn(`TestingBotDriver: video for session ${request.sessionId} is unavailable (disabled or not ready in time); skipping ${request.output}`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const response = await fetch(videoUrl, { signal: AbortSignal.timeout(60_000) });
|
|
112
|
+
if (!response.ok)
|
|
113
|
+
throw new Error(`HTTP ${response.status}`);
|
|
114
|
+
await writeFile(request.output, Buffer.from(await response.arrayBuffer()));
|
|
115
|
+
debug('saved recording of %s to %s', request.sessionId, request.output);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
console.warn(`TestingBotDriver: could not download video for session ${request.sessionId}: ${String(err)}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The single test that ran inside this session, when unambiguous.
|
|
124
|
+
* Direction matters: connect() and disconnect() happen INSIDE the test, so
|
|
125
|
+
* the session interval is a sub-interval of its test's — but a test's
|
|
126
|
+
* reported duration also covers fixture setup (device allocation), so the
|
|
127
|
+
* test interval is much wider than the session's. The session midpoint must
|
|
128
|
+
* therefore be looked up in the test intervals, never the other way around.
|
|
129
|
+
*/
|
|
130
|
+
testForSession(session, outcomes) {
|
|
131
|
+
// A session that hosted several tests has no single correct name/verdict.
|
|
132
|
+
if (session.spans !== 1)
|
|
133
|
+
return undefined;
|
|
134
|
+
if (session.startedAt === undefined || session.endedAt === undefined)
|
|
135
|
+
return undefined;
|
|
136
|
+
const midpoint = (session.startedAt + session.endedAt) / 2;
|
|
137
|
+
// Clock skew tolerance between the worker's stamps and ours.
|
|
138
|
+
const SLACK_MS = 2_000;
|
|
139
|
+
const containing = outcomes.filter((o) => midpoint >= o.startedAt - SLACK_MS && midpoint <= o.endedAt + SLACK_MS);
|
|
140
|
+
if (containing.length === 1)
|
|
141
|
+
return containing[0];
|
|
142
|
+
if (containing.length > 1) {
|
|
143
|
+
debug('%d tests overlap session %s — falling back to the run verdict', containing.length, session.sessionId);
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=observer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observer.js","sourceRoot":"","sources":["../src/observer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which allocations an app entry applies to. Specific keys win over
|
|
3
|
+
* platform-wide ones ('ios-real' beats 'ios').
|
|
4
|
+
*/
|
|
5
|
+
export type AppSlot = 'android' | 'android-emulator' | 'android-real' | 'ios' | 'ios-simulator' | 'ios-real';
|
|
6
|
+
export interface TestingBotDriverOptions {
|
|
7
|
+
/**
|
|
8
|
+
* App under test per platform/device type. TestingBot sessions must start
|
|
9
|
+
* with an app (or a browser) — there is no app-less session or mid-session
|
|
10
|
+
* install — so the driver uploads these at allocation time and starts the
|
|
11
|
+
* session with `appium:app`. Values are local paths (.apk/.ipa/.zip) or
|
|
12
|
+
* existing storage URLs ("tb://...").
|
|
13
|
+
*
|
|
14
|
+
* apps: { android: './build/app.apk', 'ios-simulator': './build/app.zip', 'ios-real': './build/app.ipa' }
|
|
15
|
+
*/
|
|
16
|
+
apps?: Partial<Record<AppSlot, string>>;
|
|
17
|
+
/** TestingBot API key. Default: TESTINGBOT_KEY (or TB_KEY) env variable. */
|
|
18
|
+
key?: string;
|
|
19
|
+
/** TestingBot API secret. Default: TESTINGBOT_SECRET (or TB_SECRET) env variable. */
|
|
20
|
+
secret?: string;
|
|
21
|
+
/** WebDriver hub URL. Default: https://hub.testingbot.com/wd/hub */
|
|
22
|
+
hubUrl?: string;
|
|
23
|
+
/** REST API base URL. Default: https://api.testingbot.com/v1 */
|
|
24
|
+
apiUrl?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Max time to wait for a device allocation (POST /session), in ms. The hub
|
|
27
|
+
* queues the request until a matching device frees up and gives up after
|
|
28
|
+
* 390s, so the default (395s) lets the hub's own verdict arrive first.
|
|
29
|
+
*/
|
|
30
|
+
allocationTimeout?: number;
|
|
31
|
+
/** Per-WebDriver-command timeout, in ms. Default: 60000. */
|
|
32
|
+
commandTimeout?: number;
|
|
33
|
+
/**
|
|
34
|
+
* TestingBot idle timeout in seconds (`tb:options.idletimeout`). Pooled
|
|
35
|
+
* device slots sit idle between tests, so this defaults higher than
|
|
36
|
+
* TestingBot's own default of 130. Default: 230.
|
|
37
|
+
*/
|
|
38
|
+
idleTimeout?: number;
|
|
39
|
+
/** TestingBot max session duration in seconds (`tb:options.maxduration`). */
|
|
40
|
+
maxDuration?: number;
|
|
41
|
+
/** Pin the Appium version TestingBot uses (`tb:options.appiumVersion`). */
|
|
42
|
+
appiumVersion?: string;
|
|
43
|
+
/** Test name shown on the TestingBot dashboard (`tb:options.name`). */
|
|
44
|
+
name?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Build name for grouping sessions on the dashboard (`tb:options.build`).
|
|
47
|
+
* Defaults from the CI environment (GitHub Actions, GitLab, CircleCI,
|
|
48
|
+
* Buildkite, Bitrise, Travis, Azure, Jenkins, TeamCity — e.g.
|
|
49
|
+
* "owner/repo #123") or the TESTINGBOT_BUILD variable.
|
|
50
|
+
*/
|
|
51
|
+
build?: string;
|
|
52
|
+
/** Device timezone (tz database name, e.g. "Europe/Brussels"). Default: "Etc/UTC". */
|
|
53
|
+
timeZone?: string;
|
|
54
|
+
/** Route device traffic through a proxy in this country ("DE", "US", "*" = random). */
|
|
55
|
+
geoCountryCode?: string;
|
|
56
|
+
/** Simulate network conditions: a preset ("Edge", "3G", "4G", "airplane") or custom speeds. */
|
|
57
|
+
throttleNetwork?: string | {
|
|
58
|
+
downloadSpeed: number;
|
|
59
|
+
uploadSpeed: number;
|
|
60
|
+
latency: number;
|
|
61
|
+
loss: number;
|
|
62
|
+
};
|
|
63
|
+
/** Capture a screenshot at every test step. Default: false. */
|
|
64
|
+
screenshots?: boolean;
|
|
65
|
+
/** Record a video of each session. Default: true. */
|
|
66
|
+
video?: boolean;
|
|
67
|
+
/** Command-log capture: true, false, or 'strip-parameters' (redacts request params). */
|
|
68
|
+
recordLogs?: boolean | 'strip-parameters';
|
|
69
|
+
/** Make test results publicly accessible. Default: false. */
|
|
70
|
+
public?: boolean;
|
|
71
|
+
/** Custom metadata (release, commit hash, ...) shown on the test detail page. */
|
|
72
|
+
extra?: string;
|
|
73
|
+
/** Restrict allocation to tablets only. */
|
|
74
|
+
tabletOnly?: boolean;
|
|
75
|
+
/** Restrict allocation to phones only. */
|
|
76
|
+
phoneOnly?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Route sessions through a running TestingBot Tunnel with this identifier
|
|
79
|
+
* (`tb:options.tunnelIdentifier`). Start the tunnel yourself, or use the
|
|
80
|
+
* `tunnel` option to have the driver manage one.
|
|
81
|
+
*/
|
|
82
|
+
tunnelIdentifier?: string;
|
|
83
|
+
/**
|
|
84
|
+
* Have the driver start a TestingBot Tunnel before the run and stop it
|
|
85
|
+
* afterwards, so tests can reach localhost/staging servers. Requires the
|
|
86
|
+
* optional `testingbot-tunnel-launcher` package (and Java) on the machine.
|
|
87
|
+
* Pass `true`, or an object with a tunnel `identifier` and any other
|
|
88
|
+
* testingbot-tunnel-launcher options.
|
|
89
|
+
*/
|
|
90
|
+
tunnel?: boolean | ({
|
|
91
|
+
identifier?: string;
|
|
92
|
+
} & Record<string, unknown>);
|
|
93
|
+
/** Extra W3C/Appium capabilities, merged last (escape hatch). */
|
|
94
|
+
capabilities?: Record<string, unknown>;
|
|
95
|
+
/** Extra `tb:options` entries, merged last (escape hatch). */
|
|
96
|
+
tbOptions?: Record<string, unknown>;
|
|
97
|
+
/** Report pass/fail to TestingBot after the run. Default: 'on'. */
|
|
98
|
+
testResults?: 'on' | 'off';
|
|
99
|
+
/**
|
|
100
|
+
* Start a fresh TestingBot session for every test instead of reusing the
|
|
101
|
+
* device slot's session across tests. One dashboard entry, video, and
|
|
102
|
+
* pass/fail per test — at the cost of session startup time (device boot)
|
|
103
|
+
* on every test. Default: false (mobilewright's pool reuses sessions).
|
|
104
|
+
*/
|
|
105
|
+
sessionPerTest?: boolean;
|
|
106
|
+
}
|
|
107
|
+
export interface ResolvedOptions {
|
|
108
|
+
apps: Partial<Record<AppSlot, string>>;
|
|
109
|
+
key: string;
|
|
110
|
+
secret: string;
|
|
111
|
+
hubUrl: string;
|
|
112
|
+
apiUrl: string;
|
|
113
|
+
allocationTimeout: number;
|
|
114
|
+
commandTimeout: number;
|
|
115
|
+
idleTimeout: number;
|
|
116
|
+
maxDuration: number | undefined;
|
|
117
|
+
appiumVersion: string | undefined;
|
|
118
|
+
name: string | undefined;
|
|
119
|
+
build: string | undefined;
|
|
120
|
+
timeZone: string | undefined;
|
|
121
|
+
geoCountryCode: string | undefined;
|
|
122
|
+
throttleNetwork: string | {
|
|
123
|
+
downloadSpeed: number;
|
|
124
|
+
uploadSpeed: number;
|
|
125
|
+
latency: number;
|
|
126
|
+
loss: number;
|
|
127
|
+
} | undefined;
|
|
128
|
+
screenshots: boolean | undefined;
|
|
129
|
+
video: boolean;
|
|
130
|
+
recordLogs: boolean | 'strip-parameters' | undefined;
|
|
131
|
+
public: boolean | undefined;
|
|
132
|
+
extra: string | undefined;
|
|
133
|
+
tabletOnly: boolean | undefined;
|
|
134
|
+
phoneOnly: boolean | undefined;
|
|
135
|
+
tunnelIdentifier: string | undefined;
|
|
136
|
+
tunnel: false | ({
|
|
137
|
+
identifier?: string;
|
|
138
|
+
} & Record<string, unknown>);
|
|
139
|
+
capabilities: Record<string, unknown>;
|
|
140
|
+
tbOptions: Record<string, unknown>;
|
|
141
|
+
testResults: 'on' | 'off';
|
|
142
|
+
sessionPerTest: boolean;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Resolve options against environment variables and defaults. Missing
|
|
146
|
+
* credentials are tolerated here (`key`/`secret` become empty strings): the
|
|
147
|
+
* config file constructing the driver is imported in every mobilewright
|
|
148
|
+
* process, so throwing from the constructor would break unrelated commands.
|
|
149
|
+
* `prepare()` fails fast instead.
|
|
150
|
+
*/
|
|
151
|
+
export declare function resolveOptions(options?: TestingBotDriverOptions): ResolvedOptions;
|
|
152
|
+
/** Ensure credentials are present, with an actionable message when not. */
|
|
153
|
+
export declare function requireCredentials(opts: ResolvedOptions): void;
|
|
154
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"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"}
|
package/dist/options.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { detectCiBuild } from './ci.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve options against environment variables and defaults. Missing
|
|
4
|
+
* credentials are tolerated here (`key`/`secret` become empty strings): the
|
|
5
|
+
* config file constructing the driver is imported in every mobilewright
|
|
6
|
+
* process, so throwing from the constructor would break unrelated commands.
|
|
7
|
+
* `prepare()` fails fast instead.
|
|
8
|
+
*/
|
|
9
|
+
export function resolveOptions(options = {}) {
|
|
10
|
+
return {
|
|
11
|
+
apps: options.apps ?? {},
|
|
12
|
+
key: options.key ?? process.env['TESTINGBOT_KEY'] ?? process.env['TB_KEY'] ?? '',
|
|
13
|
+
secret: options.secret ?? process.env['TESTINGBOT_SECRET'] ?? process.env['TB_SECRET'] ?? '',
|
|
14
|
+
hubUrl: (options.hubUrl ?? 'https://hub.testingbot.com/wd/hub').replace(/\/+$/, ''),
|
|
15
|
+
apiUrl: (options.apiUrl ?? 'https://api.testingbot.com/v1').replace(/\/+$/, ''),
|
|
16
|
+
allocationTimeout: options.allocationTimeout ?? 395_000,
|
|
17
|
+
commandTimeout: options.commandTimeout ?? 60_000,
|
|
18
|
+
idleTimeout: options.idleTimeout ?? 230,
|
|
19
|
+
maxDuration: options.maxDuration,
|
|
20
|
+
appiumVersion: options.appiumVersion,
|
|
21
|
+
name: options.name,
|
|
22
|
+
build: options.build ?? detectCiBuild(),
|
|
23
|
+
timeZone: options.timeZone,
|
|
24
|
+
geoCountryCode: options.geoCountryCode,
|
|
25
|
+
throttleNetwork: options.throttleNetwork,
|
|
26
|
+
screenshots: options.screenshots,
|
|
27
|
+
video: options.video ?? true,
|
|
28
|
+
recordLogs: options.recordLogs,
|
|
29
|
+
public: options.public,
|
|
30
|
+
extra: options.extra,
|
|
31
|
+
tabletOnly: options.tabletOnly,
|
|
32
|
+
phoneOnly: options.phoneOnly,
|
|
33
|
+
tunnelIdentifier: options.tunnelIdentifier ?? (typeof options.tunnel === 'object' ? options.tunnel.identifier : undefined),
|
|
34
|
+
tunnel: options.tunnel === true ? {} : (options.tunnel || false),
|
|
35
|
+
capabilities: options.capabilities ?? {},
|
|
36
|
+
tbOptions: options.tbOptions ?? {},
|
|
37
|
+
testResults: options.testResults ?? 'on',
|
|
38
|
+
sessionPerTest: options.sessionPerTest ?? false,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Ensure credentials are present, with an actionable message when not. */
|
|
42
|
+
export function requireCredentials(opts) {
|
|
43
|
+
if (!opts.key || !opts.secret) {
|
|
44
|
+
throw new Error('TestingBot credentials are missing. Set TESTINGBOT_KEY and TESTINGBOT_SECRET ' +
|
|
45
|
+
'environment variables, or pass { key, secret } to new TestingBotDriver(...). ' +
|
|
46
|
+
'Find your credentials at https://testingbot.com/members/user/edit');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AA4IxC;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,UAAmC,EAAE;IAClE,OAAO;QACL,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,EAAE;QACxB,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE;QAChF,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE;QAC5F,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,IAAI,mCAAmC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QACnF,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,IAAI,+BAA+B,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QAC/E,iBAAiB,EAAE,OAAO,CAAC,iBAAiB,IAAI,OAAO;QACvD,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,MAAM;QAChD,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,GAAG;QACvC,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,aAAa,EAAE;QACvC,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI;QAC5B,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,IAAI,CAAC,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;QAC1H,MAAM,EAAE,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC;QAChE,YAAY,EAAE,OAAO,CAAC,YAAY,IAAI,EAAE;QACxC,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,EAAE;QAClC,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,IAAI;QACxC,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,KAAK;KAChD,CAAC;AACJ,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,kBAAkB,CAAC,IAAqB;IACtD,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CACb,+EAA+E;YAC/E,+EAA+E;YAC/E,mEAAmE,CACpE,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { FetchFn } from './webdriver-client.js';
|
|
2
|
+
export interface TestingBotDevice {
|
|
3
|
+
id: number;
|
|
4
|
+
name: string;
|
|
5
|
+
model_number?: string;
|
|
6
|
+
platform_name: 'Android' | 'iOS';
|
|
7
|
+
version: string | number;
|
|
8
|
+
available?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** One row of GET /v1/browsers — for mobile rows, a virtual device+OS combo. */
|
|
11
|
+
export interface BrowserEntry {
|
|
12
|
+
name?: string;
|
|
13
|
+
platform?: string;
|
|
14
|
+
platformName?: string;
|
|
15
|
+
deviceName?: string;
|
|
16
|
+
version?: string | number;
|
|
17
|
+
}
|
|
18
|
+
export interface StorageEntry {
|
|
19
|
+
app_url: string;
|
|
20
|
+
state?: 'PROCESSING' | 'DONE';
|
|
21
|
+
type?: string;
|
|
22
|
+
sim_only?: boolean;
|
|
23
|
+
url?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface TestUpdate {
|
|
26
|
+
success?: boolean;
|
|
27
|
+
name?: string;
|
|
28
|
+
statusMessage?: string;
|
|
29
|
+
build?: string;
|
|
30
|
+
extra?: string;
|
|
31
|
+
groups?: string[];
|
|
32
|
+
}
|
|
33
|
+
export interface TestDetails {
|
|
34
|
+
id?: number | string;
|
|
35
|
+
/** Signed S3 URL to the recording, or false when video was disabled. */
|
|
36
|
+
video?: string | false;
|
|
37
|
+
/** True once asset processing (video, logs, screenshots) has finished. */
|
|
38
|
+
assets_available?: boolean;
|
|
39
|
+
duration?: number;
|
|
40
|
+
[key: string]: unknown;
|
|
41
|
+
}
|
|
42
|
+
/** Client for api.testingbot.com/v1 (HTTP Basic auth with key:secret). */
|
|
43
|
+
export declare class RestApiClient {
|
|
44
|
+
private readonly apiUrl;
|
|
45
|
+
private readonly auth;
|
|
46
|
+
private readonly fetchFn;
|
|
47
|
+
constructor(apiUrl: string, key: string, secret: string, fetchFn?: FetchFn);
|
|
48
|
+
/** Validate credentials; used by prepare() to fail fast. */
|
|
49
|
+
getUser(): Promise<Record<string, unknown>>;
|
|
50
|
+
/** Physical devices. `available` restricts to currently-acquirable ones. */
|
|
51
|
+
getDevices(available: boolean): Promise<TestingBotDevice[]>;
|
|
52
|
+
/** Simulator/emulator (and desktop-browser) combos; mobile rows carry platformName + deviceName + OS version. */
|
|
53
|
+
getBrowsers(): Promise<BrowserEntry[]>;
|
|
54
|
+
getDevice(id: string | number): Promise<TestingBotDevice>;
|
|
55
|
+
/** Upload an app binary; returns the storage entry with its tb:// URL. */
|
|
56
|
+
uploadApp(filePath: string): Promise<StorageEntry>;
|
|
57
|
+
getStorageEntry(appUrl: string): Promise<StorageEntry>;
|
|
58
|
+
/** Update a test by WebDriver session UUID (pass/fail, name, build, ...). */
|
|
59
|
+
updateTest(sessionId: string, update: TestUpdate): Promise<void>;
|
|
60
|
+
getTest(sessionId: string): Promise<TestDetails>;
|
|
61
|
+
/** Force-stop a running session (fallback when DELETE /session fails). */
|
|
62
|
+
stopTest(sessionId: string): Promise<void>;
|
|
63
|
+
private request;
|
|
64
|
+
private parse;
|
|
65
|
+
}
|
|
66
|
+
export declare function sha256(data: Buffer): string;
|
|
67
|
+
//# sourceMappingURL=rest-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rest-api.d.ts","sourceRoot":"","sources":["../src/rest-api.ts"],"names":[],"mappings":"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"}
|