-
Notifications
You must be signed in to change notification settings - Fork 384
Expand file tree
/
Copy pathandroid.ts
More file actions
182 lines (145 loc) · 6.99 KB
/
android.ts
File metadata and controls
182 lines (145 loc) · 6.99 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
import assert from "node:assert";
import { PNG } from "../src/png";
import { AndroidRobot, AndroidDeviceManager } from "../src/android";
const manager = new AndroidDeviceManager();
const devices = manager.getConnectedDevices();
const hasOneAndroidDevice = devices.length === 1;
describe("android", () => {
const android = new AndroidRobot(devices?.[0]?.deviceId || "");
it("should be able to get the screen size", async function() {
hasOneAndroidDevice || this.skip();
const screenSize = await android.getScreenSize();
assert.ok(screenSize.width > 1024);
assert.ok(screenSize.height > 1024);
assert.ok(screenSize.scale === 1);
assert.equal(Object.keys(screenSize).length, 3, "screenSize should have exactly 3 properties");
});
it("should be able to take screenshot", async function() {
hasOneAndroidDevice || this.skip();
const screenSize = await android.getScreenSize();
const screenshot = await android.getScreenshot();
assert.ok(screenshot.length > 64 * 1024);
// must be a valid png image that matches the screen size
const image = new PNG(screenshot);
const pngSize = image.getDimensions();
assert.equal(pngSize.width, screenSize.width);
assert.equal(pngSize.height, screenSize.height);
});
it("should be able to list apps", async function() {
hasOneAndroidDevice || this.skip();
const apps = await android.listApps();
const packages = apps.map(app => app.packageName);
assert.ok(packages.includes("com.android.settings"));
});
it("should be able to open a url", async function() {
hasOneAndroidDevice || this.skip();
await android.adb("shell", "input", "keyevent", "HOME");
await android.openUrl("https://www.example.com");
});
it("should be able to list elements on screen", async function() {
hasOneAndroidDevice || this.skip();
await android.terminateApp("com.android.chrome");
await android.adb("shell", "input", "keyevent", "HOME");
await android.openUrl("https://www.example.com");
const elements = await android.getElementsOnScreen();
// make sure title (TextView) is present
const foundTitle = elements.find(element => element.type === "android.widget.TextView" && element.text?.startsWith("This domain is for use in illustrative examples in documents"));
assert.ok(foundTitle, "Title element not found");
// make sure navbar (EditText) is present
const foundNavbar = elements.find(element => element.type === "android.widget.EditText" && element.label === "Search or type URL" && element.text === "example.com");
assert.ok(foundNavbar, "Navbar element not found");
// this is an icon, but has accessibility label
const foundSecureIcon = elements.find(element => element.type === "android.widget.ImageButton" && element.text === "" && element.label === "New tab");
assert.ok(foundSecureIcon, "New tab icon not found");
});
it("should be able to send keys and tap", async function() {
hasOneAndroidDevice || this.skip();
await android.terminateApp("com.google.android.deskclock");
await android.adb("shell", "pm", "clear", "com.google.android.deskclock");
await android.launchApp("com.google.android.deskclock");
// We probably start at Clock tab
await new Promise(resolve => setTimeout(resolve, 3000));
let elements = await android.getElementsOnScreen();
const timerElement = elements.find(e => e.label === "Timer" && e.type === "android.widget.FrameLayout");
assert.ok(timerElement !== undefined);
await android.tap(timerElement.rect.x, timerElement.rect.y);
// now we're in Timer tab
await new Promise(resolve => setTimeout(resolve, 3000));
elements = await android.getElementsOnScreen();
const currentTime = elements.find(e => e.text === "00h 00m 00s");
assert.ok(currentTime !== undefined, "Expected time to be 00h 00m 00s");
await android.sendKeys("123456");
// now the title has changed with new timer
await new Promise(resolve => setTimeout(resolve, 3000));
elements = await android.getElementsOnScreen();
const newTime = elements.find(e => e.text === "12h 34m 56s");
assert.ok(newTime !== undefined, "Expected time to be 12h 34m 56s");
await android.terminateApp("com.google.android.deskclock");
});
it("should be able to launch and terminate an app", async function() {
hasOneAndroidDevice || this.skip();
// kill if running
await android.terminateApp("com.android.chrome");
await android.launchApp("com.android.chrome");
await new Promise(resolve => setTimeout(resolve, 3000));
const processes = await android.listRunningProcesses();
assert.ok(processes.includes("com.android.chrome"));
await android.terminateApp("com.android.chrome");
const processes2 = await android.listRunningProcesses();
assert.ok(!processes2.includes("com.android.chrome"));
});
it("should handle orientation changes", async function() {
hasOneAndroidDevice || this.skip();
// assume we start in portrait
const originalOrientation = await android.getOrientation();
assert.equal(originalOrientation, "portrait");
const screenSize1 = await android.getScreenSize();
// set to landscape
await android.setOrientation("landscape");
await new Promise(resolve => setTimeout(resolve, 1500));
const orientation = await android.getOrientation();
assert.equal(orientation, "landscape");
const screenSize2 = await android.getScreenSize();
// set to portrait
await android.setOrientation("portrait");
await new Promise(resolve => setTimeout(resolve, 1500));
const orientation2 = await android.getOrientation();
assert.equal(orientation2, "portrait");
// screen size should not have changed
assert.deepEqual(screenSize1, screenSize2);
});
it("should be able to dump logcat for a package", async function() {
hasOneAndroidDevice || this.skip();
const res = await android.logcatDump({
packageName: "com.android.settings",
lines: 120,
format: "threadtime",
buffers: ["main", "crash"],
minPriority: "I",
});
assert.ok(typeof res.text === "string");
assert.ok(res.text.length > 0);
assert.equal(res.truncated, false);
assert.ok(res.meta.pidResolved !== undefined, "Expected pidResolved to be set");
assert.ok(res.meta.pidResolved > 0, "Expected pidResolved to be > 0");
assert.ok(
res.meta.pidFilterMode === "logcat--pid" || res.meta.pidFilterMode === "client-side" || res.meta.pidFilterMode === "none",
"Unexpected pidFilterMode"
);
// logcat often prints this marker line
assert.ok(res.text.includes("beginning of"), "Expected log output to include buffer marker");
});
it("should support includeRegex / excludeRegex filtering in logcat dump", async function() {
hasOneAndroidDevice || this.skip();
const res = await android.logcatDump({
packageName: "com.android.settings",
lines: 400,
includeRegex: "SecurityException|FATAL|Exception|ANR|E ",
excludeRegex: "Choreographer",
});
assert.ok(typeof res.text === "string");
// If there are no matches, text may be empty; but tool should still succeed and return meta.
assert.ok(res.meta.pidResolved !== undefined, "Expected pidResolved to be set");
assert.ok(res.meta.pidResolved > 0, "Expected pidResolved to be > 0");
});
});