appium-uiautomator2-driver 6.6.1 → 6.6.2

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/build/lib/commands/screenshot.d.ts +17 -14
  3. package/build/lib/commands/screenshot.d.ts.map +1 -1
  4. package/build/lib/commands/screenshot.js +50 -36
  5. package/build/lib/commands/screenshot.js.map +1 -1
  6. package/build/test/unit/commands/general-specs.d.ts +2 -0
  7. package/build/test/unit/commands/general-specs.d.ts.map +1 -0
  8. package/build/test/unit/commands/general-specs.js +80 -0
  9. package/build/test/unit/commands/general-specs.js.map +1 -0
  10. package/build/test/unit/commands/screenshot-specs.d.ts +2 -0
  11. package/build/test/unit/commands/screenshot-specs.d.ts.map +1 -0
  12. package/build/test/unit/commands/screenshot-specs.js +124 -0
  13. package/build/test/unit/commands/screenshot-specs.js.map +1 -0
  14. package/build/test/unit/css-converter-specs.d.ts +2 -0
  15. package/build/test/unit/css-converter-specs.d.ts.map +1 -0
  16. package/build/test/unit/css-converter-specs.js +68 -0
  17. package/build/test/unit/css-converter-specs.js.map +1 -0
  18. package/build/test/unit/driver-specs.d.ts +2 -0
  19. package/build/test/unit/driver-specs.d.ts.map +1 -0
  20. package/build/test/unit/driver-specs.js +328 -0
  21. package/build/test/unit/driver-specs.js.map +1 -0
  22. package/build/test/unit/uiautomator2-specs.d.ts +2 -0
  23. package/build/test/unit/uiautomator2-specs.d.ts.map +1 -0
  24. package/build/test/unit/uiautomator2-specs.js +325 -0
  25. package/build/test/unit/uiautomator2-specs.js.map +1 -0
  26. package/build/tsconfig.tsbuildinfo +1 -1
  27. package/lib/commands/screenshot.ts +146 -0
  28. package/npm-shrinkwrap.json +8 -8
  29. package/package.json +2 -2
  30. package/lib/commands/screenshot.js +0 -116
@@ -0,0 +1,146 @@
1
+ import _ from 'lodash';
2
+ import B from 'bluebird';
3
+ import {imageUtil} from 'appium/support';
4
+ import type {AndroidUiautomator2Driver} from '../driver';
5
+ import type {Screenshot} from './types';
6
+ import type {StringRecord} from '@appium/types';
7
+
8
+ // Matches SurfaceFlinger output format:
9
+ // Physical: Display 4619827259835644672 (HWC display 0): port=0 pnpId=GGL displayName="EMU_display_0"
10
+ // Virtual: Display 11529215049243506835 (Virtual display): displayName="Emulator 2D Display" uniqueId="..."
11
+ const DISPLAY_PATTERN = /^Display\s+(\d+)\s+\((?:HWC\s+display\s+(\d+)|Virtual\s+display)\):.*?displayName="([^"]*)"/gm;
12
+
13
+ /**
14
+ * Parses SurfaceFlinger display output to extract display information.
15
+ * @param displaysInfo - The raw output from `adb shell dumpsys SurfaceFlinger --display-id`
16
+ * @returns A record mapping display IDs to their information (without payload)
17
+ */
18
+ export function parseSurfaceFlingerDisplays(
19
+ displaysInfo: string
20
+ ): Record<string, Partial<Screenshot>> {
21
+ const infos: Record<string, Partial<Screenshot>> = {};
22
+ const lines = displaysInfo.split('\n');
23
+
24
+ for (const line of lines) {
25
+ let match: RegExpExecArray | null;
26
+
27
+ // Try to match display header line
28
+ if ((match = DISPLAY_PATTERN.exec(line))) {
29
+ const [, matchedDisplayId, hwcId, displayName] = match; // Skip match[0] (full match), then Display ID, HWC ID (optional), Display name
30
+
31
+ // Determine if default: HWC display 0 is default, or first physical display if no HWC info
32
+ const isDefault = hwcId !== undefined
33
+ ? hwcId === '0'
34
+ : !line.includes('Virtual') && Object.keys(infos).length === 0;
35
+
36
+ infos[matchedDisplayId] = {
37
+ id: matchedDisplayId,
38
+ isDefault,
39
+ name: displayName || undefined,
40
+ };
41
+
42
+ // Reset regex lastIndex for next iteration
43
+ DISPLAY_PATTERN.lastIndex = 0;
44
+ }
45
+ }
46
+
47
+ return infos;
48
+ }
49
+
50
+ /**
51
+ * Takes a screenshot of the current viewport
52
+ */
53
+ export async function mobileViewportScreenshot(
54
+ this: AndroidUiautomator2Driver
55
+ ): Promise<string> {
56
+ return await this.getViewportScreenshot();
57
+ }
58
+
59
+ /**
60
+ * Gets a screenshot of the current viewport
61
+ */
62
+ export async function getViewportScreenshot(
63
+ this: AndroidUiautomator2Driver
64
+ ): Promise<string> {
65
+ const screenshot = await this.getScreenshot();
66
+ const rect = await this.getViewPortRect();
67
+ return await imageUtil.cropBase64Image(screenshot, rect);
68
+ }
69
+
70
+ /**
71
+ * Gets a screenshot of the current screen
72
+ */
73
+ export async function getScreenshot(
74
+ this: AndroidUiautomator2Driver
75
+ ): Promise<string> {
76
+ if (this.mjpegStream) {
77
+ const data = await this.mjpegStream.lastChunkPNGBase64();
78
+ if (data) {
79
+ return data;
80
+ }
81
+ this.log.warn(
82
+ 'Tried to get screenshot from active MJPEG stream, but there ' +
83
+ 'was no data yet. Falling back to regular screenshot methods.'
84
+ );
85
+ }
86
+ return String(
87
+ await this.uiautomator2.jwproxy.command('/screenshot', 'GET')
88
+ );
89
+ }
90
+
91
+ /**
92
+ * Retrieves screenshots of each display available to Android.
93
+ * This functionality is only supported since Android 10.
94
+ * @param displayId - Android display identifier to take a screenshot for.
95
+ * If not provided then screenshots of all displays are going to be returned.
96
+ * If no matches were found then an error is thrown.
97
+ */
98
+ export async function mobileScreenshots(
99
+ this: AndroidUiautomator2Driver,
100
+ displayId?: number | string
101
+ ): Promise<StringRecord<Screenshot>> {
102
+ const displaysInfo = await this.adb.shell([
103
+ 'dumpsys',
104
+ 'SurfaceFlinger',
105
+ '--display-id',
106
+ ]);
107
+ const infos = parseSurfaceFlingerDisplays(displaysInfo);
108
+ if (_.isEmpty(infos)) {
109
+ this.log.debug(displaysInfo);
110
+ throw new Error('Cannot determine the information about connected Android displays');
111
+ }
112
+ this.log.info(`Parsed Android display infos: ${JSON.stringify(infos)}`);
113
+
114
+ const toB64Screenshot = async (dispId: string): Promise<string> =>
115
+ (await this.adb.takeScreenshot(dispId)).toString('base64');
116
+
117
+ const displayIdStr: string | null =
118
+ _.isNil(displayId) || displayId === ''
119
+ ? null
120
+ : String(displayId);
121
+
122
+ if (displayIdStr) {
123
+ if (!infos[displayIdStr]) {
124
+ throw new Error(
125
+ `The provided display identifier '${displayId}' is not known. ` +
126
+ `Only the following displays have been detected: ${JSON.stringify(infos)}`
127
+ );
128
+ }
129
+ return {
130
+ [displayIdStr]: {
131
+ ...infos[displayIdStr],
132
+ payload: await toB64Screenshot(displayIdStr),
133
+ } as Screenshot,
134
+ };
135
+ }
136
+
137
+ const allInfos = _.values(infos).filter((info): info is Partial<Screenshot> & {id: string} => !!info?.id);
138
+ const screenshots = await B.all(allInfos.map((info) => toB64Screenshot(info.id)));
139
+ for (const [info, payload] of _.zip(allInfos, screenshots) as Array<[Partial<Screenshot>, string]>) {
140
+ if (info && payload) {
141
+ info.payload = payload;
142
+ }
143
+ }
144
+ return infos as StringRecord<Screenshot>;
145
+ }
146
+
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "appium-uiautomator2-driver",
3
- "version": "6.6.1",
3
+ "version": "6.6.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "appium-uiautomator2-driver",
9
- "version": "6.6.1",
9
+ "version": "6.6.2",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "appium-adb": "^14.0.0",
@@ -631,9 +631,9 @@
631
631
  }
632
632
  },
633
633
  "node_modules/appium-chromedriver": {
634
- "version": "8.0.23",
635
- "resolved": "https://registry.npmjs.org/appium-chromedriver/-/appium-chromedriver-8.0.23.tgz",
636
- "integrity": "sha512-T1A+9A4X396/MSmVaLkQb1Gkbj0OYxTG/hIr+Qq/Iv0HMsIq7boFiDauPhfi5cVoJ6gMdv9KQc+9BdhvpPAboA==",
634
+ "version": "8.0.24",
635
+ "resolved": "https://registry.npmjs.org/appium-chromedriver/-/appium-chromedriver-8.0.24.tgz",
636
+ "integrity": "sha512-OL2OoICSwBzv7k08MnVEemRGTCL+cHxyOkPWxg4gWQ4nv76+U/rKGtU9b7MP0j1rC7rdpZGjGuyDcsD3U7BZqg==",
637
637
  "license": "Apache-2.0",
638
638
  "dependencies": {
639
639
  "@appium/base-driver": "^10.0.0-rc.2",
@@ -656,9 +656,9 @@
656
656
  }
657
657
  },
658
658
  "node_modules/appium-uiautomator2-server": {
659
- "version": "9.8.0",
660
- "resolved": "https://registry.npmjs.org/appium-uiautomator2-server/-/appium-uiautomator2-server-9.8.0.tgz",
661
- "integrity": "sha512-iG/PufSB2hRj7yMtnpiyocOkEQdlmXHGfKEVTJXtuVmNO2ZZ/frZ8jdknO83T2zqzQBVUj5saNBx+zpfiWvI2g==",
659
+ "version": "9.9.0",
660
+ "resolved": "https://registry.npmjs.org/appium-uiautomator2-server/-/appium-uiautomator2-server-9.9.0.tgz",
661
+ "integrity": "sha512-j9XN7AjP7dWk9HA+/segTQUyhV4n0LyMJdtKMLRXp60r+6oYX4UYh57Rp9NaHY5UPykw+2eFhOdc5n101IDItg==",
662
662
  "license": "Apache-2.0",
663
663
  "engines": {
664
664
  "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "automated testing",
8
8
  "android"
9
9
  ],
10
- "version": "6.6.1",
10
+ "version": "6.6.2",
11
11
  "bugs": {
12
12
  "url": "https://github.com/appium/appium-uiautomator2-driver/issues"
13
13
  },
@@ -48,7 +48,7 @@
48
48
  "rebuild": "npm run clean; npm run build",
49
49
  "format": "prettier -w ./lib",
50
50
  "reset": "node ./scripts/reset.js",
51
- "test": "mocha --exit --timeout 1m \"./test/unit/**/*-specs.js\""
51
+ "test": "mocha --exit --timeout 1m \"./test/unit/**/*-specs.ts\""
52
52
  },
53
53
  "prettier": {
54
54
  "bracketSpacing": false,
@@ -1,116 +0,0 @@
1
- import _ from 'lodash';
2
- import B from 'bluebird';
3
- import {imageUtil} from 'appium/support';
4
-
5
- // Display 4619827259835644672 (HWC display 0): port=0 pnpId=GGL displayName="EMU_display_0"
6
- const DISPLAY_PATTERN = /^Display\s+(\d+)\s+\(.+display\s+(\d+)\).+displayName="([^"]*)/gm;
7
-
8
- /**
9
- * @this {AndroidUiautomator2Driver}
10
- * @returns {Promise<string>}
11
- */
12
- export async function mobileViewportScreenshot() {
13
- return await this.getViewportScreenshot();
14
- }
15
-
16
- /**
17
- * @this {AndroidUiautomator2Driver}
18
- * @returns {Promise<string>}
19
- */
20
- export async function getViewportScreenshot() {
21
- const screenshot = await this.getScreenshot();
22
- const rect = await this.getViewPortRect();
23
- return await imageUtil.cropBase64Image(screenshot, rect);
24
- }
25
-
26
- /**
27
- * @this {AndroidUiautomator2Driver}
28
- * @returns {Promise<string>}
29
- */
30
- export async function getScreenshot() {
31
- if (this.mjpegStream) {
32
- const data = await this.mjpegStream.lastChunkPNGBase64();
33
- if (data) {
34
- return data;
35
- }
36
- this.log.warn(
37
- 'Tried to get screenshot from active MJPEG stream, but there ' +
38
- 'was no data yet. Falling back to regular screenshot methods.'
39
- );
40
- }
41
- return String(
42
- await /** @type {import('../uiautomator2').UiAutomator2Server} */ (
43
- this.uiautomator2
44
- ).jwproxy.command('/screenshot', 'GET')
45
- );
46
- }
47
-
48
- /**
49
- * Retrieves screenshots of each display available to Android.
50
- * This functionality is only supported since Android 10.
51
- * @this {AndroidUiautomator2Driver}
52
- * @param {number | string} [displayId] Android display identifier to take a screenshot for.
53
- * If not provided then screenshots of all displays are going to be returned.
54
- * If no matches were found then an error is thrown.
55
- * @returns {Promise<import('@appium/types').StringRecord<import('./types').Screenshot>>}
56
- */
57
- export async function mobileScreenshots(displayId) {
58
- const displaysInfo = await /** @type {import('appium-adb').ADB} */ (this.adb).shell([
59
- 'dumpsys',
60
- 'SurfaceFlinger',
61
- '--display-id',
62
- ]);
63
- /** @type {import('@appium/types').StringRecord<import('./types').Screenshot>} */
64
- const infos = {};
65
- let match;
66
- while ((match = DISPLAY_PATTERN.exec(displaysInfo))) {
67
- infos[match[1]] = /** @type {any} */ ({
68
- id: match[1],
69
- isDefault: match[2] === '0',
70
- name: match[3],
71
- });
72
- }
73
- if (_.isEmpty(infos)) {
74
- this.log.debug(displaysInfo);
75
- throw new Error('Cannot determine the information about connected Android displays');
76
- }
77
- this.log.info(`Parsed Android display infos: ${JSON.stringify(infos)}`);
78
-
79
- /**
80
- * @param {string} dispId
81
- */
82
- const toB64Screenshot = async (dispId) =>
83
- (await /** @type {import('appium-adb').ADB} */ (this.adb).takeScreenshot(dispId)).toString(
84
- 'base64'
85
- );
86
-
87
- // @ts-ignore isNaN works properly here
88
- const displayIdStr = isNaN(displayId) ? null : `${displayId}`;
89
- if (displayIdStr) {
90
- if (!infos[displayIdStr]) {
91
- throw new Error(
92
- `The provided display identifier '${displayId}' is not known. ` +
93
- `Only the following displays have been detected: ${JSON.stringify(infos)}`
94
- );
95
- }
96
- return {
97
- [displayIdStr]: {
98
- ...infos[displayIdStr],
99
- payload: await toB64Screenshot(displayIdStr),
100
- },
101
- };
102
- }
103
-
104
- const allInfos = _.values(infos);
105
- const screenshots = await B.all(allInfos.map(({id}) => toB64Screenshot(id)));
106
- for (const [info, payload] of /** @type {[import('./types').Screenshot, string][]} */ (
107
- _.zip(allInfos, screenshots)
108
- )) {
109
- info.payload = payload;
110
- }
111
- return infos;
112
- }
113
-
114
- /**
115
- * @typedef {import('../driver').AndroidUiautomator2Driver} AndroidUiautomator2Driver
116
- */