UNPKG

detox

Version:

E2E tests and automation for mobile

203 lines (168 loc) 9.46 kB
const path = require('path'); const fs = require('fs-extra'); const tempfile = require('tempfile'); const DetoxRuntimeError = require('../../errors/DetoxRuntimeError'); const invoke = require('../../invoke'); const { removeMilliseconds } = require('../../utils/dateUtils'); const { actionDescription } = require('../../utils/invocationTraceDescriptions'); const mapLongPressArguments = require('../../utils/mapLongPressArguments'); const actions = require('../actions/native'); const DetoxMatcherApi = require('../espressoapi/DetoxMatcher'); const { ActionInteraction } = require('../interactions/native'); class NativeElement { constructor(invocationManager, emitter, matcher) { this._invocationManager = invocationManager; this._emitter = emitter; this._matcher = matcher; } get _call() { return invoke.call(invoke.Espresso, 'onView', this._matcher._call); } atIndex(index) { if (typeof index !== 'number') throw new DetoxRuntimeError({ message: `Element atIndex argument must be a number, got ${typeof index}` }); const matcher = this._matcher; this._matcher._call = invoke.callDirectly(DetoxMatcherApi.matcherForAtIndex(index, matcher._call.value)); return this; } async tap(value) { const action = new actions.TapAction(value); const traceDescription = actionDescription.tapAtPoint(value); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async tapAtPoint(value) { const action = new actions.TapAtPointAction(value); const traceDescription = actionDescription.tapAtPoint(value); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async longPress(optionalPointOrDuration, optionalDuration) { const { point, duration } = mapLongPressArguments(optionalPointOrDuration, optionalDuration); const action = new actions.LongPressAction(point, duration); const traceDescription = actionDescription.longPress(point, duration); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async longPressAndDrag(duration, normalizedPositionX, normalizedPositionY, targetElement, normalizedTargetPositionX, normalizedTargetPositionY, speed, holdDuration) { const action = new actions.LongPressAndDragAction( duration, normalizedPositionX, normalizedPositionY, targetElement, normalizedTargetPositionX, normalizedTargetPositionY, speed, holdDuration); const traceDescription = actionDescription.longPressAndDrag( duration, normalizedPositionX, normalizedPositionY, targetElement, normalizedTargetPositionX, normalizedTargetPositionY, speed, holdDuration); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async 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 action = new actions.MultiClickAction(times); const traceDescription = actionDescription.multiTap(times); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async tapBackspaceKey() { const action = new actions.PressKeyAction(67); const traceDescription = actionDescription.tapBackspaceKey(); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async tapReturnKey() { const action = new actions.TypeTextAction('\n'); const traceDescription = actionDescription.tapReturnKey(); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async typeText(value) { const action = new actions.TypeTextAction(value); const traceDescription = actionDescription.typeText(value); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async replaceText(value) { const action = new actions.ReplaceTextAction(value); const traceDescription = actionDescription.replaceText(value); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async clearText() { const action = new actions.ClearTextAction(); const traceDescription = actionDescription.clearText(); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async scroll(amount, direction = 'down', startPositionX, startPositionY) { const action = new actions.ScrollAmountAction(direction, amount, startPositionX, startPositionY); const traceDescription = actionDescription.scroll(amount, direction, startPositionX, startPositionY); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async scrollTo(edge, startPositionX, startPositionY) { // override the user's element selection with an extended matcher that looks for UIScrollView children this._matcher = this._matcher._extendToDescendantScrollViews(); const action = new actions.ScrollEdgeAction(edge, startPositionX, startPositionY); const traceDescription = actionDescription.scrollTo(edge, startPositionX, startPositionY); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async scrollToIndex(index) { // override the user's element selection with an extended matcher that looks for UIScrollView children this._matcher = this._matcher._extendToDescendantScrollViews(); const action = new actions.ScrollToIndex(index); const traceDescription = actionDescription.scrollToIndex(index); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async setDatePickerDate(rawDateString, formatString) { const dateString = formatString === 'ISO8601' ? removeMilliseconds(rawDateString) : rawDateString; const action = new actions.SetDatePickerDateAction(dateString, formatString); const traceDescription = actionDescription.setDatePickerDate(dateString, formatString); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } /** * @param {'up' | 'right' | 'down' | 'left'} direction * @param {'slow' | 'fast'} [speed] * @param {number} [normalizedSwipeOffset] - swipe amount relative to the screen width/height * @param {number} [normalizedStartingPointX] - X coordinate of swipe starting point, relative to the view width * @param {number} [normalizedStartingPointY] - Y coordinate of swipe starting point, relative to the view height */ async swipe(direction, speed = 'fast', normalizedSwipeOffset = NaN, normalizedStartingPointX = NaN, normalizedStartingPointY = NaN) { normalizedSwipeOffset = Number.isNaN(normalizedSwipeOffset) ? 0.75 : normalizedSwipeOffset; // override the user's element selection with an extended matcher that avoids RN issues with RCTScrollView this._matcher = this._matcher._avoidProblematicReactNativeElements(); const action = new actions.SwipeAction(direction, speed, normalizedSwipeOffset, normalizedStartingPointX, normalizedStartingPointY); const traceDescription = actionDescription.swipe(direction, speed, normalizedSwipeOffset, normalizedStartingPointX, normalizedStartingPointY); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async takeScreenshot(screenshotName) { const action = new actions.TakeElementScreenshot(); const traceDescription = actionDescription.takeScreenshot(screenshotName); const resultBase64 = await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); const filePath = tempfile('detox.element-screenshot.png'); await fs.writeFile(filePath, resultBase64, 'base64'); await this._emitter.emit('createExternalArtifact', { pluginId: 'screenshot', artifactName: screenshotName || path.basename(filePath, '.png'), artifactPath: filePath, }); return filePath; } async getAttributes() { const action = new actions.GetAttributes(); const traceDescription = actionDescription.getAttributes(); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async adjustSliderToPosition(newPosition) { const action = new actions.AdjustSliderToPosition(newPosition); const traceDescription = actionDescription.adjustSliderToPosition(newPosition); return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute(); } async performAccessibilityAction(actionName) { const traceDescription = actionDescription.performAccessibilityAction(actionName); return await new ActionInteraction(this._invocationManager, this._matcher, new actions.AccessibilityActionAction(actionName), traceDescription).execute(); } } module.exports = { NativeElement, };