Skip to content

Commit

Permalink
Merge branch 'master' into webview-retry-with-reconnect
Browse files Browse the repository at this point in the history
  • Loading branch information
KazuCocoa authored Jul 10, 2024
2 parents 89300ca + 1fd38d7 commit 6e091b8
Show file tree
Hide file tree
Showing 89 changed files with 1,773 additions and 1,472 deletions.
67 changes: 67 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,70 @@
## [7.23.1](https://github.com/appium/appium-xcuitest-driver/compare/v7.23.0...v7.23.1) (2024-07-09)

### Miscellaneous Chores

* Remove extra import ([ab07082](https://github.com/appium/appium-xcuitest-driver/commit/ab070823f7287111a085cacf63ab6d77c2d2f031))

## [7.23.0](https://github.com/appium/appium-xcuitest-driver/compare/v7.22.1...v7.23.0) (2024-07-04)

### Features

* Rewrite py-ios-device client and crash reports logger into typescript ([#2423](https://github.com/appium/appium-xcuitest-driver/issues/2423)) ([8d405e8](https://github.com/appium/appium-xcuitest-driver/commit/8d405e8081eb0c4a09217717eb380ab4076a9736))

## [7.22.1](https://github.com/appium/appium-xcuitest-driver/compare/v7.22.0...v7.22.1) (2024-07-03)

### Miscellaneous Chores

* Simplify subprocess output analysis ([#2422](https://github.com/appium/appium-xcuitest-driver/issues/2422)) ([c6b9be8](https://github.com/appium/appium-xcuitest-driver/commit/c6b9be8d5120b8097880bef49f67dc06a8bc548e))

## [7.22.0](https://github.com/appium/appium-xcuitest-driver/compare/v7.21.2...v7.22.0) (2024-07-02)

### Features

* Update console and network log handlers ([#2421](https://github.com/appium/appium-xcuitest-driver/issues/2421)) ([3c72721](https://github.com/appium/appium-xcuitest-driver/commit/3c727219577c51d941d6fab68feda62eaf7bf774))

## [7.21.2](https://github.com/appium/appium-xcuitest-driver/compare/v7.21.1...v7.21.2) (2024-07-01)

### Miscellaneous Chores

* Rewrite logging-related classes to typescript ([#2420](https://github.com/appium/appium-xcuitest-driver/issues/2420)) ([9789575](https://github.com/appium/appium-xcuitest-driver/commit/97895755c41a3a729a8f4fd972c0f900a41f383a))

## [7.21.1](https://github.com/appium/appium-xcuitest-driver/compare/v7.21.0...v7.21.1) (2024-06-30)

### Miscellaneous Chores

* Streamline logging helpers ([#2419](https://github.com/appium/appium-xcuitest-driver/issues/2419)) ([d469237](https://github.com/appium/appium-xcuitest-driver/commit/d469237304d507feb1f59b07fd6a76d51f63fe19))

## [7.21.0](https://github.com/appium/appium-xcuitest-driver/compare/v7.20.2...v7.21.0) (2024-06-27)

### Features

* Add mobile: wrappers for the clipboard API ([#2418](https://github.com/appium/appium-xcuitest-driver/issues/2418)) ([3b41576](https://github.com/appium/appium-xcuitest-driver/commit/3b41576b5cb51f6b4c296e48c799c069cae50f63))

## [7.20.2](https://github.com/appium/appium-xcuitest-driver/compare/v7.20.1...v7.20.2) (2024-06-27)

### Miscellaneous Chores

* Bump chai and chai-as-promised ([#2414](https://github.com/appium/appium-xcuitest-driver/issues/2414)) ([6ba1b5e](https://github.com/appium/appium-xcuitest-driver/commit/6ba1b5e4ba192da6b8d7a0370cd3fa79947c540e))

## [7.20.1](https://github.com/appium/appium-xcuitest-driver/compare/v7.20.0...v7.20.1) (2024-06-26)

### Bug Fixes

* Apply the default exec timeout if not provided explicitly ([#2416](https://github.com/appium/appium-xcuitest-driver/issues/2416)) ([9a793b1](https://github.com/appium/appium-xcuitest-driver/commit/9a793b10a7cbbe317d6b2f85b25162e64a614dee))
* Respect the remote port capability for real devices ([#2417](https://github.com/appium/appium-xcuitest-driver/issues/2417)) ([f2d80da](https://github.com/appium/appium-xcuitest-driver/commit/f2d80da102b8fb3333b97a768bafe463553704cc))

## [7.20.0](https://github.com/appium/appium-xcuitest-driver/compare/v7.19.0...v7.20.0) (2024-06-25)

### Features

* Introduce the `webScreenshotMode` setting ([#2415](https://github.com/appium/appium-xcuitest-driver/issues/2415)) ([c9d9d44](https://github.com/appium/appium-xcuitest-driver/commit/c9d9d4475bcb8d394ae0ba5f3c0a80bea40d1eed))

## [7.19.0](https://github.com/appium/appium-xcuitest-driver/compare/v7.18.0...v7.19.0) (2024-06-25)

### Features

* take viewport screenshot using safari remote debugger ([#2413](https://github.com/appium/appium-xcuitest-driver/issues/2413)) ([4402c29](https://github.com/appium/appium-xcuitest-driver/commit/4402c294333e6084c854d63b4a8387a3b3cbe9ff))

## [7.18.0](https://github.com/appium/appium-xcuitest-driver/compare/v7.17.6...v7.18.0) (2024-06-20)

### Features
Expand Down
2 changes: 2 additions & 0 deletions docs/reference/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ about capabilities, refer to the [Appium documentation](https://appium.io/docs/e
|`appium:autoWebview`| Move directly into Webview context if available. Default `false`|`true`, `false`|
|`appium:skipTriggerInputEventAfterSendkeys`| If this capability is set to `true`, then whenever you call the Send Keys method in a web context, the driver will not fire an additional `input` event on the input field used for the call. This event, turned on by default, helps in situations where JS frameworks (like React) do not respond to the input events that occur by default when the underlying Selenium atom is executed. Default `false`|`true`, `false`|
|`appium:sendKeyStrategy`| If this capability is set to `oneByOne`, then whenever you call the Send Keys method in a web context, the driver will type each character the given string consists of in serial order to the element. This strategy helps in situations where JS frameworks (like React) update the view for each input. If `appium:skipTriggerInputEventAfterSendkeys` capability is `true`, it will affect every type. For example, when you are going to type the word `appium` with `oneByOne` strategy and `appium:skipTriggerInputEventAfterSendkeys` is enabled, the `appium:skipTriggerInputEventAfterSendkeys` option affects each typing action: `a`, `p`, `p`,`i`, `u` and `m`. Suppose any other value or no value has been provided to the `appium:sendKeyStrategy` capability. In that case, the driver types the given string in the destination input element. `appium` Send Keys input types `appium` if `oneByOne` was not set. |`oneByOne`|
|`appium:showSafariConsoleLog`| Adds Safari JavaScript console events to Appium server logs (`true`) and writes fully serialized events into the `safariConsole` logs bucket (both `true` and `false`). If unset then no console events are being collected, which helps to save CPU and memory resources. Before the driver version 7.22 the default behavior was to always collect console logs if the capability is not set. Setting the value to `false` mimics that legacy behavior. |`true`, `false`|
|`appium:showSafariNetworkLog`| Adds Safari network events to Appium server logs (`true`) and writes fully serialized events into the `safariNetwork` logs bucket (both `true` and `false`). If unset then no network events are being collected, which helps to save CPU and memory resources. Before the driver version 7.22 the default behavior was to always collect network logs if the capability is not set. Setting the value to `false` mimics that legacy behavior. |`true`, `false`|

### Other

Expand Down
26 changes: 26 additions & 0 deletions docs/reference/execute-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,32 @@ Name | Type | Required | Description | Example
--- | --- | --- | --- | ---
style | string | yes | Either `light` or `dark` | dark

### mobile: getClipboard

Gets the content of the primary clipboard on the device under test.

#### Arguments

Name | Type | Required | Description | Example
--- | --- | --- | --- | ---
contentType | string | no | `plaintext` (default), `image` or `url` | image

#### Returned Result

The actual clipboard content encoded into base64 string.
An empty string is returned if the clipboard contains no data.

### mobile: setClipboard

Sets the primary clipboard's content on the device under test.

#### Arguments

Name | Type | Required | Description | Example
--- | --- | --- | --- | ---
content| string | yes | The content to be set as base64-encoded string. | QXBwaXVt
contentType | string | no | `plaintext` (default), `image` or `url` | image

### mobile: siriCommand

Presents the Siri UI, if it is not currently active, and accepts a string which is then processed as if it were recognized speech. Check the documentation on [activateWithVoiceRecognitionText](https://developer.apple.com/documentation/xctest/xcuisiriservice/2852140-activatewithvoicerecognitiontext?language=objc) XCTest method for more details.
Expand Down
1 change: 1 addition & 0 deletions docs/reference/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ Along with the common settings, the following driver-specific settings are avail
| `pageSourceExcludedAttributes` | `string` | One or more comma-separated attribute names to be excluded from the XML output. It might be sometimes helpful to exclude, for example, the `visible` attribute, to significantly speed-up page source retrieval. This does not affect the XML output when `useJSONSource` is enabled. Defaults to an empty string. Example: `"visible,accessible"` |
| `maxTypingFrequency` | `int` | Maximum frequency of keystrokes for typing and clear. If your tests are failing because of typing errors, you may want to adjust this. Defaults to `60` keystrokes per minute. |
| `respectSystemAlerts` | `boolean` | Currently we detect the app under test as active if XCTest returns XCUIApplicationStateRunningForeground state for it. In case the app under test is covered by a system alert from the Springboard app this approach might be confusing as we cannot interact with it unless an alert is properly handled. If this setting is set to true (by default it is false) then it forces WDA to verify the presence of alerts shown by Springboard and return the latter while performing the automated app detection. It affects the performance of active app detection, but might be more convenient for writing test scripts (e.g. eliminates the need of proactive switching between system and custom apps). Also, this behavior emulates the legacy active application detection logic before version 6 of the driver. |
| `webScreenshotMode` | `native` or `page` or `viewport` | Defines the screenshoting logic if the current context is set to a web one. The default value is `native`, which makes the driver to take screenshots from WDA, e.g. the whole device screen including status bars. The `page` mode tries to retrieve the screenshot of the whole active web page, while the `viewport` one only retrieves a shot of the visible viewport. |
2 changes: 1 addition & 1 deletion lib/commands/certificate.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import path from 'path';
import http from 'http';
import {exec} from 'teen_process';
import {findAPortNotInUse, checkPortStatus} from 'portscanner';
import Pyidevice from '../py-ios-device-client';
import {Pyidevice} from '../real-device-clients/py-ios-device-client';
import {errors} from 'appium/driver';

const CONFIG_EXTENSION = 'mobileconfig';
Expand Down
9 changes: 6 additions & 3 deletions lib/commands/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -558,20 +558,23 @@ const commands = {
// attempt to start performance logging, if requested
if (this.opts.enablePerformanceLogging && this.remote) {
this.log.debug(`Starting performance log on '${this.curContext}'`);
this.logs.performance = new IOSPerformanceLog(this.remote);
this.logs.performance = new IOSPerformanceLog({
remoteDebugger: this.remote,
log: this.log,
});
await this.logs.performance.startCapture();
}

// start safari logging if the logs handlers are active
if (name && name !== NATIVE_WIN && this.logs) {
if (this.logs.safariConsole) {
await this.remote.startConsole(
this.logs.safariConsole.addLogLine.bind(this.logs.safariConsole),
this.logs.safariConsole.onConsoleLogEvent.bind(this.logs.safariConsole),
);
}
if (this.logs.safariNetwork) {
await this.remote.startNetwork(
this.logs.safariNetwork.addLogLine.bind(this.logs.safariNetwork),
this.logs.safariNetwork.onNetworkEvent.bind(this.logs.safariNetwork),
);
}
}
Expand Down
72 changes: 42 additions & 30 deletions lib/commands/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import log from '../logger';
import WebSocket from 'ws';
import SafariConsoleLog from '../device-log/safari-console-log';
import SafariNetworkLog from '../device-log/safari-network-log';
import { toLogEntry } from '../device-log/helpers';

/**
* Determines the websocket endpoint based on the `sessionId`
Expand Down Expand Up @@ -47,26 +48,23 @@ const SUPPORTED_LOG_TYPES = {
server: {
description: 'Appium server logs',
/**
* @returns {AppiumServerLogEntry[]}
* @returns {import('./types').LogEntry[]}
*/
getter: (self) => {
self.assertFeatureEnabled(GET_SERVER_LOGS_FEATURE);
return log.unwrap().record.map((x) => ({
timestamp: /** @type {any} */ (x).timestamp ?? Date.now(),
level: 'ALL',
message: _.isEmpty(x.prefix) ? x.message : `[${x.prefix}] ${x.message}`,
}));
return log.unwrap().record.map((x) => toLogEntry(
_.isEmpty(x.prefix) ? x.message : `[${x.prefix}] ${x.message}`,
/** @type {any} */ (x).timestamp ?? Date.now()
));
},
},
};

/**
* Log entry in the array returned by `getLogs('server')`
* @typedef AppiumServerLogEntry
* @property {number} timestamp
* @property {'ALL'} level
* @property {string} message
*/
const LOG_NAMES_TO_CAPABILITY_NAMES_MAP = {
safariConsole: 'showSafariConsoleLog',
safariNetwork: 'showSafariNetworkLog',
enablePerformanceLogging: 'enablePerformanceLogging',
};

export default {
supportedLogTypes: SUPPORTED_LOG_TYPES,
Expand All @@ -85,11 +83,18 @@ export default {

// If logs captured successfully send response with data, else send error
const logObject = logsContainer[logType];
const logs = logObject ? await logObject.getLogs() : null;
if (logs) {
return logs;
if (logObject) {
return await logObject.getLogs();
}
throw new Error(`No logs of type '${String(logType)}' found.`);
if (logType in LOG_NAMES_TO_CAPABILITY_NAMES_MAP) {
throw new Error(
`${logType} logs are not enabled. Make sure you've set a proper value ` +
`to the 'appium:${LOG_NAMES_TO_CAPABILITY_NAMES_MAP[logType]}' capability.`
);
}
throw new Error(
`No logs of type '${logType}' found. Supported log types are: ${_.keys(SUPPORTED_LOG_TYPES)}.`
);
},

/**
Expand All @@ -103,25 +108,34 @@ export default {
}
if (_.isUndefined(this.logs.syslog)) {
this.logs.crashlog = new IOSCrashLog({
sim: this.device,
sim: /** @type {import('appium-ios-simulator').Simulator} */ (this.device),
udid: this.isRealDevice() ? this.opts.udid : undefined,
log: this.log,
});

if (this.isRealDevice()) {
this.logs.syslog = new IOSDeviceLog({
this.logs.syslog = this.isRealDevice()
? new IOSDeviceLog({
udid: this.opts.udid,
showLogs: this.opts.showIOSLog,
});
} else {
this.logs.syslog = new IOSSimulatorLog({
sim: this.device,
log: this.log,
})
: new IOSSimulatorLog({
sim: /** @type {import('appium-ios-simulator').Simulator} */ (this.device),
showLogs: this.opts.showIOSLog,
xcodeVersion: this.xcodeVersion,
iosSimulatorLogsPredicate: this.opts.iosSimulatorLogsPredicate,
log: this.log,
});
if (_.isBoolean(this.opts.showSafariConsoleLog)) {
this.logs.safariConsole = new SafariConsoleLog({
showLogs: this.opts.showSafariConsoleLog,
log: this.log,
});
}
if (_.isBoolean(this.opts.showSafariNetworkLog)) {
this.logs.safariNetwork = new SafariNetworkLog({
showLogs: this.opts.showSafariNetworkLog,
log: this.log,
});
}
this.logs.safariConsole = new SafariConsoleLog(!!this.opts.showSafariConsoleLog);
this.logs.safariNetwork = new SafariNetworkLog(!!this.opts.showSafariNetworkLog);
}

let didStartSyslog = false;
Expand All @@ -137,8 +151,6 @@ export default {
}
})(),
this.logs.crashlog.startCapture(),
this.logs.safariConsole.startCapture(),
this.logs.safariNetwork.startCapture(),
];
await B.all(promises);

Expand Down
2 changes: 1 addition & 1 deletion lib/commands/memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default {

const device = /** @type {import('../real-device').RealDevice} */ (this.device);

/** @type {import('../devicectl').AppInfo[]} */
/** @type {import('../real-device-clients/devicectl').AppInfo[]} */
const appInfos = await device.devicectl.listApps(bundleId);
if (_.isEmpty(appInfos)) {
throw new errors.InvalidArgumentError(
Expand Down
11 changes: 3 additions & 8 deletions lib/commands/pcap.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import Pyidevice from '../py-ios-device-client';
import {fs, tempDir, logger, util} from 'appium/support';
import { Pyidevice } from '../real-device-clients/py-ios-device-client';
import {fs, tempDir, util} from 'appium/support';
import {encodeBase64OrUpload} from '../utils';
import {errors} from 'appium/driver';

const MAX_CAPTURE_TIME_SEC = 60 * 60 * 12;
const DEFAULT_EXT = '.pcap';
const pcapLogger = logger.getLogger('pcapd');

export class TrafficCapture {
/** @type {import('teen_process').SubProcess|null} */
Expand All @@ -21,11 +20,7 @@ export class TrafficCapture {
this.mainProcess = /** @type {import('teen_process').SubProcess} */ (
await new Pyidevice(this.udid).collectPcap(this.resultPath)
);
this.mainProcess.on('output', (stdout, stderr) => {
if (stderr) {
pcapLogger.info(`${stderr}`);
}
});
this.mainProcess.on('line-stderr', (line) => this.log.info(`[Pcap] ${line}`));
this.log.info(
`Starting network traffic capture session on the device '${this.udid}'. ` +
`Will timeout in ${timeoutSeconds}s`,
Expand Down
8 changes: 3 additions & 5 deletions lib/commands/performance.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,9 @@ export class PerfRecorder {
this._process = new SubProcess(fullCmd[0], fullCmd.slice(1));
this._archivePromise = null;
this._logger.debug(`Starting performance recording: ${util.quote(fullCmd)}`);
this._process.on('output', (stdout, stderr) => {
if (_.trim(stdout || stderr)) {
this._logger.debug(`[${toolName}] ${stdout || stderr}`);
}
});
for (const streamName of ['stdout', 'stderr']) {
this._process.on(`line-${streamName}`, (line) => this._logger.debug(`[${toolName}] ${line}`));
}
this._process.once('exit', async (code, signal) => {
this._process = null;
if (code === 0) {
Expand Down
15 changes: 7 additions & 8 deletions lib/commands/recordscreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const QUALITY_MAPPING = {
high: 75,
photo: 100,
};
const CAPTURE_START_MARKER = /^\s*frame=/;

export class ScreenRecorder {
constructor(udid, log, videoPath, opts = {}) {
Expand Down Expand Up @@ -102,15 +103,13 @@ export class ScreenRecorder {

this.mainProcess = new SubProcess(FFMPEG_BINARY, args);
let isCaptureStarted = false;
this.mainProcess.on('output', (stdout, stderr) => {
if (stderr) {
if (stderr.trim().startsWith('frame=')) {
if (!isCaptureStarted) {
isCaptureStarted = true;
}
} else {
ffmpegLogger.info(`${stderr}`);
this.mainProcess.on('line-stderr', (line) => {
if (CAPTURE_START_MARKER.test(line)) {
if (!isCaptureStarted) {
isCaptureStarted = true;
}
} else {
ffmpegLogger.info(line);
}
});
await this.mainProcess.start(0);
Expand Down
25 changes: 25 additions & 0 deletions lib/commands/screenshots.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@ export default {
* @returns {Promise<string>}
*/
async getScreenshot() {
if (this.isWebContext()) {
const webScreenshotMode = (await this.settings.getSettings()).webScreenshotMode;
switch (_.toLower(webScreenshotMode)) {
case 'page':
case 'viewport':
return await this.remote.captureScreenshot({
coordinateSystem: _.capitalize(webScreenshotMode),
});
case 'native':
case undefined:
case null:
break;
default:
this.log.warn(
`The webScreenshotMode setting value '${webScreenshotMode}' is not known. ` +
`Supported values are: page, viewport and native. Falling back to the native mode.`
);
break;
}
}

const getScreenshotFromWDA = async () => {
this.log.debug(`Taking screenshot with WDA`);
const data = await this.proxyCommand('/screenshot', 'GET');
Expand Down Expand Up @@ -72,6 +93,10 @@ export default {
* @this {XCUITestDriver}
*/
async getViewportScreenshot() {
if (this.isWebContext()) {
return await this.remote.captureScreenshot();
}

let statusBarHeight = await this.getStatusBarHeight();
const screenshot = await this.getScreenshot();

Expand Down
Loading

0 comments on commit 6e091b8

Please sign in to comment.