Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: wix/Detox
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 20.27.6
Choose a base ref
...
head repository: wix/Detox
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 20.28.0
Choose a head ref

Commits on Nov 3, 2024

  1. Copy the full SHA
    a49b90b View commit details

Commits on Nov 4, 2024

  1. test(e2e): Add assertions test suite for copilot

    Including assertions screen changes
    LironMShemen committed Nov 4, 2024
    Copy the full SHA
    3d37651 View commit details
  2. Copy the full SHA
    96b04c3 View commit details
  3. Copy the full SHA
    419d17e View commit details
  4. Copy the full SHA
    64d6f6d View commit details
  5. test(e2e): Add datepicker test suite for detox-copilot

    test: Change the launching app date to constant date
    
    Instead of generating the current date and time. For more stable testing, and for the copilot cache.
    
    including changes in the date picker screen
    LironMShemen committed Nov 4, 2024
    Copy the full SHA
    a793116 View commit details
  6. Copy the full SHA
    9ba0eca View commit details
  7. chore: Update cache file due to addition of 4 new test suites

    The cache file was modified as a result of incorporating four new test suites: assertions, location, datepicker and visibility.
    LironMShemen committed Nov 4, 2024
    Copy the full SHA
    6834970 View commit details
  8. Merge pull request #4623 from wix/E2E-test-suites-for-detox-copilot

    test: add more E2E test suites for Detox Copilot
    asafkorem authored Nov 4, 2024
    Copy the full SHA
    0c652db View commit details

Commits on Nov 7, 2024

  1. website: integrate google analytics.

    asafkorem committed Nov 7, 2024
    Copy the full SHA
    b908352 View commit details
  2. Merge pull request #4629 from wix/integreate-analytics

    website: integrate google analytics.
    asafkorem authored Nov 7, 2024
    Copy the full SHA
    e1c3c73 View commit details

Commits on Nov 9, 2024

  1. website: configure gtag manager plugin.

    asafkorem committed Nov 9, 2024
    Copy the full SHA
    d689696 View commit details
  2. Merge pull request #4631 from wix/website/add-concent-button

    website: configure gtag manager plugin.
    asafkorem authored Nov 9, 2024
    Copy the full SHA
    c9e1be5 View commit details

Commits on Nov 11, 2024

  1. #4537 - Enabled detox butler on demo app

    gosha212 committed Nov 11, 2024
    Copy the full SHA
    b314dd5 View commit details

Commits on Nov 12, 2024

  1. Merge pull request #4632 from wix/feat/add-detox-butler-to-test-app

    #4537 - Enabled detox butler on demo app
    gosha212 authored Nov 12, 2024
    Copy the full SHA
    532f696 View commit details

Commits on Nov 13, 2024

  1. Copy the full SHA
    c1081e9 View commit details
  2. Copy the full SHA
    f8eb3ea View commit details
  3. Copy the full SHA
    749ecf7 View commit details
  4. test(mapDeviceLongPressArguments): add unit tests.

    asafkorem committed Nov 13, 2024
    Copy the full SHA
    8e5fb44 View commit details
  5. test(app): fix buttons out-of-layout issue (visibility error on test).

    asafkorem committed Nov 13, 2024
    Copy the full SHA
    d48482d View commit details

Commits on Nov 14, 2024

  1. Merge pull request #4542 from wix/feat/device-tap-ios

    feat: add `device.tap()` and `device.longPress()`.
    asafkorem authored Nov 14, 2024
    Copy the full SHA
    b4554de View commit details
  2. Publish 20.28.0 [ci skip]

    mobileoss committed Nov 14, 2024
    Copy the full SHA
    c97e583 View commit details
Showing with 1,895 additions and 58 deletions.
  1. +4 −0 .gitignore
  2. +53 −2 detox/android/detox/src/full/java/com/wix/detox/espresso/EspressoDetox.java
  3. +11 −0 detox/android/detox/src/full/java/com/wix/detox/espresso/UiAutomatorHelper.java
  4. +2 −2 detox/android/detox/src/full/java/com/wix/detox/espresso/matcher/IsDisplayingAtLeastDetoxMatcher.kt
  5. +39 −0 detox/detox.d.ts
  6. +2 −4 detox/ios/DetoxXCUITestRunner/DetoxXCUITestRunner/DetoxXCUITestRunner.swift
  7. +1 −1 .../ios/DetoxXCUITestRunner/DetoxXCUITestRunner/Extensions/InvocationParams+matcherDescription.swift
  8. +45 −2 detox/ios/DetoxXCUITestRunner/DetoxXCUITestRunner/Handlers/ActionHandler.swift
  9. +2 −1 detox/ios/DetoxXCUITestRunner/DetoxXCUITestRunner/Handlers/ExpectationHandler.swift
  10. +3 −1 detox/ios/DetoxXCUITestRunner/DetoxXCUITestRunner/Handlers/PredicateHandler.swift
  11. +4 −2 detox/ios/DetoxXCUITestRunner/DetoxXCUITestRunner/Params Reader/InvocationParams.swift
  12. +2 −2 detox/package.json
  13. +83 −0 detox/src/android/espressoapi/EspressoDetox.js
  14. +27 −15 detox/src/copilot/detoxCopilotFrameworkDriver.js
  15. +11 −0 detox/src/devices/runtime/RuntimeDevice.js
  16. +17 −0 detox/src/devices/runtime/RuntimeDevice.test.js
  17. +8 −0 detox/src/devices/runtime/drivers/DeviceDriverBase.js
  18. +16 −0 detox/src/devices/runtime/drivers/android/AndroidDriver.js
  19. +20 −0 detox/src/devices/runtime/drivers/android/AndroidDriver.test.js
  20. +35 −0 detox/src/devices/runtime/drivers/ios/SimulatorDriver.js
  21. +124 −0 detox/src/devices/runtime/drivers/ios/SimulatorDriver.test.js
  22. +9 −0 detox/src/utils/assertArgument.js
  23. +1 −0 detox/src/utils/invocationTraceDescriptions.js
  24. +56 −0 detox/src/utils/mapDeviceLongPressArguments.js
  25. +62 −0 detox/src/utils/mapDeviceLongPressArguments.test.js
  26. +736 −1 detox/test/detox_copilot_cache.json
  27. +65 −0 detox/test/e2e/06.device-tap.test.js
  28. +143 −0 detox/test/e2e/copilot/07.copilot.assertions.test.js
  29. +59 −0 detox/test/e2e/copilot/08.copilot.location.test.js
  30. +62 −0 detox/test/e2e/copilot/09.copilot.datepicker.test.js
  31. +50 −0 detox/test/e2e/copilot/10.copilot.visibility.test.js
  32. +2 −2 detox/test/package.json
  33. +11 −0 detox/test/src/Screens/AssertionsScreen.js
  34. +2 −1 detox/test/src/Screens/DatePickerScreen.js
  35. +43 −0 detox/test/src/Screens/DeviceTapScreen.tsx
  36. +2 −0 detox/test/src/Screens/index.js
  37. +9 −1 detox/test/src/app.js
  38. +45 −0 docs/api/device.md
  39. +2 −2 examples/demo-native-android/package.json
  40. +2 −2 examples/demo-native-ios/package.json
  41. +2 −2 examples/demo-plugin/package.json
  42. +2 −2 examples/demo-react-native-detox-instruments/package.json
  43. +9 −9 examples/demo-react-native/detox.config.js
  44. +2 −2 examples/demo-react-native/package.json
  45. +1 −1 lerna.json
  46. +1 −1 package.json
  47. +7 −0 website/docusaurus.config.js
  48. +1 −0 website/package.json
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -236,3 +236,7 @@ Detox-ios.tbz

# Ignore .env files across the project
**/.env

#ignore yalc
.yalc/
yalc.lock
Original file line number Diff line number Diff line change
@@ -21,19 +21,22 @@

import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.NoMatchingViewException;
import androidx.test.platform.app.InstrumentationRegistry;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
import static com.wix.detox.espresso.UiAutomatorHelper.getStatusBarHeightDps;

/**
* Created by rotemm on 26/12/2016.
*/
public class EspressoDetox {
private static final String LOG_TAG = "detox";

private static int calculateAdjustedY(View view, Integer y, boolean shouldIgnoreStatusBar) {
return shouldIgnoreStatusBar ? y + getStatusBarHeightDps(view) : y;
}

public static Object perform(Matcher<View> matcher, ViewAction action) {
ViewActionPerformer performer = ViewActionPerformer.forAction(action);
return performer.performOn(matcher);
@@ -121,5 +124,53 @@ public void run() {
}
});
}

public static void tap(Integer x, Integer y, boolean shouldIgnoreStatusBar) {
onView(isRoot()).perform(new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return isRoot();
}

@Override
public String getDescription() {
return "tap on screen";
}

@Override
public void perform(UiController uiController, View view) {
int adjustedY = calculateAdjustedY(view, y, shouldIgnoreStatusBar);
ViewAction action = DetoxAction.tapAtLocation(x, adjustedY);
action.perform(uiController, view);
uiController.loopMainThreadUntilIdle();
}
});
}

public static void longPress(Integer x, Integer y, boolean shouldIgnoreStatusBar) {
longPress(x, y, null, shouldIgnoreStatusBar);
}

public static void longPress(Integer x, Integer y, Integer duration, boolean shouldIgnoreStatusBar) {
onView(isRoot()).perform(new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return isRoot();
}

@Override
public String getDescription() {
return "long press on screen";
}

@Override
public void perform(UiController uiController, View view) {
int adjustedY = calculateAdjustedY(view, y, shouldIgnoreStatusBar);
ViewAction action = DetoxAction.longPress(x, adjustedY, duration);
action.perform(uiController, view);
uiController.loopMainThreadUntilIdle();
}
});
}
}

Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.wix.detox.espresso;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.Choreographer;
import android.view.View;

import com.wix.detox.common.UIThread;
import com.wix.detox.espresso.action.common.utils.UiControllerUtils;
@@ -111,4 +116,10 @@ public void doFrame(long frameTimeNanos) {
}
}

@SuppressLint({"DiscouragedApi", "InternalInsetResource"})
public static int getStatusBarHeightDps(View view) {
Context context = view.getContext();
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
return (int) (context.getResources().getDimensionPixelSize(resourceId) / ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT));
}
}
Original file line number Diff line number Diff line change
@@ -120,7 +120,7 @@ class IsDisplayingAtLeastDetoxMatcher(private val areaPercentage: Int) : TypeSaf
.defaultDisplay
.getMetrics(m)

val statusBarHeight = getStatusBarHeight(view)
val statusBarHeight = getStatusBarHeightPixels(view)
val actionBarHeight = getActionBarHeight(view)
return Rect(0, 0, m.widthPixels, m.heightPixels - (statusBarHeight + actionBarHeight))
}
@@ -138,7 +138,7 @@ class IsDisplayingAtLeastDetoxMatcher(private val areaPercentage: Int) : TypeSaf
}

@SuppressLint("InternalInsetResource", "DiscouragedApi")
private fun getStatusBarHeight(view: View): Int {
private fun getStatusBarHeightPixels(view: View): Int {
val resourceId = view.context.resources.getIdentifier("status_bar_height", "dimen", "android")
return if (resourceId > 0) view.context.resources.getDimensionPixelSize(resourceId) else 0
}
39 changes: 39 additions & 0 deletions detox/detox.d.ts
Original file line number Diff line number Diff line change
@@ -784,6 +784,45 @@ declare global {
*/
setOrientation(orientation: Orientation): Promise<void>;

/**
* Perform a tap at arbitrary coordinates on the device's screen.
* @param point Coordinates in the element's coordinate space. Optional. defaults: x: 100, y: 100
* @param shouldIgnoreStatusBar Coordinates will be measured starting from under the status bar. this param will affect only in Android tests. Optional. default: true
* @example await device.tap();
* @example await device.tap({ x: 100, y: 150 }, false);
* @example await device.tap({ x: 100, y: 150 });
* @example await device.tap(false);
*/
tap(): Promise<void>;
tap(point: Point2D): Promise<void>;
tap(point: Point2D, shouldIgnoreStatusBar: boolean): Promise<void>;
tap(shouldIgnoreStatusBar: boolean): Promise<void>;

/**
* Perform a long press at arbitrary coordinates on the device's screen. Custom press duration if needed.
* @param point Coordinates in the device's coordinate space. Optional. defaults: x: 100, y: 100
* @param duration Custom press duration time, in milliseconds. Optional (defaults to the standard long-press duration for Android and 1000 milliseconds for ios).
* Custom durations should be used cautiously, as they can affect test consistency and user experience expectations.
* They are typically necessary when testing components that behave differently from the platform's defaults or when simulating unique user interactions.
* @param shouldIgnoreStatusBar Coordinates will be measured starting from under the status bar. this param will affect only in Android tests. Optional. default: true
* @example await device.longPress();
* @example await device.longPress({ x: 100, y: 150 }, 2000, false);
* @example await device.longPress({ x: 100, y: 150 }, 2000);
* @example await device.longPress(2000, false);
* @example await device.longPress({ x: 100, y: 150 }, false);
* @example await device.longPress({ x: 100, y: 150 });
* @example await device.longPress(2000);
* @example await device.longPress(false);
*/
longPress(): Promise<void>;
longPress(point: Point2D, duration: number, shouldIgnoreStatusBar: boolean): Promise<void>;
longPress(point: Point2D, duration: number): Promise<void>;
longPress(duration: number, shouldIgnoreStatusBar: boolean): Promise<void>;
longPress(point: Point2D, shouldIgnoreStatusBar: boolean): Promise<void>;
longPress(point: Point2D): Promise<void>;
longPress(duration: number): Promise<void>;
longPress(shouldIgnoreStatusBar: boolean): Promise<void>;

/**
* Sets the simulator/emulator location to the given latitude and longitude.
*
Original file line number Diff line number Diff line change
@@ -26,14 +26,12 @@ final class DetoxXCUITestRunner: XCTestCase {
appUnderTest: appUnderTest
)

let element = predicateHandler.findElement(using: params)

switch params.type {
case .systemAction, .webAction:
try actionHandler.handle(from: params, on: element)
try actionHandler.handle(from: params, predicateHandler: predicateHandler)

case .systemExpectation, .webExpectation:
try expectationHandler.handle(from: params, on: element)
try expectationHandler.handle(from: params, predicateHandler: predicateHandler)
}
}
}
Original file line number Diff line number Diff line change
@@ -7,6 +7,6 @@ import Foundation

extension InvocationParams {
var matcherDescription: String {
return predicate.description
return predicate?.description ?? "none"
}
}
Original file line number Diff line number Diff line change
@@ -7,48 +7,91 @@ import Foundation
import XCTest

class ActionHandler {
func handle(from params: InvocationParams, on element: XCUIElement) throws {

func findElement(from params: InvocationParams, predicateHandler: PredicateHandler) -> XCUIElement {
let element = predicateHandler.findElement(using: params)
let exists = element.waitForExistence(timeout: .defaultTimeout)
DTXAssert(
exists,
"Action failed, element with matcher `\(params.matcherDescription)` does not exist"
)
return element
}

func getNormalizedCoordinate(from params: InvocationParams) throws -> XCUICoordinate {
let x = Int(params.params?.first ?? "100") ?? 100
let y = Int(params.params?[1] ?? "100") ?? 100

let appUnderTest = try XCUIApplication.appUnderTest()
let screenFrame = appUnderTest.frame
let normalizedX = CGFloat(x) / screenFrame.width
let normalizedY = CGFloat(y) / screenFrame.height
let normalizedPoint = CGVector(dx: normalizedX, dy: normalizedY)
let coordinate = appUnderTest.coordinate(
withNormalizedOffset: normalizedPoint)

return coordinate
}

func handle(from params: InvocationParams, predicateHandler: PredicateHandler) throws {

guard let action = params.action else { return }
switch action {
case .tap:
let element = findElement(from: params, predicateHandler: predicateHandler);
element.tap()

case .typeText:
guard let text = params.params?.first else {
throw Error.missingTypeTextParam
}

let element = findElement(from: params, predicateHandler: predicateHandler);
element.typeTextOnEnd(text)

case .replaceText:
guard let text = params.params?.first else {
throw Error.missingTypeTextParam
}

let element = findElement(from: params, predicateHandler: predicateHandler);
element.replaceText(text)

case .clearText:
let element = findElement(from: params, predicateHandler: predicateHandler);
element.clearText()

case .coordinateTap:
do {
try getNormalizedCoordinate(from: params).tap();
} catch {
throw Error.failedToTapDeviceByCoordinates
}
case .coordinateLongPress:
guard let pressDuration = Double(params.params?[2] ?? "1") else { throw Error.missingTypeTextParam
}

do {
try getNormalizedCoordinate(from: params).press(forDuration: pressDuration);
} catch {
throw Error.failedToTapDeviceByCoordinates
}
}
}
}

extension ActionHandler {
enum Error: Swift.Error, LocalizedError {
case missingTypeTextParam
case failedToTapDeviceByCoordinates

var errorDescription: String? {
switch self {
case .missingTypeTextParam:
return "Missing text param for type action"
}
case .failedToTapDeviceByCoordinates:
return "Failed to perform tap action by coordinates"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -7,11 +7,12 @@ import Foundation
import XCTest

class ExpectationHandler {
func handle(from params: InvocationParams, on element: XCUIElement) throws {
func handle(from params: InvocationParams, predicateHandler: PredicateHandler) throws {
guard let expectation = params.expectation else {
throw Error.invalidInvocationParams("Expectation type is missing")
}

let element = predicateHandler.findElement(using: params)
let expectedEvaluation = expectedEvaluation(params)

switch expectation {
Original file line number Diff line number Diff line change
@@ -16,7 +16,9 @@ class PredicateHandler {
}

func findElement(using params: InvocationParams) -> XCUIElement {
let predicate = params.predicate
guard let predicate = params.predicate else {
fatalError("expected predicate param")
}
let query: XCUIElementQuery

switch params.type {
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import Foundation

struct InvocationParams: Codable {
let type: InvocationType
let predicate: Predicate
let predicate: Predicate?
let atIndex: Int?
let action: Action?
let expectation: Expectation?
@@ -34,7 +34,7 @@ struct InvocationParams: Codable {
} else if let webPredicate = try? container.decode(Predicate.self, forKey: .webPredicate) {
predicate = webPredicate
} else {
throw Error.dataCorruptedError("predicate")
predicate = nil
}

// Handle both systemAtIndex and webAtIndex for the atIndex property
@@ -157,6 +157,8 @@ extension InvocationParams.Predicate {
extension InvocationParams {
enum Action: String, Codable {
case tap
case coordinateTap
case coordinateLongPress
case typeText
case replaceText
case clearText
4 changes: 2 additions & 2 deletions detox/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "detox",
"description": "E2E tests and automation for mobile",
"version": "20.27.6",
"version": "20.28.0",
"bin": {
"detox": "local-cli/cli.js"
},
@@ -71,7 +71,7 @@
"caf": "^15.0.1",
"chalk": "^4.0.0",
"child-process-promise": "^2.2.0",
"detox-copilot": "^0.0.23",
"detox-copilot": "^0.0.24",
"execa": "^5.1.1",
"find-up": "^5.0.0",
"fs-extra": "^11.0.0",
Loading