diff --git a/package-lock.json b/package-lock.json index ff6b30e59da..bc6cdac647a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,7 +63,7 @@ "through2": "4.0.2", "ts-node": "10.9.2", "tsd": "0.30.7", - "typescript": "5.2.2", + "typescript": "5.4.2", "validate.js": "0.13.1", "webdriverio": "8.33.1", "ws": "8.16.0", @@ -19604,9 +19604,9 @@ "license": "MIT" }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -21851,7 +21851,7 @@ "source-map-support": "0.5.21", "teen_process": "2.1.1", "type-fest": "4.10.1", - "typescript": "5.2.2", + "typescript": "5.4.2", "yaml": "2.4.1", "yargs": "17.7.2", "yargs-parser": "21.1.1" @@ -23022,7 +23022,7 @@ "source-map-support": "0.5.21", "teen_process": "2.1.1", "type-fest": "4.10.1", - "typescript": "5.2.2", + "typescript": "5.4.2", "yaml": "2.4.1", "yargs": "17.7.2", "yargs-parser": "21.1.1" @@ -37123,9 +37123,9 @@ "dev": true }, "typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==" + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==" }, "ua-parser-js": { "version": "1.0.33", diff --git a/package.json b/package.json index 77c449a7d88..79c2f429862 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "through2": "4.0.2", "ts-node": "10.9.2", "tsd": "0.30.7", - "typescript": "5.2.2", + "typescript": "5.4.2", "validate.js": "0.13.1", "webdriverio": "8.33.1", "ws": "8.16.0", diff --git a/packages/appium/lib/appium.js b/packages/appium/lib/appium.js index acc058fa148..0a3995df651 100644 --- a/packages/appium/lib/appium.js +++ b/packages/appium/lib/appium.js @@ -1085,7 +1085,7 @@ class AppiumDriver extends DriverCore { // is to do the proxy and retrieve the result internally so it can be passed to the plugin // in case it calls 'await next()'. This requires that the driver have defined // 'proxyCommand' and not just 'proxyReqRes'. - if (!dstSession.proxyCommand) { + if (!dstSession?.proxyCommand) { throw new NoDriverProxyCommandError(); } return await dstSession.proxyCommand( @@ -1106,7 +1106,7 @@ class AppiumDriver extends DriverCore { } // here we know that we are executing a session command, and have a valid session driver - return await dstSession.executeCommand(cmd, ...args); + return await (/** @type {any} */ (dstSession)).executeCommand(cmd, ...args); }; // now take our default behavior, wrap it with any number of plugin behaviors, and run it diff --git a/packages/base-driver/lib/basedriver/capabilities.js b/packages/base-driver/lib/basedriver/capabilities.ts similarity index 64% rename from packages/base-driver/lib/basedriver/capabilities.js rename to packages/base-driver/lib/basedriver/capabilities.ts index 12c22b6179b..7ccf8eebaeb 100644 --- a/packages/base-driver/lib/basedriver/capabilities.js +++ b/packages/base-driver/lib/basedriver/capabilities.ts @@ -1,34 +1,54 @@ -// @ts-check - +import type { + Constraints, + NSCapabilities, + Capabilities, + W3CCapabilities, + StandardCapabilities, +} from '@appium/types'; +import type { + StringKeyOf, + MergeExclusive, +} from 'type-fest'; import _ from 'lodash'; import {validator} from './desired-caps'; import {util} from '@appium/support'; import log from './logger'; import {errors} from '../protocol/errors'; -const APPIUM_VENDOR_PREFIX = 'appium:'; -const PREFIXED_APPIUM_OPTS_CAP = `${APPIUM_VENDOR_PREFIX}options`; +export const APPIUM_VENDOR_PREFIX = 'appium:'; +export const PREFIXED_APPIUM_OPTS_CAP = `${APPIUM_VENDOR_PREFIX}options`; + +export type ParsedCaps = { + allFirstMatchCaps: NSCapabilities[]; + validatedFirstMatchCaps: Capabilities[]; + requiredCaps: NSCapabilities; + matchedCaps: Capabilities | null; + validationErrors: string[]; +}; +export type ValidateCapsOpts = { + /** if true, skip the presence constraint */ + skipPresenceConstraint?: boolean | undefined; +} /** * Takes primary caps object and merges it into a secondary caps object. - * @template {Constraints} T - * @template {Constraints} U - * @template {Capabilities} Primary - * @template {Capabilities} Secondary - * @param {Primary} [primary] - * @param {Secondary} [secondary] - * @returns {MergeExclusive} + * * @see https://www.w3.org/TR/webdriver/#dfn-merging-capabilities) */ -function mergeCaps( - primary = /** @type {Primary} */ ({}), - secondary = /** @type {Secondary} */ ({}) -) { - let result = /** @type {MergeExclusive} */ ({ +export function mergeCaps< + T extends Constraints, + U extends Constraints, + Primary extends Capabilities, + Secondary extends Capabilities +>( + primary: Primary | undefined = {} as Primary, + secondary: Secondary | undefined = {} as Secondary +): MergeExclusive { + const result = ({ ...primary, - }); + }) as MergeExclusive; - for (let [name, value] of Object.entries(secondary)) { + for (const [name, value] of Object.entries(secondary)) { // Overwriting is not allowed. Primary and secondary must have different properties (w3c rule 4.4) if (!_.isUndefined(primary[name])) { throw new errors.InvalidArgumentError( @@ -37,7 +57,7 @@ function mergeCaps( )}) and secondary (${JSON.stringify(secondary)}) object` ); } - result[/** @type {keyof typeof result} */ (name)] = value; + result[name as keyof typeof result] = value; } return result; @@ -45,21 +65,20 @@ function mergeCaps( /** * Validates caps against a set of constraints - * @template {Constraints} C - * @param {Capabilities} caps - * @param {C} [constraints] - * @param {ValidateCapsOpts} [opts] - * @returns {Capabilities} */ -function validateCaps(caps, constraints = /** @type {C} */ ({}), opts = {}) { - let {skipPresenceConstraint} = opts; +export function validateCaps( + caps: Capabilities, + constraints: C | undefined = {} as C, + opts: ValidateCapsOpts | undefined = {} +): Capabilities { + const {skipPresenceConstraint} = opts; if (!_.isPlainObject(caps)) { throw new errors.InvalidArgumentError(`must be a JSON object`); } // Remove the 'presence' constraint if we're not checking for it - constraints = /** @type {C} */ ( + constraints = ( _.mapValues( constraints, skipPresenceConstraint @@ -73,16 +92,16 @@ function validateCaps(caps, constraints = /** @type {C} */ ({}), opts = {}) { return constraint; } ) - ); + ) as C; const validationErrors = validator.validate(_.pickBy(caps, util.hasValue), constraints, { fullMessages: false, }); if (validationErrors) { - let message = []; - for (let [attribute, reasons] of _.toPairs(validationErrors)) { - for (let reason of reasons) { + const message: string[] = []; + for (const [attribute, reasons] of _.toPairs(validationErrors)) { + for (const reason of (reasons as string[])) { message.push(`'${attribute}' ${reason}`); } } @@ -99,7 +118,7 @@ function validateCaps(caps, constraints = /** @type {C} */ ({}), opts = {}) { */ export const STANDARD_CAPS = Object.freeze( new Set( - /** @type {StringKeyOf[]} */ ([ + ([ 'browserName', 'browserVersion', 'platformName', @@ -110,29 +129,22 @@ export const STANDARD_CAPS = Object.freeze( 'timeouts', 'unhandledPromptBehavior', 'webSocketUrl', - ]) + ]) as StringKeyOf[] ) ); const STANDARD_CAPS_LOWER = new Set([...STANDARD_CAPS].map((cap) => cap.toLowerCase())); -/** - * @param {string} cap - * @returns {boolean} - */ -function isStandardCap(cap) { +export function isStandardCap(cap: string): boolean { return STANDARD_CAPS_LOWER.has(cap.toLowerCase()); } /** * If the 'appium:' prefix was provided and it's a valid capability, strip out the prefix - * @template {Constraints} C - * @param {NSCapabilities} caps * @see https://www.w3.org/TR/webdriver/#dfn-extension-capabilities * @internal - * @returns {Capabilities} */ -function stripAppiumPrefixes(caps) { +export function stripAppiumPrefixes(caps: NSCapabilities): Capabilities { // split into prefixed and non-prefixed. // non-prefixed should be standard caps at this point const [prefixedCaps, nonPrefixedCaps] = _.partition(_.keys(caps), (cap) => @@ -140,14 +152,12 @@ function stripAppiumPrefixes(caps) { ); // initialize this with the k/v pairs of the non-prefixed caps - let strippedCaps = /** @type {Capabilities} */ (_.pick(caps, nonPrefixedCaps)); - const badPrefixedCaps = []; + const strippedCaps = (_.pick(caps, nonPrefixedCaps)) as Capabilities; + const badPrefixedCaps: string[] = []; // Strip out the 'appium:' prefix - for (let prefixedCap of prefixedCaps) { - const strippedCapName = /** @type {StringKeyOf>} */ ( - prefixedCap.substring(APPIUM_VENDOR_PREFIX.length) - ); + for (const prefixedCap of prefixedCaps) { + const strippedCapName = prefixedCap.substring(APPIUM_VENDOR_PREFIX.length) as StringKeyOf>; // If it's standard capability that was prefixed, add it to an array of incorrectly prefixed capabilities if (isStandardCap(strippedCapName)) { @@ -178,10 +188,13 @@ function stripAppiumPrefixes(caps) { /** * Get an array of all the unprefixed caps that are being used in 'alwaysMatch' and all of the 'firstMatch' object - * @template {Constraints} C - * @param {W3CCapabilities} caps A capabilities object */ -function findNonPrefixedCaps({alwaysMatch = {}, firstMatch = []}) { +export function findNonPrefixedCaps( + { + alwaysMatch = {}, + firstMatch = [] + }: W3CCapabilities +): string[] { return _.chain([alwaysMatch, ...firstMatch]) .reduce( (unprefixedCaps, caps) => [ @@ -194,27 +207,15 @@ function findNonPrefixedCaps({alwaysMatch = {}, firstMatch = []}) { .value(); } -/** - * Returned by {@linkcode parseCaps} - * @template {Constraints} C - * @typedef ParsedCaps - * @property {NSCapabilities[]} allFirstMatchCaps - * @property {Capabilities[]} validatedFirstMatchCaps - * @property {NSCapabilities} requiredCaps - * @property {Capabilities|null} matchedCaps - * @property {string[]} validationErrors - */ - /** * Parse capabilities - * @template {Constraints} C - * @param {W3CCapabilities} caps - * @param {C} [constraints] - * @param {boolean} [shouldValidateCaps] * @see https://www.w3.org/TR/webdriver/#processing-capabilities - * @returns {ParsedCaps} */ -function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps = true) { +export function parseCaps( + caps: W3CCapabilities, + constraints: C | undefined = {} as C, + shouldValidateCaps: boolean | undefined = true +): ParsedCaps { // If capabilities request is not an object, return error (#1.1) if (!_.isPlainObject(caps)) { throw new errors.InvalidArgumentError( @@ -224,9 +225,9 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps // Let 'requiredCaps' be property named 'alwaysMatch' from capabilities request (#2) // and 'allFirstMatchCaps' be property named 'firstMatch' from capabilities request (#3) - let { - alwaysMatch: requiredCaps = /** @type {NSCapabilities} */ ({}), // If 'requiredCaps' is undefined, set it to an empty JSON object (#2.1) - firstMatch: allFirstMatchCaps = /** @type {NSCapabilities[]} */ ([{}]), // If 'firstMatch' is undefined set it to a singleton list with one empty object (#3.1) + const { + alwaysMatch: requiredCaps = {} as NSCapabilities, // If 'requiredCaps' is undefined, set it to an empty JSON object (#2.1) + firstMatch: allFirstMatchCaps = [{}] as NSCapabilities[], // If 'firstMatch' is undefined set it to a singleton list with one empty object (#3.1) } = caps; // Reject 'firstMatch' argument if it's not an array (#3.2) @@ -247,7 +248,7 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps } // Check for non-prefixed, non-standard capabilities and log warnings if they are found - let nonPrefixedCaps = findNonPrefixedCaps(caps); + const nonPrefixedCaps = findNonPrefixedCaps(caps); if (!_.isEmpty(nonPrefixedCaps)) { throw new errors.InvalidArgumentError( `All non-standard capabilities should have a vendor prefix. The following capabilities did not have one: ${nonPrefixedCaps}` @@ -256,8 +257,7 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps // Strip out the 'appium:' prefix from all let strippedRequiredCaps = stripAppiumPrefixes(requiredCaps); - /** @type {Capabilities[]} */ - let strippedAllFirstMatchCaps = allFirstMatchCaps.map(stripAppiumPrefixes); + const strippedAllFirstMatchCaps: Capabilities[] = allFirstMatchCaps.map(stripAppiumPrefixes); // Validate the requiredCaps. But don't validate 'presence' because if that constraint fails on 'alwaysMatch' it could still pass on one of the 'firstMatch' keys if (shouldValidateCaps) { @@ -267,14 +267,11 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps } // Remove the 'presence' constraint for any keys that are already present in 'requiredCaps' // since we know that this constraint has already passed - const filteredConstraints = /** @type {C} */ ( - _.omitBy(constraints, (_, key) => key in strippedRequiredCaps) - ); + const filteredConstraints = _.omitBy(constraints, (_, key) => key in strippedRequiredCaps) as C; // Validate all of the first match capabilities and return an array with only the valid caps (see spec #5) - /** @type {string[]} */ - let validationErrors = []; - let validatedFirstMatchCaps = _.compact( + const validationErrors: string[] = []; + const validatedFirstMatchCaps = _.compact( strippedAllFirstMatchCaps.map((firstMatchCaps) => { try { // Validate firstMatch caps @@ -285,17 +282,16 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps validationErrors.push(e.message); } }) - ); + ) as Capabilities[]; /** * Try to merge requiredCaps with first match capabilities, break once it finds its first match * (see spec #6) - * @type {ParsedCaps['matchedCaps']} */ - let matchedCaps = null; - for (let firstMatchCaps of validatedFirstMatchCaps) { + let matchedCaps: ParsedCaps['matchedCaps'] = null; + for (const firstMatchCaps of validatedFirstMatchCaps) { try { - matchedCaps = mergeCaps(strippedRequiredCaps, firstMatchCaps); + matchedCaps = mergeCaps(strippedRequiredCaps, firstMatchCaps) as ParsedCaps['matchedCaps']; if (matchedCaps) { break; } @@ -317,18 +313,15 @@ function parseCaps(caps, constraints = /** @type {C} */ ({}), shouldValidateCaps /** * Calls parseCaps and just returns the matchedCaps variable - * @template {Constraints} C - * @template {W3CCapabilities} W3CCaps - * @param {W3CCaps} w3cCaps - * @param {C} [constraints] - * @param {boolean} [shouldValidateCaps] - * @returns {Capabilities} */ -function processCapabilities( - w3cCaps, - constraints = /** @type {C} */ ({}), - shouldValidateCaps = true -) { +export function processCapabilities< + C extends Constraints, + W3CCaps extends W3CCapabilities +>( + w3cCaps: W3CCaps, + constraints: C | undefined = {} as C, + shouldValidateCaps: boolean | undefined = true +): Capabilities { const {matchedCaps, validationErrors} = parseCaps(w3cCaps, constraints, shouldValidateCaps); // If we found an error throw an exception @@ -346,18 +339,14 @@ function processCapabilities( } } - return /** @type {Capabilities} */ (matchedCaps ?? {}); + return (matchedCaps ?? {}) as Capabilities; } /** * Return a copy of a "bare" (single-level, non-W3C) capabilities object which has taken everything * within the 'appium:options' capability and promoted it to the top level. - * - * @template {Constraints} C - * @param {NSCapabilities} obj - * @return {NSCapabilities} the capabilities with 'options' promoted if necessary */ -function promoteAppiumOptionsForObject(obj) { +export function promoteAppiumOptionsForObject(obj: NSCapabilities): NSCapabilities { const appiumOptions = obj[PREFIXED_APPIUM_OPTS_CAP]; if (!appiumOptions) { return obj; @@ -379,8 +368,8 @@ function promoteAppiumOptionsForObject(obj) { /** * @param {string} capName */ - const shouldAddVendorPrefix = (capName) => !capName.startsWith(APPIUM_VENDOR_PREFIX); - const verifyIfAcceptable = (/** @type {string} */ capName) => { + const shouldAddVendorPrefix = (capName: string) => !capName.startsWith(APPIUM_VENDOR_PREFIX); + const verifyIfAcceptable = (capName: string) => { if (!_.isString(capName)) { throw new errors.SessionNotCreatedError( `Capability names in ${PREFIXED_APPIUM_OPTS_CAP} must be strings. '${capName}' is unexpected` @@ -394,8 +383,8 @@ function promoteAppiumOptionsForObject(obj) { return capName; }; const preprocessedOptions = _(appiumOptions) - .mapKeys((value, /** @type {string} */ key) => verifyIfAcceptable(key)) - .mapKeys((value, key) => (shouldAddVendorPrefix(key) ? `${APPIUM_VENDOR_PREFIX}${key}` : key)) + .mapKeys((value, key: string) => verifyIfAcceptable(key)) + .mapKeys((value, key: string) => (shouldAddVendorPrefix(key) ? `${APPIUM_VENDOR_PREFIX}${key}` : key)) .value(); // warn if we are going to overwrite any keys on the base caps object const overwrittenKeys = _.intersection(Object.keys(obj), Object.keys(preprocessedOptions)); @@ -406,7 +395,7 @@ function promoteAppiumOptionsForObject(obj) { ); } return _.cloneDeep({ - .../** @type {NSCapabilities} */ (_.omit(obj, PREFIXED_APPIUM_OPTS_CAP)), + ..._.omit(obj, PREFIXED_APPIUM_OPTS_CAP) as NSCapabilities, ...preprocessedOptions, }); } @@ -414,13 +403,9 @@ function promoteAppiumOptionsForObject(obj) { /** * Return a copy of a capabilities object which has taken everything within the 'options' * capability and promoted it to the top level. - * - * @template {Constraints} C - * @param {W3CCapabilities} originalCaps - * @return {W3CCapabilities} the capabilities with 'options' promoted if necessary */ -function promoteAppiumOptions(originalCaps) { - const result = {}; +export function promoteAppiumOptions(originalCaps: W3CCapabilities): W3CCapabilities { + const result = {} as W3CCapabilities; const {alwaysMatch, firstMatch} = originalCaps; if (_.isPlainObject(alwaysMatch)) { result.alwaysMatch = promoteAppiumOptionsForObject(alwaysMatch); @@ -434,59 +419,3 @@ function promoteAppiumOptions(originalCaps) { } return result; } - -export { - parseCaps, - processCapabilities, - validateCaps, - mergeCaps, - APPIUM_VENDOR_PREFIX, - findNonPrefixedCaps, - isStandardCap, - stripAppiumPrefixes, - promoteAppiumOptions, - promoteAppiumOptionsForObject, - PREFIXED_APPIUM_OPTS_CAP, -}; - -/** - * @typedef {import('@appium/types').Constraints} Constraints - * @typedef {import('@appium/types').Constraint} Constraint - * @typedef {import('@appium/types').StringRecord} StringRecord - * @typedef {import('@appium/types').BaseDriverCapConstraints} BaseDriverCapConstraints - */ - -/** - * @template {Constraints} C - * @typedef {import('@appium/types').ConstraintsToCaps} ConstraintsToCaps - */ - -/** - * @typedef ValidateCapsOpts - * @property {boolean} [skipPresenceConstraint] - if true, skip the presence constraint - */ - -/** - * @template {Constraints} C - * @typedef {import('@appium/types').NSCapabilities} NSCapabilities - */ - -/** - * @template {Constraints} C - * @typedef {import('@appium/types').Capabilities} Capabilities - */ - -/** - * @template {Constraints} C - * @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities - */ - -/** - * @template T - * @typedef {import('type-fest').StringKeyOf} StringKeyOf - */ - -/** - * @template T,U - * @typedef {import('type-fest').MergeExclusive} MergeExclusive - */ diff --git a/packages/docutils/package.json b/packages/docutils/package.json index dae6aa408d0..6ac256e289f 100644 --- a/packages/docutils/package.json +++ b/packages/docutils/package.json @@ -64,7 +64,7 @@ "source-map-support": "0.5.21", "teen_process": "2.1.1", "type-fest": "4.10.1", - "typescript": "5.2.2", + "typescript": "5.4.2", "yaml": "2.4.1", "yargs": "17.7.2", "yargs-parser": "21.1.1" diff --git a/packages/plugin-test-support/lib/harness.js b/packages/plugin-test-support/lib/harness.js index 58989b4fd21..8a16cbcdaa1 100644 --- a/packages/plugin-test-support/lib/harness.js +++ b/packages/plugin-test-support/lib/harness.js @@ -111,7 +111,7 @@ export function pluginE2EHarness(opts) { console.log(`${info} Will use port ${port} for Appium server`); this.port = port; - /** @type {import('appium').Args} */ + /** @type {import('appium/types').Args} */ const args = { port, address: host, @@ -141,7 +141,7 @@ export function pluginE2EHarness(opts) { * @property {string} [appiumHome] - Path to Appium home directory * @property {Mocha.before} before - Mocha "before all" hook function * @property {Mocha.after} after - Mocha "after all" hook function - * @property {Partial} [serverArgs] - Arguments to pass to Appium server + * @property {Partial} [serverArgs] - Arguments to pass to Appium server * @property {import('appium/types').InstallType & string} driverSource - Source of driver to install * @property {string} [driverPackage] - Package name of driver to install * @property {string} driverName - Name of driver to install