Skip to content

Commit

Permalink
iOS: Add pinch actions (wix#1104)
Browse files Browse the repository at this point in the history
* Create the pinch actions and link it to 

* Add pinch direction sanitizer to generator

* Remove duplicate supportedTypes

* Add grey pinch sanitizer to snapshot test

* Add double to supported types and remap it to NSNumber

* Add sanitizing test

* Unit test pinch action

* Add e2e test for pinch on iOS

* Remove duplicate console logger for unsupported types

* Fix ios artifacts

* Restrict flaky test on android to ios only

* Add docs on the pinch action

* Fixing doc

* Remove useless artifacts tests

* Fixing steps numbers on e2e tests modification

* Re enable tests on android

* Add artifacts snapshots and android tests back
  • Loading branch information
sraikimaxime authored and rotemmiz committed Jan 30, 2019
1 parent d46fb25 commit 90cbc13
Show file tree
Hide file tree
Showing 15 changed files with 264 additions and 26 deletions.
83 changes: 83 additions & 0 deletions detox/src/ios/earlgreyapi/GREYActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ function sanitize_greyContentEdge(action) {
throw new Error(`GREYAction.GREYContentEdge must be a 'left'/'right'/'top'/'bottom', got ${action}`);
}
}
function sanitize_greyPinchDirection(action) {
switch (action) {
case 'outward':
return 1;
case 'inward':
return 2;

default:
throw new Error(`GREYAction.GREYPinchDirection must be a 'outward'/'inward', got ${action}`);
}
}
class GREYActions {
/*@return A GREYAction that performs multiple taps of a specified @c count.*/
static actionForMultipleTapsWithCount(count) {
Expand Down Expand Up @@ -495,6 +506,78 @@ class GREYActions {
};
}

/*Returns an action that pinches view quickly in the specified @c direction and @c angle.
@param pinchDirection The direction of the pinch action.
@param angle The angle of the pinch action in radians.
Use @c kGREYPinchAngleDefault for the default angle (currently set to
30 degrees).
@return A GREYAction that performs a fast pinch on the view in the specified @c direction.*/
static actionForPinchFastInDirectionWithAngle(pinchDirection, angle) {
if (!["outward", "inward"].some(option => option === pinchDirection)) throw new Error("pinchDirection should be one of [outward, inward], but got " + pinchDirection);
return {
target: {
type: "Class",
value: "GREYActions"
},
method: "actionForPinchFastInDirection:withAngle:",
args: [{
type: "NSInteger",
value: sanitize_greyPinchDirection(pinchDirection)
}, {
type: "NSNumber",
value: angle
}]
};
}

/*Returns an action that pinches view slowly in the specified @c direction and @c angle.
@param pinchDirection The direction of the pinch action.
@param angle The angle of the pinch action in radians.
Use @c kGREYPinchAngleDefault for the default angle (currently set to
30 degrees).
@return A GREYAction that performs a slow pinch on the view in the specified @c direction.*/
static actionForPinchSlowInDirectionWithAngle(pinchDirection, angle) {
if (!["outward", "inward"].some(option => option === pinchDirection)) throw new Error("pinchDirection should be one of [outward, inward], but got " + pinchDirection);
return {
target: {
type: "Class",
value: "GREYActions"
},
method: "actionForPinchSlowInDirection:withAngle:",
args: [{
type: "NSInteger",
value: sanitize_greyPinchDirection(pinchDirection)
}, {
type: "NSNumber",
value: angle
}]
};
}

/*Returns an action that changes the value of UIStepper to @c value by tapping the appropriate
button multiple times.
@param value The value to change the UIStepper to.
@return A GREYAction that sets the given @c value on a stepper.*/
static actionForSetStepperValue(value) {
return {
target: {
type: "Class",
value: "GREYActions"
},
method: "actionForSetStepperValue:",
args: [{
type: "NSNumber",
value: value
}]
};
}

/*Returns an action that taps on an element at the activation point of the element.
@return A GREYAction to tap on an element.*/
Expand Down
44 changes: 44 additions & 0 deletions detox/src/ios/earlgreyapi/GREYMatchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,25 @@ class GREYMatchers {
};
}

/*Matcher that matches UIStepper with value as @c value.
@param value A value that the UIStepper should be checked for.
@return A matcher for checking UIStepper values.*/
static matcherForStepperValue(value) {
return {
target: {
type: "Class",
value: "GREYMatchers"
},
method: "matcherForStepperValue:",
args: [{
type: "NSNumber",
value: value
}]
};
}

/*Matcher that matches a UISlider's value.
@param valueMatcher A matcher for the UISlider's value. You must provide a valid
Expand Down Expand Up @@ -557,6 +576,31 @@ class GREYMatchers {
};
}

/*A Matcher for NSNumbers that matches when the examined number is within a specified @c delta
from the specified value.
@param value The expected value of the number being matched.
@param delta The delta within which matches are allowed
@return A matcher that checks if a number is close to a specified @c value.*/
static matcherForCloseToDelta(value, delta) {
return {
target: {
type: "Class",
value: "GREYMatchers"
},
method: "matcherForCloseTo:delta:",
args: [{
type: "NSNumber",
value: value
}, {
type: "NSNumber",
value: delta
}]
};
}

/*A Matcher that matches against any object, including @c nils.
@return A matcher that matches any object.*/
Expand Down
19 changes: 19 additions & 0 deletions detox/src/ios/expect.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ class MultiTapAction extends Action {
}
}

class PinchAction extends Action {
constructor(direction, speed, angle) {
super();
if (typeof direction !== 'string') throw new Error(`PinchAction ctor 1st argument must be a string, got ${typeof direction}`);
if (typeof speed !== 'string') throw new Error(`PinchAction ctor 2nd argument must be a string, got ${typeof speed}`);
if (typeof angle !== 'number') throw new Error(`PinchAction ctor 3nd argument must be a number, got ${typeof angle}`);
if (speed == 'fast') {
this._call = invoke.callDirectly(GreyActions.actionForPinchFastInDirectionWithAngle(direction, angle));
} else if (speed == 'slow') {
this._call = invoke.callDirectly(GreyActions.actionForPinchSlowInDirectionWithAngle(direction, angle));
} else {
throw new Error(`PinchAction speed must be a 'fast'/'slow', got ${speed}`);
}
}
}

class TypeTextAction extends Action {
constructor(value) {
super();
Expand Down Expand Up @@ -293,6 +309,9 @@ class Element {
async clearText() {
return await new ActionInteraction(this, new ClearTextAction()).execute();
}
async pinchWithAngle(direction, speed = 'slow', angle = 0) {
return await new ActionInteraction(this, new PinchAction(direction, speed, angle)).execute();
}
async scroll(amount, direction = 'down') {
// override the user's element selection with an extended matcher that looks for UIScrollView children
this._selectElementWithMatcher(this._originalMatcher._extendToDescendantScrollViews());
Expand Down
8 changes: 8 additions & 0 deletions detox/src/ios/expect.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ describe('expect', async () => {
await e.element(e.by.id('UniqueId937')).tapReturnKey();
await e.element(e.by.id('UniqueId005')).clearText();
await e.element(e.by.id('UniqueId005')).replaceText('replaceTo');
await e.element(e.by.id('UniqueId005')).pinchWithAngle('outward', 'fast', 0);
await e.element(e.by.id('UniqueId005')).pinchWithAngle('outward');
await e.element(e.by.id('ScrollView161')).scroll(100);
await e.element(e.by.id('ScrollView161')).scroll(100, 'down');
await e.element(e.by.id('ScrollView161')).scroll(100, 'up');
Expand All @@ -133,6 +135,12 @@ describe('expect', async () => {
await expectToThrow(() => e.element(e.by.id('UniqueId819')).multiTap('NaN'));
await expectToThrow(() => e.element(e.by.id('UniqueId937')).typeText(0));
await expectToThrow(() => e.element(e.by.id('UniqueId005')).replaceText(3));
await expectToThrow(() => e.element(e.by.id('UniqueId005')).pinchWithAngle('noDirection', 'slow', 0));
await expectToThrow(() => e.element(e.by.id('UniqueId005')).pinchWithAngle(1, 'slow', 0));
await expectToThrow(() => e.element(e.by.id('UniqueId005')).pinchWithAngle('outward', 1, 0));
await expectToThrow(() => e.element(e.by.id('UniqueId005')).pinchWithAngle('outward', 'noDirection', 0));
await expectToThrow(() => e.element(e.by.id('UniqueId005')).pinchWithAngle('outward', 'slow', 'NaN'));
await expectToThrow(() => e.element(e.by.id('UniqueId005')).replaceText(3));
await expectToThrow(() => e.element(e.by.id('ScrollView161')).scroll('NaN', 'down'));
await expectToThrow(() => e.element(e.by.id('ScrollView161')).scroll(100, 'noDirection'));
await expectToThrow(() => e.element(e.by.id('ScrollView161')).scroll(100, 0));
Expand Down
7 changes: 7 additions & 0 deletions detox/test/e2e/03.actions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,11 @@ describe('Actions', () => {
await expect(element(by.id('WhyDoAllTheTestIDsHaveTheseStrangeNames'))).toBeVisible();
});

it(':ios: should zoom in and out the pinchable scrollview', async () => {
await element(by.id('PinchableScrollView')).pinchWithAngle('outward', 'slow', 0);
await expect(element(by.id('UniqueId007'))).toBeNotVisible();
await element(by.id('PinchableScrollView')).pinchWithAngle('inward', 'slow', 0);
await expect(element(by.id('UniqueId007'))).toBeVisible();
});

});
7 changes: 7 additions & 0 deletions detox/test/src/Screens/ActionsScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ export default class ActionsScreen extends Component {
}>
</ScrollView>
</View>
<View>
<ScrollView testID='PinchableScrollView' minimumZoomScale={1} maximumZoomScale={10}>
<View>
<View testID='UniqueId007' style={{ height: 30, width: 30, backgroundColor:'red' }} />
</View>
</ScrollView>
</View>
</View>
);
}
Expand Down
14 changes: 13 additions & 1 deletion docs/APIRef.ActionsOnElement.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Actions are functions that emulate user behavior. They are being performed on ma
- [`.scrollTo()`](#scrolltoedge)
- [`.swipe()`](#swipedirection-speed-percentage)
- [`.setColumnToValue()`](#setcolumntovaluecolumnvalue--ios-only) **iOS only**
- [`.pinchWithAngle()`](#pinchWithAngle--ios-only) **iOS only**


### `tap()`
Expand Down Expand Up @@ -125,7 +126,7 @@ await element(by.id('scrollView')).swipe('down');
await element(by.id('scrollView')).swipe('down', 'fast');
await element(by.id('scrollView')).swipe('down', 'fast', 0.5);
```
### `setColumnToValue(column,value)` iOS only
### `setColumnToValue(column, value)` iOS only

column - date picker column index<br>
value - string value to set in column<br>
Expand All @@ -135,3 +136,14 @@ await expect(element(by.type('UIPickerView'))).toBeVisible();
await element(by.type('UIPickerView')).setColumnToValue(1,"6");
await element(by.type('UIPickerView')).setColumnToValue(2,"34");
```

### `pinchWithAngle(direction, speed, angle)` iOS only

direction - inward/outward<br>
speed - slow/fast - default is slow<br>
angle - value in radiant - default is 0<br>

```js
await expect(element(by.id('PinchableScrollView'))).toBeVisible();
await element(by.id('PinchableScrollView')).pinchWithAngle('outward', 'slow', 0);
```
16 changes: 6 additions & 10 deletions docs/Guide.Contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,16 +119,12 @@ launchctl setenv PATH $PATH

##### Changing Detox e2e test suite

If you add, rename, or delete a test in `detox/test/e2e` suite, you also have to update Jest snapshots
of expected artifacts. It is usually done in five steps:

1. In `detox/test` project, run all end-to-end tests on iOS with `npm run e2e:ios-multi`.
2. Update the snapshots with: `npm run verify-artifacts:ios -- -u`.
3. Re-run Android tests with `npm run e2e:android`.
4. Update the snapshots with: `npm run verify-artifacts:android -- -u`.
5. Add the snapshots to your Git commit:
* Android: `detox/test/scripts/__snapshots__/verify_artifacts_are_not_missing.android.test.js.snap`
* iOS: `detox/test/scripts/__snapshots__/verify_artifacts_are_not_missing.ios.test.js.snap`
If you add, rename, or delete a test in `detox/test/e2e` suite, you should follow these steps:

1. In `detox/test` project, build the ios project with `npm run build:ios`.
2. Run all end-to-end tests on iOS with `npm run e2e:ios`.
3. In `detox/test` project, build the android project with `npm run build:android`
4. Run all end-to-end tests on Android with `npm run e2e:android`.

#### 3. Android Native tests

Expand Down
2 changes: 2 additions & 0 deletions generation/__tests__/__snapshots__/global-functions.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ exports[`globals sanitize_greyContentEdge should fail with unknown value 1`] = `

exports[`globals sanitize_greyDirection should fail with unknown value 1`] = `"GREYAction.GREYDirection must be a 'left'/'right'/'up'/'down', got kittens"`;

exports[`globals sanitize_greyPinchDirection should fail with unknown value 1`] = `"GREYAction.GREYPinchDirection must be a 'outward'/'inward', got kittens"`;

exports[`globals sanitize_uiAccessibilityTraits should throw if unknown trait is accessed 1`] = `"Unknown trait 'unknown', see list in https://facebook.github.io/react-native/docs/accessibility.html#accessibilitytraits-ios"`;
20 changes: 20 additions & 0 deletions generation/__tests__/__snapshots__/ios.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,26 @@ Object {
}
`;

exports[`iOS generation Invocations should sanitize the pinch directions 1`] = `
Object {
"args": Array [
Object {
"type": "NSInteger",
"value": 1,
},
Object {
"type": "NSNumber",
"value": 0,
},
],
"method": "actionForPinchSlowInDirection:withAngle:",
"target": Object {
"type": "Class",
"value": "GREYActions",
},
}
`;

exports[`iOS generation special case: id<GREYMatcher> should fail with wrongly formatted matchers 1`] = `"firstMatcher should be a GREYMatcher, but got {\\"type\\":\\"Invocation\\",\\"value\\":{\\"target\\":{\\"type\\":\\"Class\\",\\"value\\":\\"GREYAction\\"},\\"method\\":\\"matcherForAccessibilityID:\\",\\"args\\":[\\"Grandfather883\\"]}}"`;
exports[`iOS generation special case: id<GREYMatcher> should fail with wrongly formatted matchers 2`] = `"ancestorMatcher should be a GREYMatcher, but got {\\"type\\":\\"Invocation\\",\\"value\\":{\\"target\\":{\\"type\\":\\"Class\\",\\"value\\":\\"GREYAction\\"},\\"method\\":\\"matcherForAccessibilityID:\\",\\"args\\":[\\"Grandson883\\"]}}"`;
Expand Down
12 changes: 12 additions & 0 deletions generation/__tests__/global-functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ describe('globals', () => {
}).toThrowErrorMatchingSnapshot();
});
});
describe('sanitize_greyPinchDirection', () => {
it('should return numbers for strings', () => {
expect(globals.sanitize_greyPinchDirection('outward')).toBe(1);
expect(globals.sanitize_greyPinchDirection('inward')).toBe(2);
});

it('should fail with unknown value', () => {
expect(() => {
globals.sanitize_greyPinchDirection('kittens');
}).toThrowErrorMatchingSnapshot();
});
});

describe('sanitize_greyContentEdge', () => {
it('should return numbers for strings', () => {
Expand Down
8 changes: 8 additions & 0 deletions generation/__tests__/ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ describe('iOS generation', () => {
expect(result).toMatchSnapshot();
});

it('should sanitize the pinch directions', () => {
const result = ExampleClass.actionForPinchSlowInDirectionWithAngle('outward', 0);

expect(result.args[0].type).toBe('NSInteger');
expect(result.args[0].value).toBe(1);
expect(result).toMatchSnapshot();
});

it('should sanitize the content edge', () => {
const result = ExampleClass.actionForScrollToContentEdge('bottom');

Expand Down
Loading

0 comments on commit 90cbc13

Please sign in to comment.