@astur-mobile/ios 0.1.0-beta.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/assets/ios-xctest-agent/AsturAgent.swift +1872 -0
- package/assets/ios-xctest-agent/AsturAgentBridgeClient.swift +144 -0
- package/assets/ios-xctest-agent/AsturAgentServer.swift +249 -0
- package/assets/ios-xctest-agent/AsturAgentUITests-Info.plist +30 -0
- package/assets/ios-xctest-agent/AsturAgentUITests.swift +55 -0
- package/assets/ios-xctest-agent/AsturIOSAgent.xcodeproj/project.pbxproj +448 -0
- package/assets/ios-xctest-agent/AsturIOSAgent.xcodeproj/xcshareddata/xcschemes/AsturIOSAgent.xcscheme +133 -0
- package/assets/ios-xctest-agent/HostApp/AppDelegate.swift +105 -0
- package/assets/ios-xctest-agent/HostApp/Info.plist +32 -0
- package/assets/ios-xctest-agent/README.md +74 -0
- package/dist/command.d.ts +9 -0
- package/dist/command.d.ts.map +1 -0
- package/dist/command.js +37 -0
- package/dist/command.js.map +1 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2039 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>CFBundleDevelopmentRegion</key>
|
|
6
|
+
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
7
|
+
<key>CFBundleExecutable</key>
|
|
8
|
+
<string>$(EXECUTABLE_NAME)</string>
|
|
9
|
+
<key>CFBundleIdentifier</key>
|
|
10
|
+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
11
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
|
12
|
+
<string>6.0</string>
|
|
13
|
+
<key>CFBundleName</key>
|
|
14
|
+
<string>$(PRODUCT_NAME)</string>
|
|
15
|
+
<key>CFBundlePackageType</key>
|
|
16
|
+
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
|
17
|
+
<key>CFBundleShortVersionString</key>
|
|
18
|
+
<string>1.0</string>
|
|
19
|
+
<key>CFBundleVersion</key>
|
|
20
|
+
<string>1</string>
|
|
21
|
+
<key>LSRequiresIPhoneOS</key>
|
|
22
|
+
<true/>
|
|
23
|
+
<key>UIApplicationSupportsIndirectInputEvents</key>
|
|
24
|
+
<true/>
|
|
25
|
+
<key>UILaunchScreen</key>
|
|
26
|
+
<dict/>
|
|
27
|
+
<key>UISupportedInterfaceOrientations</key>
|
|
28
|
+
<array>
|
|
29
|
+
<string>UIInterfaceOrientationPortrait</string>
|
|
30
|
+
</array>
|
|
31
|
+
</dict>
|
|
32
|
+
</plist>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# iOS XCUITest Agent
|
|
2
|
+
|
|
3
|
+
iOS native UI automation must run through XCTest/XCUITest. The iOS agent is
|
|
4
|
+
Astur's Swift device-side automation bridge. It is not an AI agent and it is not
|
|
5
|
+
an Appium/WebDriver server.
|
|
6
|
+
|
|
7
|
+
The public test API should match Android:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
await device.getById('login-submit-button').tap();
|
|
11
|
+
await expect(device.getByText('Welcome')).toBeVisible();
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The runtime path is:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
Astur locator/action API
|
|
18
|
+
-> @astur-mobile/core session element methods
|
|
19
|
+
-> @astur-mobile/ios agent transport
|
|
20
|
+
-> Swift XCUITest agent
|
|
21
|
+
-> XCUIApplication / XCUIElement
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
This directory is the code that makes iOS inspection and native interaction possible. The host-side `@astur-mobile/ios` package can install, launch, and screenshot simulator apps through Xcode tools, but only this XCTest runner can read the iOS accessibility tree and perform native element actions inside Apple's supported automation boundary.
|
|
25
|
+
|
|
26
|
+
Current command surface:
|
|
27
|
+
|
|
28
|
+
- `agent.ping`
|
|
29
|
+
- `device.setOrientation`
|
|
30
|
+
- `tree.get`
|
|
31
|
+
- `element.find`
|
|
32
|
+
- `element.findAll`
|
|
33
|
+
- `element.findMany`
|
|
34
|
+
- `element.wait`
|
|
35
|
+
- `element.tap`
|
|
36
|
+
- `element.doubleTap`
|
|
37
|
+
- `element.longPress`
|
|
38
|
+
- `element.fill`
|
|
39
|
+
- `element.drag`
|
|
40
|
+
- `gesture.tap`
|
|
41
|
+
- `gesture.doubleTap`
|
|
42
|
+
- `gesture.longPress`
|
|
43
|
+
- `gesture.swipe`
|
|
44
|
+
- `gesture.drag`
|
|
45
|
+
- `keyboard.state`
|
|
46
|
+
- `keyboard.dismiss`
|
|
47
|
+
|
|
48
|
+
Current implementation status:
|
|
49
|
+
|
|
50
|
+
- Swift XCUITest command dispatcher is implemented in `AsturAgent.swift`
|
|
51
|
+
- host-side iOS driver can connect to a provided endpoint or bootstrap the bundled simulator agent
|
|
52
|
+
- reverse HTTP bridge mode is available for simulator execution
|
|
53
|
+
- the bundled simulator agent is started once per Astur worker session, and Xcode DerivedData is reused per simulator id to avoid rebuilding into a fresh temp directory on every run
|
|
54
|
+
- native lookup, wait, tap, double tap, long press, fill, drag, swipe, and keyboard commands run in XCUITest
|
|
55
|
+
- fill replaces existing text inside the agent, using bounded delete input so React Native text fields do not leak state between specs
|
|
56
|
+
- orientation changes run through `XCUIDevice.shared.orientation`
|
|
57
|
+
- lifecycle, install, launch, screenshots, and video remain host-side through Xcode tools
|
|
58
|
+
|
|
59
|
+
Implementation rules:
|
|
60
|
+
|
|
61
|
+
- Use XCTest/XCUITest selectors and expectations for native UI control.
|
|
62
|
+
- Do wait, lookup, actionability checks, and the final action inside the agent.
|
|
63
|
+
- Return compact JSON snapshots for diagnostics and assertions.
|
|
64
|
+
- Keep simulator lifecycle in the host driver; keep native element control here.
|
|
65
|
+
- Preserve the public TypeScript API; complexity belongs in this layer.
|
|
66
|
+
|
|
67
|
+
Astur avoids Appium and WebDriver, but it cannot bypass Apple's XCTest
|
|
68
|
+
requirement for native iOS UI control.
|
|
69
|
+
|
|
70
|
+
Current platform limits:
|
|
71
|
+
|
|
72
|
+
- real-device execution still needs signing, provisioning, trusted-device handling, and real-device transport validation
|
|
73
|
+
- system alerts are limited to what XCTest exposes through stable queries
|
|
74
|
+
- direct per-app data/cache clearing is not available through `simctl`; use uninstall/reinstall reset for simulator clean state
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ChildProcess, type SpawnOptions } from 'node:child_process';
|
|
2
|
+
export interface CommandResult {
|
|
3
|
+
stdout: Buffer;
|
|
4
|
+
stderr: Buffer;
|
|
5
|
+
}
|
|
6
|
+
export declare function run(file: string, args: readonly string[], maxBuffer?: number): Promise<CommandResult>;
|
|
7
|
+
export declare function runText(file: string, args: readonly string[]): Promise<string>;
|
|
8
|
+
export declare function spawnCommand(file: string, args: readonly string[], options?: SpawnOptions): ChildProcess;
|
|
9
|
+
//# sourceMappingURL=command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,YAAY,EAA0B,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGnH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,SAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAc/G;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAGpF;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,YAAiB,GAAG,YAAY,CAK5G"}
|
package/dist/command.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { execFile, spawn } from 'node:child_process';
|
|
2
|
+
import { AsturError } from '@astur-mobile/core';
|
|
3
|
+
export function run(file, args, maxBuffer = 20 * 1024 * 1024) {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
execFile(file, [...args], { encoding: 'buffer', maxBuffer }, (error, stdout, stderr) => {
|
|
6
|
+
if (error) {
|
|
7
|
+
reject(toCommandError(file, args, error, stdout, stderr));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
resolve({
|
|
11
|
+
stdout: Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout ?? ''),
|
|
12
|
+
stderr: Buffer.isBuffer(stderr) ? stderr : Buffer.from(stderr ?? '')
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export async function runText(file, args) {
|
|
18
|
+
const result = await run(file, args);
|
|
19
|
+
return result.stdout.toString('utf8');
|
|
20
|
+
}
|
|
21
|
+
export function spawnCommand(file, args, options = {}) {
|
|
22
|
+
return spawn(file, [...args], {
|
|
23
|
+
...options,
|
|
24
|
+
stdio: options.stdio ?? 'ignore'
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function toCommandError(file, args, error, stdout, stderr) {
|
|
28
|
+
const stdoutText = Buffer.isBuffer(stdout) ? stdout.toString('utf8') : stdout;
|
|
29
|
+
const stderrText = Buffer.isBuffer(stderr) ? stderr.toString('utf8') : stderr;
|
|
30
|
+
const message = stderrText.trim() || stdoutText.trim() || error.message;
|
|
31
|
+
return new AsturError('COMMAND_FAILED', `${file} ${args.join(' ')} failed: ${message}`, {
|
|
32
|
+
code: error.code,
|
|
33
|
+
stdout: stdoutText,
|
|
34
|
+
stderr: stderrText
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAgE,MAAM,oBAAoB,CAAC;AACnH,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAOhD,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,IAAuB,EAAE,SAAS,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI;IACrF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACrF,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;gBAC1D,OAAO;YACT,CAAC;YAED,OAAO,CAAC;gBACN,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;gBACpE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;aACrE,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAY,EAAE,IAAuB;IACjE,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACrC,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,IAAuB,EAAE,UAAwB,EAAE;IAC5F,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE;QAC5B,GAAG,OAAO;QACV,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,QAAQ;KACjC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,cAAc,CACrB,IAAY,EACZ,IAAuB,EACvB,KAAwB,EACxB,MAAuB,EACvB,MAAuB;IAEvB,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9E,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9E,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,IAAI,UAAU,CAAC,IAAI,EAAE,IAAI,KAAK,CAAC,OAAO,CAAC;IAExE,OAAO,IAAI,UAAU,CAAC,gBAAgB,EAAE,GAAG,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,OAAO,EAAE,EAAE;QACtF,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,UAAU;QAClB,MAAM,EAAE,UAAU;KACnB,CAAC,CAAC;AACL,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { DeviceInfo, DoctorCheck, NormalizedCapabilities } from '@astur-mobile/protocol';
|
|
2
|
+
import { type PlatformDriver, type PlatformSession } from '@astur-mobile/core';
|
|
3
|
+
export interface IosDriverOptions {
|
|
4
|
+
xcrunPath?: string;
|
|
5
|
+
xcodebuildPath?: string;
|
|
6
|
+
}
|
|
7
|
+
interface DevicectlDevicesJson {
|
|
8
|
+
result?: {
|
|
9
|
+
devices?: DevicectlDeviceJson[];
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
interface DevicectlDeviceJson {
|
|
13
|
+
identifier?: string;
|
|
14
|
+
connectionProperties?: {
|
|
15
|
+
pairingState?: string;
|
|
16
|
+
tunnelState?: string;
|
|
17
|
+
transportType?: string;
|
|
18
|
+
};
|
|
19
|
+
deviceProperties?: {
|
|
20
|
+
bootState?: string;
|
|
21
|
+
developerModeStatus?: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
osVersionNumber?: string;
|
|
24
|
+
};
|
|
25
|
+
hardwareProperties?: {
|
|
26
|
+
marketingName?: string;
|
|
27
|
+
platform?: string;
|
|
28
|
+
udid?: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export declare function createIosDriver(options?: IosDriverOptions): IosDriver;
|
|
32
|
+
export declare class IosDriver implements PlatformDriver {
|
|
33
|
+
readonly platform: "ios";
|
|
34
|
+
private readonly xcrunPath;
|
|
35
|
+
private readonly xcodebuildPath;
|
|
36
|
+
constructor(options?: IosDriverOptions);
|
|
37
|
+
doctor(): Promise<DoctorCheck[]>;
|
|
38
|
+
listDevices(): Promise<DeviceInfo[]>;
|
|
39
|
+
private listDevicesForSelector;
|
|
40
|
+
private listSimulators;
|
|
41
|
+
private listConnectedDevices;
|
|
42
|
+
createSession(capabilities: NormalizedCapabilities): Promise<PlatformSession>;
|
|
43
|
+
private ensureDeviceReady;
|
|
44
|
+
private resolveNativeAgent;
|
|
45
|
+
private tryBootstrapBundledNativeAgent;
|
|
46
|
+
}
|
|
47
|
+
export declare function iosAgentDerivedDataRoot(): string;
|
|
48
|
+
/**
|
|
49
|
+
* Removes the managed XCUITest agent DerivedData directories left behind by
|
|
50
|
+
* previous agent source versions for a device, keeping only the current build
|
|
51
|
+
* (`keepPath`). Without this, every change to the bundled Swift agent — and each
|
|
52
|
+
* source-stamp it produces — leaves a full Xcode build behind, and they
|
|
53
|
+
* accumulate until the disk fills. Other devices' builds are left untouched.
|
|
54
|
+
* Best-effort: never throws.
|
|
55
|
+
*/
|
|
56
|
+
export declare function pruneStaleIosAgentDerivedData(deviceId: string, keepPath: string, root?: string): Promise<void>;
|
|
57
|
+
export declare function parseSimctlDevices(output: string): DeviceInfo[];
|
|
58
|
+
export declare function parseXcdeviceDevices(output: string): DeviceInfo[];
|
|
59
|
+
export declare function parseDevicectlDevices(output: string | DevicectlDevicesJson): DeviceInfo[];
|
|
60
|
+
export {};
|
|
61
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAKV,UAAU,EAGV,WAAW,EAmBX,sBAAsB,EAGvB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAKL,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AAG5B,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAoJD,UAAU,oBAAoB;IAC5B,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,mBAAmB,EAAE,CAAC;KACjC,CAAC;CACH;AAED,UAAU,mBAAmB;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oBAAoB,CAAC,EAAE;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,gBAAgB,CAAC,EAAE;QACjB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;IACF,kBAAkB,CAAC,EAAE;QACnB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AA8BD,wBAAgB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS,CAEzE;AAED,qBAAa,SAAU,YAAW,cAAc;IAC9C,QAAQ,CAAC,QAAQ,EAAG,KAAK,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;gBAE5B,OAAO,GAAE,gBAAqB;IAKpC,MAAM,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAoGhC,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YAS5B,sBAAsB;YAYtB,cAAc;YAKd,oBAAoB;IAS5B,aAAa,CAAC,YAAY,EAAE,sBAAsB,GAAG,OAAO,CAAC,eAAe,CAAC;YA6BrE,iBAAiB;YAyCjB,kBAAkB;YAqDlB,8BAA8B;CAuO7C;AA29CD,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD;AAED;;;;;;;GAOG;AACH,wBAAsB,6BAA6B,CACjD,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,MAAkC,GACvC,OAAO,CAAC,IAAI,CAAC,CAiBf;AAoND,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE,CAuB/D;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE,CAkBjE;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,oBAAoB,GAAG,UAAU,EAAE,CAgBzF"}
|