UNPKG

detox

Version:

E2E tests and automation for mobile

850 lines (680 loc) 29.4 kB
// @ts-nocheck const path = require('path'); const fs = require('fs-extra'); const _ = require('lodash'); const tempfile = require('tempfile'); const { assertTraceDescription, assertEnum, assertNormalized } = require('../utils/assertArgument'); const { removeMilliseconds } = require('../utils/dateUtils'); const { actionDescription, expectDescription } = require('../utils/invocationTraceDescriptions'); const { isRegExp } = require('../utils/isRegExp'); const log = require('../utils/logger').child({ cat: 'ws-client, ws' }); const mapLongPressArguments = require('../utils/mapLongPressArguments'); const traceInvocationCall = require('../utils/traceInvocationCall').bind(null, log); const { systemElement, systemMatcher, systemExpect, isSystemElement } = require('./system'); const { webElement, webMatcher, webExpect, isWebElement } = require('./web'); const assertDirection = assertEnum(['left', 'right', 'up', 'down']); const assertSpeed = assertEnum(['fast', 'slow']); class Expect { constructor(invocationManager, element) { this._invocationManager = invocationManager; this.element = element; this.modifiers = []; } get not() { this.modifiers.push('not'); return this; } toBeVisible(percent) { if (percent !== undefined && (!Number.isSafeInteger(percent) || percent < 1 || percent > 100)) { throw new Error('`percent` must be an integer between 1 and 100, but got ' + (percent + (' (' + (typeof percent + ')')))); } const traceDescription = expectDescription.toBeVisible(percent); return this.expect('toBeVisible', traceDescription, percent); } toBeNotVisible() { return this.not.toBeVisible(); } toBeFocused() { const traceDescription = expectDescription.toBeFocused(); return this.expect('toBeFocused', traceDescription); } toBeNotFocused() { return this.not.toBeFocused(); } toExist() { const traceDescription = expectDescription.toExist(); return this.expect('toExist', traceDescription); } toNotExist() { return this.not.toExist(); } toHaveText(text) { const traceDescription = expectDescription.toHaveText(text); return this.expect('toHaveText', traceDescription, text); } toNotHaveText(text) { return this.not.toHaveText(text); } toHaveLabel(label) { const traceDescription = expectDescription.toHaveLabel(label); return this.expect('toHaveLabel', traceDescription, label); } toNotHaveLabel(label) { return this.not.toHaveLabel(label); } toHaveId(id) { const traceDescription = expectDescription.toHaveId(id); return this.expect('toHaveId', traceDescription, id); } toNotHaveId(id) { return this.not.toHaveId(id); } toHaveValue(value) { const traceDescription = expectDescription.toHaveValue(value); return this.expect('toHaveValue', traceDescription, value); } toNotHaveValue(value) { return this.not.toHaveValue(value); } toHaveSliderPosition(position, tolerance = 0) { const traceDescription = expectDescription.toHaveSliderPosition(position, tolerance); return this.expect('toHaveSliderPosition', traceDescription, position, tolerance); } toHaveToggleValue(value) { const expectedValue = Number(value); const traceDescription = expectDescription.toHaveToggleValue(expectedValue); return this.expect('toHaveToggleValue', traceDescription, expectedValue); } createInvocation(expectation, ...params) { const definedParams = _.without(params, undefined); return { type: 'expectation', predicate: this.element.matcher.predicate, ...(this.element.index !== undefined && { atIndex: this.element.index }), ...(this.modifiers.length !== 0 && { modifiers: this.modifiers }), expectation, ...(definedParams.length !== 0 && { params: definedParams }) }; } expect(expectation, traceDescription, ...params) { assertTraceDescription(traceDescription); const invocation = this.createInvocation(expectation, ...params); traceDescription = expectDescription.full(traceDescription, this.modifiers.includes('not')); return _executeInvocation(this._invocationManager, invocation, traceDescription); } } class InternalExpect extends Expect { expect(expectation, _traceDescription, ...params) { return this.createInvocation(expectation, ...params); } } class Element { constructor(invocationManager, emitter, matcher, index) { this._invocationManager = invocationManager; this._emitter = emitter; this.matcher = matcher; this.index = index; } atIndex(index) { if (typeof index !== 'number') throw new Error(`atIndex argument must be a number, got ${typeof index}`); this.index = index; return this; } getAttributes() { const traceDescription = actionDescription.getAttributes(); return this.withAction('getAttributes', traceDescription); } tap(point) { _assertValidPoint(point); const traceDescription = actionDescription.tapAtPoint(point); return this.withAction('tap', traceDescription, point); } longPress(arg1, arg2) { let { point, duration } = mapLongPressArguments(arg1, arg2); const traceDescription = actionDescription.longPress(point, duration); return this.withAction('longPress', traceDescription, point, duration); } longPressAndDrag(duration, normalizedPositionX, normalizedPositionY, targetElement, normalizedTargetPositionX = NaN, normalizedTargetPositionY = NaN, speed = 'fast', holdDuration = 1000) { if (typeof duration !== 'number') throw new Error('duration should be a number, but got ' + (duration + (' (' + (typeof duration + ')')))); if (!(targetElement instanceof Element)) throwElementError(targetElement); if (typeof holdDuration !== 'number') throw new Error('duration should be a number, but got ' + (holdDuration + (' (' + (typeof holdDuration + ')')))); assertSpeed({ speed }); assertNormalized({ normalizedPositionX }); assertNormalized({ normalizedPositionY }); assertNormalized({ normalizedTargetPositionX }); assertNormalized({ normalizedTargetPositionY }); const traceDescription = actionDescription.longPressAndDrag(duration, normalizedPositionX, normalizedPositionY, targetElement, normalizedTargetPositionX, normalizedTargetPositionY, speed, holdDuration); return this.withActionAndTargetElement('longPress', targetElement, traceDescription, duration, normalizedPositionX, normalizedPositionY, normalizedTargetPositionX, normalizedTargetPositionY, speed, holdDuration); } multiTap(times) { if (typeof times !== 'number') throw new Error('times should be a number, but got ' + (times + (' (' + (typeof times + ')')))); if (times < 1) throw new Error('times should be greater than 0, but got ' + times); const traceDescription = actionDescription.multiTap(times); return this.withAction('multiTap', traceDescription, times); } tapAtPoint(point) { return this.tap(point); } tapBackspaceKey() { const traceDescription = actionDescription.tapBackspaceKey(); return this.withAction('tapBackspaceKey', traceDescription); } tapReturnKey() { const traceDescription = actionDescription.tapReturnKey(); return this.withAction('tapReturnKey', traceDescription); } typeText(text) { if (typeof text !== 'string') throw new Error('text should be a string, but got ' + (text + (' (' + (typeof text + ')')))); const traceDescription = actionDescription.typeText(text); return this.withAction('typeText', traceDescription, text); } replaceText(text) { if (typeof text !== 'string') throw new Error('text should be a string, but got ' + (text + (' (' + (typeof text + ')')))); const traceDescription = actionDescription.replaceText(text); return this.withAction('replaceText', traceDescription, text); } clearText() { const traceDescription = actionDescription.clearText(); return this.withAction('clearText', traceDescription); } performAccessibilityAction(actionName) { if (typeof actionName !== 'string') throw new Error('actionName should be a string, but got ' + (actionName + (' (' + (typeof actionName + ')')))); const traceDescription = actionDescription.performAccessibilityAction(actionName); return this.withAction('accessibilityAction', traceDescription, actionName); } scroll(pixels, direction = 'down', startPositionX = NaN, startPositionY = NaN) { if (!['left', 'right', 'up', 'down'].some(option => option === direction)) throw new Error('direction should be one of [left, right, up, down], but got ' + direction); if (typeof pixels !== 'number') throw new Error('amount of pixels should be a number, but got ' + (pixels + (' (' + (typeof pixels + ')')))); if (typeof startPositionX !== 'number') throw new Error('startPositionX should be a number, but got ' + (startPositionX + (' (' + (typeof startPositionX + ')')))); if (typeof startPositionY !== 'number') throw new Error('startPositionY should be a number, but got ' + (startPositionY + (' (' + (typeof startPositionY + ')')))); const traceDescription = actionDescription.scroll(pixels, direction, startPositionX, startPositionY); return this.withAction('scroll', traceDescription, pixels, direction, startPositionX, startPositionY); } scrollTo(edge, startPositionX = NaN, startPositionY = NaN) { if (!['left', 'right', 'top', 'bottom'].some(option => option === edge)) throw new Error('edge should be one of [left, right, top, bottom], but got ' + edge); if (typeof startPositionX !== 'number') throw new Error('startPositionX should be a number, but got ' + (startPositionX + (' (' + (typeof startPositionX + ')')))); if (typeof startPositionY !== 'number') throw new Error('startPositionY should be a number, but got ' + (startPositionY + (' (' + (typeof startPositionY + ')')))); const traceDescription = actionDescription.scrollTo(edge, startPositionX, startPositionY); return this.withAction('scrollTo', traceDescription, edge, startPositionX, startPositionY); } swipe(direction, speed = 'fast', normalizedSwipeOffset = NaN, normalizedStartingPointX = NaN, normalizedStartingPointY = NaN) { assertDirection({ direction }); assertSpeed({ speed }); assertNormalized({ normalizedSwipeOffset }); assertNormalized({ normalizedStartingPointX }); assertNormalized({ normalizedStartingPointY }); normalizedSwipeOffset = Number.isNaN(normalizedSwipeOffset) ? 0.75 : normalizedSwipeOffset; const traceDescription = actionDescription.swipe(direction, speed, normalizedSwipeOffset, normalizedStartingPointX, normalizedStartingPointY); return this.withAction( 'swipe', traceDescription, direction, speed, normalizedSwipeOffset, normalizedStartingPointX, normalizedStartingPointY ); } setColumnToValue(column, value) { if (typeof column !== 'number') throw new Error('column should be a number, but got ' + (column + (' (' + (typeof column + ')')))); if (typeof value !== 'string') throw new Error('value should be a string, but got ' + (value + (' (' + (typeof value + ')')))); const traceDescription = actionDescription.setColumnToValue(column, value); return this.withAction('setColumnToValue', traceDescription, column, value); } setDatePickerDate(dateString, dateFormat) { if (typeof dateString !== 'string') throw new Error('dateString should be a string, but got ' + (dateString + (' (' + (typeof dateString + ')')))); if (typeof dateFormat !== 'string') throw new Error('dateFormat should be a string, but got ' + (dateFormat + (' (' + (typeof dateFormat + ')')))); if (dateFormat === 'ISO8601') { dateString = removeMilliseconds(dateString); } const traceDescription = actionDescription.setDatePickerDate(dateString, dateFormat); return this.withAction('setDatePickerDate', traceDescription, dateString, dateFormat); } pinch(scale, speed = 'fast', angle = 0) { if (typeof scale !== 'number' || !Number.isFinite(scale) || scale < 0) throw new Error(`pinch scale must be a finite number larger than zero`); if (!['slow', 'fast'].includes(speed)) throw new Error(`pinch speed is either 'slow' or 'fast'`); if (typeof angle !== 'number' || !Number.isFinite(angle)) throw new Error(`pinch angle must be a finite number (radian)`); const traceDescription = actionDescription.pinch(scale, speed, angle); return this.withAction('pinch', traceDescription, scale, speed, angle); } pinchWithAngle(direction, speed = 'slow', angle = 0) { if (!['inward', 'outward'].includes(direction)) throw new Error(`pinchWithAngle direction is either 'inward' or 'outward'`); if (!['slow', 'fast'].includes(speed)) throw new Error(`pinchWithAngle speed is either 'slow' or 'fast'`); if (typeof angle !== 'number') throw new Error(`pinchWithAngle angle must be a number (radiant), got ${typeof angle}`); const traceDescription = actionDescription.pinchWithAngle(direction, speed, angle); return this.withAction('pinchWithAngle', traceDescription, direction, speed, angle); } adjustSliderToPosition(position) { if (!(typeof position === 'number' && position >= 0 && position <= 1)) throw new Error('position should be a number [0.0, 1.0], but got ' + (position + (' (' + (typeof position + ')')))); const traceDescription = actionDescription.adjustSliderToPosition(position); return this.withAction('adjustSliderToPosition', traceDescription, position); } async takeScreenshot(fileName) { const traceDescription = actionDescription.takeScreenshot(fileName); const { screenshotPath } = await this.withAction('takeScreenshot', traceDescription, fileName); const filePath = tempfile('.detox.element-screenshot.png'); await fs.move(screenshotPath, filePath); await this._emitter.emit('createExternalArtifact', { pluginId: 'screenshot', artifactName: fileName || path.basename(filePath, '.png'), artifactPath: filePath, }); return filePath; } createInvocation(action, targetElementMatcher, ...params) { params = _.map(params, (param) => _.isNaN(param) ? null : param); const definedParams = _.without(params, undefined); const invocation = { type: 'action', action, ...(this.index !== undefined && { atIndex: this.index }), ...(definedParams.length !== 0 && { params: definedParams }), predicate: this.matcher.predicate }; if (targetElementMatcher && targetElementMatcher.matcher && targetElementMatcher.matcher.predicate) { invocation.targetElement = { predicate: targetElementMatcher.matcher.predicate }; } return invocation; } withAction(action, traceDescription, ...params) { const invocation = this.createInvocation(action, null, ...params); return _executeInvocation(this._invocationManager, invocation, traceDescription); } withActionAndTargetElement(action, targetElement, traceDescription, ...params) { const invocation = this.createInvocation(action, targetElement, ...params); return _executeInvocation(this._invocationManager, invocation, traceDescription); } } class InternalElement extends Element { withAction(action, _traceDescription, ...params) { return this.createInvocation(action, null, ...params); } } class By { get web() { return webMatcher(); } get system() { return systemMatcher(); } id(id) { return new Matcher().id(id); } type(type) { return new Matcher().type(type); } text(text) { return new Matcher().text(text); } label(label) { return new Matcher().label(label); } accessibilityLabel(label) { return new Matcher().accessibilityLabel(label); } traits(traits) { return new Matcher().traits(traits); } value(value) { return new Matcher().value(value); } } class Matcher { /** @private */ static *predicates(matcher) { if (matcher.predicate.type === 'and') { yield* matcher.predicate.predicates; } else { yield matcher.predicate; } } accessibilityLabel(label) { return this.label(label); } label(label) { if (typeof label !== 'string' && !isRegExp(label)) throw new Error('label should be a string or regex, but got ' + (label + (' (' + (typeof label + ')')))); this.predicate = { type: 'label', value: label.toString(), isRegex: isRegExp(label) }; return this; } id(id) { if (typeof id !== 'string' && !isRegExp(id)) throw new Error('id should be a string or regex, but got ' + (id + (' (' + (typeof id + ')')))); this.predicate = { type: 'id', value: id.toString(), isRegex: isRegExp(id) }; return this; } type(type) { if (typeof type !== 'string') throw new Error('type should be a string, but got ' + (type + (' (' + (typeof type + ')')))); this.predicate = { type: 'type', value: type }; return this; } traits(traits) { if (!Array.isArray(traits)) throw new Error('traits must be an array, got ' + typeof traits); this.predicate = { type: 'traits', value: traits }; return this; } value(value) { if (typeof value !== 'string') throw new Error('value should be a string, but got ' + (value + (' (' + (typeof value + ')')))); this.predicate = { type: 'value', value: value }; return this; } text(text) { if (typeof text !== 'string' && !isRegExp(text)) throw new Error(`text should be a string or regex, but got ` + (text + (' (' + (typeof text + ')')))); this.predicate = { type: 'text', value: text.toString(), isRegex: isRegExp(text) }; return this; } withAncestor(matcher) { if (!(matcher instanceof Matcher)) { throwMatcherError(matcher); } return this.and({ predicate: { type: 'ancestor', predicate: matcher.predicate } }); } withDescendant(matcher) { if (!(matcher instanceof Matcher)) { throwMatcherError(matcher); } return this.and({ predicate: { type: 'descendant', predicate: matcher.predicate } }); } and(matcher) { const result = new Matcher(); result.predicate = { type: 'and', predicates: [ ...Matcher.predicates(this), ...Matcher.predicates(matcher), ].map(x => _.cloneDeep(x)) }; return result; } } class WaitFor { constructor(invocationManager, emitter, element) { this._invocationManager = invocationManager; this.element = new InternalElement(invocationManager, emitter, element.matcher, element.index); this.expectation = new InternalExpect(invocationManager, this.element); this._emitter = emitter; } get not() { this.expectation.not; return this; } toBeVisible(percent) { this.expectation = this.expectation.toBeVisible(percent); return this; } toBeNotVisible() { this.expectation = this.expectation.toBeNotVisible(); return this; } toExist() { this.expectation = this.expectation.toExist(); return this; } toNotExist() { this.expectation = this.expectation.toNotExist(); return this; } toHaveText(text) { this.expectation = this.expectation.toHaveText(text); return this; } toNotHaveText(text) { this.expectation = this.expectation.toNotHaveText(text); return this; } toHaveLabel(label) { this.expectation = this.expectation.toHaveLabel(label); return this; } toNotHaveLabel(label) { this.expectation = this.expectation.toNotHaveLabel(label); return this; } toHaveId(id) { this.expectation = this.expectation.toHaveId(id); return this; } toNotHaveId(id) { this.expectation = this.expectation.toNotHaveId(id); return this; } toHaveValue(value) { this.expectation = this.expectation.toHaveValue(value); return this; } toNotHaveValue(value) { this.expectation = this.expectation.toNotHaveValue(value); return this; } toBeFocused() { this.expectation = this.expectation.toBeFocused(); return this; } toBeNotFocused() { this.expectation = this.expectation.toBeNotFocused(); return this; } withTimeout(timeout) { if (typeof timeout !== 'number') throw new Error('text should be a number, but got ' + (timeout + (' (' + (typeof timeout + ')')))); if (timeout < 0) throw new Error('timeout must be larger than 0'); this.timeout = timeout; const traceDescription = expectDescription.withTimeout(timeout); return this.waitForWithTimeout(traceDescription); } whileElement(matcher) { if (!(matcher instanceof Matcher)) throwMatcherError(matcher); this.actionableElement = new InternalElement(this._invocationManager, this._emitter, matcher); return this; } tap(point) { this.action = this.actionableElement.tap(point); const traceDescription = actionDescription.tapAtPoint(point); return this.waitForWithAction(traceDescription); } longPress(arg1, arg2) { this.action = this.actionableElement.longPress(arg1, arg2); let { point, duration } = mapLongPressArguments(arg1, arg2); const traceDescription = actionDescription.longPress(point, duration); return this.waitForWithAction(traceDescription); } multiTap(times) { this.action = this.actionableElement.multiTap(times); const traceDescription = actionDescription.multiTap(times); return this.waitForWithAction(traceDescription); } tapAtPoint(point) { this.action = this.actionableElement.tap(point); const traceDescription = actionDescription.tapAtPoint(point); return this.waitForWithAction(traceDescription); } tapBackspaceKey() { this.action = this.actionableElement.tapBackspaceKey(); const traceDescription = actionDescription.tapBackspaceKey(); return this.waitForWithAction(traceDescription); } tapReturnKey() { this.action = this.actionableElement.tapReturnKey(); const traceDescription = actionDescription.tapReturnKey(); return this.waitForWithAction(traceDescription); } typeText(text) { this.action = this.actionableElement.typeText(text); const traceDescription = actionDescription.typeText(text); return this.waitForWithAction(traceDescription); } replaceText(text) { this.action = this.actionableElement.replaceText(text); const traceDescription = actionDescription.replaceText(text); return this.waitForWithAction(traceDescription); } clearText() { this.action = this.actionableElement.clearText(); const traceDescription = actionDescription.clearText(); return this.waitForWithAction(traceDescription); } scroll(pixels, direction, startPositionX, startPositionY) { this.action = this.actionableElement.scroll(pixels, direction, startPositionX, startPositionY); const traceDescription = actionDescription.scroll(pixels, direction, startPositionX, startPositionY); return this.waitForWithAction(traceDescription); } scrollTo(edge) { this.action = this.actionableElement.scrollTo(edge); const traceDescription = actionDescription.scrollTo(edge); return this.waitForWithAction(traceDescription); } swipe(direction, speed, percentage) { this.action = this.actionableElement.swipe(direction, speed, percentage); const traceDescription = actionDescription.swipe(direction, speed, percentage); return this.waitForWithAction(traceDescription); } setColumnToValue(column, value) { this.action = this.actionableElement.setColumnToValue(column, value); const traceDescription = actionDescription.setColumnToValue(column, value); return this.waitForWithAction(traceDescription); } setDatePickerDate(dateString, dateFormat) { this.action = this.actionableElement.setDatePickerDate(dateString, dateFormat); const traceDescription = actionDescription.setDatePickerDate(dateString, dateFormat); return this.waitForWithAction(traceDescription); } performAccessibilityAction(actionName) { this.action = this.actionableElement.performAccessibilityAction(actionName); const traceDescription = actionDescription.performAccessibilityAction(actionName); return this.waitForWithAction(traceDescription); } pinch(scale, speed, angle) { this.action = this.actionableElement.pinch(scale, speed, angle); const traceDescription = actionDescription.pinch(scale, speed, angle); return this.waitForWithAction(traceDescription); } pinchWithAngle(direction, speed, angle) { this.action = this.actionableElement.pinchWithAngle(direction, speed, angle); const traceDescription = actionDescription.pinchWithAngle(direction, speed, angle); return this.waitForWithAction(traceDescription); } waitForWithAction(actionTraceDescription) { const expectation = this.expectation; const action = this.action; const invocation = this.createWaitForWithActionInvocation(expectation, action); const traceDescription = expectDescription.waitFor(actionTraceDescription); return _executeInvocation(this._invocationManager, invocation, traceDescription); } createWaitForWithActionInvocation(expectation, action) { return { ...action, while: { ...expectation } }; } waitForWithTimeout(expectTraceDescription) { const expectation = this.expectation; const action = this.action; const timeout = this.timeout; const invocation = this.createWaitForWithTimeoutInvocation(expectation, action, timeout); const traceDescription = expectDescription.waitForWithTimeout(expectTraceDescription, timeout); return _executeInvocation(this._invocationManager, invocation, traceDescription); } createWaitForWithTimeoutInvocation(expectation, action, timeout) { return { ...action, ...expectation, timeout }; } } function element(invocationManager, emitter, matcher) { if (!(matcher instanceof Matcher)) { throwMatcherError(matcher); } return new Element(invocationManager, emitter, matcher); } function expect(invocationManager, element) { if (!(element instanceof Element)) { throwMatcherError(element); } return new Expect(invocationManager, element); } function waitFor(invocationManager, emitter, element) { if (!(element instanceof Element)) { throwMatcherError(element); } return new WaitFor(invocationManager, emitter, element); } class IosExpect { constructor({ invocationManager, xcuitestRunner, emitter }) { this._invocationManager = invocationManager; this._xcuitestRunner = xcuitestRunner; this._emitter = emitter; this.element = this.element.bind(this); this.expect = this.expect.bind(this); this.waitFor = this.waitFor.bind(this); this.by = new By(); this.web = this.web.bind(this); this.web.element = this.web().element; this.system = this.system.bind(this); this.system.element = this.system().element; } element(matcher) { return element(this._invocationManager, this._emitter, matcher); } expect(element) { if (isSystemElement(element)) { return systemExpect(this._xcuitestRunner, element); } if (isWebElement(element)) { return webExpect(this._invocationManager, this._xcuitestRunner, element); } return expect(this._invocationManager, element); } waitFor(element) { return waitFor(this._invocationManager, this._emitter, element); } web(matcher) { return { atIndex: index => { if (typeof index !== 'number' || index < 0) throw new Error('index should be an integer, got ' + (index + (' (' + (typeof index + ')')))); if (!(matcher instanceof Matcher)) throw new Error('cannot apply atIndex to a non-matcher'); matcher.index = index; return this.web(matcher); }, element: webMatcher => { if (!(matcher instanceof Matcher) && matcher !== undefined) { throwMatcherError(matcher); } const webViewElement = matcher ? element(this._invocationManager, this._emitter, matcher) : undefined; return webElement(this._invocationManager, this._xcuitestRunner, this._emitter, webViewElement, webMatcher); } }; } system() { return { element: systemMatcher => { return systemElement(this._xcuitestRunner, systemMatcher); } }; } } function _assertValidPoint(point) { if (!point) { // point is optional return; } if (typeof point !== 'object') throw new Error('point should be a object, but got ' + (point + (' (' + (typeof point + ')')))); if (typeof point.x !== 'number') throw new Error('point.x should be a number, but got ' + (point.x + (' (' + (typeof point.x + ')')))); if (typeof point.y !== 'number') throw new Error('point.y should be a number, but got ' + (point.y + (' (' + (typeof point.y + ')')))); } function throwMatcherError(param) { throw new Error(`${param} is not a Detox matcher. More about Detox matchers here: https://wix.github.io/Detox/docs/api/matchers`); } function throwElementError(param) { throw new Error(`${param} is not a Detox element. More about Detox elements here: https://wix.github.io/Detox/docs/api/matchers`); } function _executeInvocation(invocationManager, invocation, traceDescription) { return traceInvocationCall(traceDescription, invocation, invocationManager.execute(invocation)); } module.exports = IosExpect;