Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move iOS Device rotation to generated code #727

Merged
merged 3 commits into from
May 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 5 additions & 15 deletions detox/src/devices/IosDriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const InvocationManager = require('../invoke').InvocationManager;
const invoke = require('../invoke');
const GREYConfigurationApi = require('./../ios/earlgreyapi/GREYConfiguration');
const GREYConfigurationDetox = require('./../ios/earlgreyapi/GREYConfigurationDetox');
const EarlyGrey = require('./../ios/earlgreyapi/EarlGrey');

class IosDriver extends DeviceDriverBase {
constructor(client) {
Expand All @@ -27,7 +28,7 @@ class IosDriver extends DeviceDriverBase {
async setURLBlacklist(urlList) {
await this.client.execute(
GREYConfigurationApi.setValueForConfigKey(
GREYConfigurationApi.sharedInstance(),
invoke.callDirectly(GREYConfigurationApi.sharedInstance()),
urlList,
"GREYConfigKeyURLBlacklistRegex"
)
Expand All @@ -37,15 +38,15 @@ class IosDriver extends DeviceDriverBase {
async enableSynchronization() {
await this.client.execute(
GREYConfigurationDetox.enableSynchronization(
GREYConfigurationApi.sharedInstance()
invoke.callDirectly(GREYConfigurationApi.sharedInstance())
)
);
}

async disableSynchronization() {
await this.client.execute(
GREYConfigurationDetox.disableSynchronization(
GREYConfigurationApi.sharedInstance()
invoke.callDirectly(GREYConfigurationApi.sharedInstance())
)
);
}
Expand All @@ -55,19 +56,8 @@ class IosDriver extends DeviceDriverBase {
}

async setOrientation(deviceId, orientation) {
// keys are possible orientations
const orientationMapping = {
landscape: 3, // top at left side landscape
portrait: 1 // non-reversed portrait
};
if (!Object.keys(orientationMapping).includes(orientation)) {
throw new Error(`setOrientation failed: provided orientation ${orientation} is not part of supported orientations: ${Object.keys(orientationMapping)}`);
}
const call = EarlyGrey.rotateDeviceToOrientationErrorOrNil(invoke.EarlGrey.instance,orientation);

const call = invoke.call(invoke.EarlGrey.instance,
'rotateDeviceToOrientation:errorOrNil:',
invoke.IOS.NSInteger(orientationMapping[orientation])
);
await this.client.execute(call);
}

Expand Down
83 changes: 83 additions & 0 deletions detox/src/ios/earlgreyapi/EarlGrey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**

This code is generated.
For more information see generation/README.md.
*/


function sanitize_uiDeviceOrientation(value) {
const orientationMapping = {
landscape: 3, // top at left side landscape
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why not expose all options?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just a refactoring, I would rather improve the situation a different PR :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could create an issue, it's probably a very good first issue 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t understand. We have no usage of Earl Grey rotation so far. If this PR introduces it, why do we need additional issues or PRs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have and this is the actual mapping we are currently using: https://github.com/wix/detox/blob/master/detox/src/devices/IosDriver.js#L59-L62

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was my first bigger PR in detox 😻

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh I confused rotate with shake.
In either case, what blocks the full enum being exposed here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not much, I just would like to first transfer it and afterwards add the new cases. That way I surely don't touch tests and refactorings in one PR

portrait: 1 // non-reversed portrait
};

return orientationMapping[value];
}
class EarlGreyImpl {
/*Provides the file name and line number of the code that is calling into EarlGrey.
In case of a failure, the information is used to tell XCTest the exact line which caused
the failure so it can be highlighted in the IDE.

@param fileName The name of the file where the failing code exists.
@param lineNumber The line number of the failing code.

@return An EarlGreyImpl instance, with details of the code invoking EarlGrey.
*/static invokedFromFileLineNumber(fileName, lineNumber) {
if (typeof fileName !== "string") throw new Error("fileName should be a string, but got " + (fileName + (" (" + (typeof fileName + ")"))));
if (typeof lineNumber !== "number") throw new Error("lineNumber should be a number, but got " + (lineNumber + (" (" + (typeof lineNumber + ")"))));
return {
target: {
type: "Class",
value: "EarlGreyImpl"
},
method: "invokedFromFile:lineNumber:",
args: [{
type: "NSString",
value: fileName
}, {
type: "NSInteger",
value: lineNumber
}]
};
}

/*Rotate the device to a given @c deviceOrientation. All device orientations except for
@c UIDeviceOrientationUnknown are supported. If a non-nil @c errorOrNil is provided, it will
be populated with the failure reason if the orientation change fails, otherwise a test failure
will be registered.

@param deviceOrientation The desired orientation of the device.
@param[out] errorOrNil Error that will be populated on failure. If @c nil, a test
failure will be reported if the rotation attempt fails.

@return @c YES if the rotation was successful, @c NO otherwise.
*/static rotateDeviceToOrientationErrorOrNil(element, deviceOrientation) {
if (!["landscape", "portrait"].some(option => option === deviceOrientation)) throw new Error("deviceOrientation should be one of [landscape, portrait], but got " + deviceOrientation);
return {
target: element,
method: "rotateDeviceToOrientation:errorOrNil:",
args: [{
type: "NSInteger",
value: sanitize_uiDeviceOrientation(deviceOrientation)
}]
};
}

/*Dismisses the keyboard by resigning the first responder, if any. Will populate the provided
error if the first responder is not present or if the keyboard is not visible.

@param[out] errorOrNil Error that will be populated on failure. If @c nil, a test
failure will be reported if the dismissing fails.

@return @c YES if the dismissing of the keyboard was successful, @c NO otherwise.
*/static dismissKeyboardWithError(element) {
return {
target: element,
method: "dismissKeyboardWithError:",
args: []
};
}

}

module.exports = EarlGreyImpl;
10 changes: 2 additions & 8 deletions detox/src/ios/earlgreyapi/GREYCondition.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@ GREYCondition::waitWithTimeout:pollInterval:
*/static waitWithTimeout(element, seconds) {
if (typeof seconds !== "number") throw new Error("seconds should be a number, but got " + (seconds + (" (" + (typeof seconds + ")"))));
return {
target: {
type: "Invocation",
value: element
},
target: element,
method: "waitWithTimeout:",
args: [{
type: "CGFloat",
Expand All @@ -48,10 +45,7 @@ the condition as close as possible to every @c interval seconds.
if (typeof seconds !== "number") throw new Error("seconds should be a number, but got " + (seconds + (" (" + (typeof seconds + ")"))));
if (typeof interval !== "number") throw new Error("interval should be a number, but got " + (interval + (" (" + (typeof interval + ")"))));
return {
target: {
type: "Invocation",
value: element
},
target: element,
method: "waitWithTimeout:pollInterval:",
args: [{
type: "CGFloat",
Expand Down
35 changes: 7 additions & 28 deletions detox/src/ios/earlgreyapi/GREYConfiguration.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ NSInvalidArgumentException is raised.
*/static valueForConfigKey(element, configKey) {
if (typeof configKey !== "string") throw new Error("configKey should be a string, but got " + (configKey + (" (" + (typeof configKey + ")"))));
return {
target: {
type: "Invocation",
value: element
},
target: element,
method: "valueForConfigKey:",
args: [{
type: "NSString",
Expand All @@ -55,10 +52,7 @@ raised.
*/static boolValueForConfigKey(element, configKey) {
if (typeof configKey !== "string") throw new Error("configKey should be a string, but got " + (configKey + (" (" + (typeof configKey + ")"))));
return {
target: {
type: "Invocation",
value: element
},
target: element,
method: "boolValueForConfigKey:",
args: [{
type: "NSString",
Expand All @@ -79,10 +73,7 @@ raised.
*/static integerValueForConfigKey(element, configKey) {
if (typeof configKey !== "string") throw new Error("configKey should be a string, but got " + (configKey + (" (" + (typeof configKey + ")"))));
return {
target: {
type: "Invocation",
value: element
},
target: element,
method: "integerValueForConfigKey:",
args: [{
type: "NSString",
Expand All @@ -103,10 +94,7 @@ raised.
*/static doubleValueForConfigKey(element, configKey) {
if (typeof configKey !== "string") throw new Error("configKey should be a string, but got " + (configKey + (" (" + (typeof configKey + ")"))));
return {
target: {
type: "Invocation",
value: element
},
target: element,
method: "doubleValueForConfigKey:",
args: [{
type: "NSString",
Expand All @@ -121,10 +109,7 @@ raised.
are not reset.
*/static reset(element) {
return {
target: {
type: "Invocation",
value: element
},
target: element,
method: "reset",
args: []
};
Expand All @@ -140,10 +125,7 @@ Overwrites any previous value for the configuration.
*/static setValueForConfigKey(element, value, configKey) {
if (typeof configKey !== "string") throw new Error("configKey should be a string, but got " + (configKey + (" (" + (typeof configKey + ")"))));
return {
target: {
type: "Invocation",
value: element
},
target: element,
method: "setValue:forConfigKey:",
args: [value, {
type: "NSString",
Expand All @@ -162,10 +144,7 @@ Overwrites any previous value for the configuration.
*/static setDefaultValueForConfigKey(element, value, configKey) {
if (typeof configKey !== "string") throw new Error("configKey should be a string, but got " + (configKey + (" (" + (typeof configKey + ")"))));
return {
target: {
type: "Invocation",
value: element
},
target: element,
method: "setDefaultValue:forConfigKey:",
args: [value, {
type: "NSString",
Expand Down
10 changes: 2 additions & 8 deletions detox/src/ios/earlgreyapi/GREYConfigurationDetox.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,15 @@
class GREYConfiguration {
static enableSynchronization(element) {
return {
target: {
type: "Invocation",
value: element
},
target: element,
method: "enableSynchronization",
args: []
};
}

static disableSynchronization(element) {
return {
target: {
type: "Invocation",
value: element
},
target: element,
method: "disableSynchronization",
args: []
};
Expand Down
65 changes: 45 additions & 20 deletions detox/src/ios/earlgreyapi/GREYInteraction.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ will be performed on.
}

return {
target: {
type: "Invocation",
value: element
},
target: element,
method: "inRoot:",
args: [rootMatcher]
};
Expand Down Expand Up @@ -60,10 +57,7 @@ performAction:grey_tap()] // This should be separately called for the action.
}

return {
target: {
type: "Invocation",
value: element
},
target: element,
method: "usingSearchAction:onElementWithMatcher:",
args: [action, matcher]
};
Expand All @@ -81,15 +75,32 @@ performAction:grey_tap()] // This should be separately called for the action.
}

return {
target: {
type: "Invocation",
value: element
},
target: element,
method: "performAction:",
args: [action]
};
}

/*Performs an @c action on the selected UI element with an error set on failure.

@param action The action to be performed on the @c element.
@param[out] errorOrNil Error populated on failure.
@throws NSException on action failure if @c errorOrNil is not set.

@return The provided GREYInteraction instance, with an action and an error that will be
populated on failure.
*/static performActionError(element, action) {
if (typeof action !== "object" || action.type !== "Invocation" || typeof action.value !== "object" || typeof action.value.target !== "object" || action.value.target.value !== "GREYActions") {
throw new Error('action should be a GREYAction, but got ' + JSON.stringify(action));
}

return {
target: element,
method: "performAction:error:",
args: [action]
};
}

/*Performs an assertion that evaluates @c matcher on the selected UI element.

@param matcher The matcher to be evaluated on the @c element.
Expand All @@ -101,15 +112,32 @@ performAction:grey_tap()] // This should be separately called for the action.
}

return {
target: {
type: "Invocation",
value: element
},
target: element,
method: "assertWithMatcher:",
args: [matcher]
};
}

/*Performs an assertion that evaluates @c matcher on the selected UI element.

@param matcher The matcher to be evaluated on the @c element.
@param[out] errorOrNil Error populated on failure.
@throws NSException on assertion failure if @c errorOrNil is not set.

@return The provided GREYInteraction instance, with a matcher to be evaluated on an element and
an error that will be populated on failure.
*/static assertWithMatcherError(element, matcher) {
if (typeof matcher !== "object" || matcher.type !== "Invocation" || typeof matcher.value !== "object" || typeof matcher.value.target !== "object" || matcher.value.target.value !== "GREYMatchers") {
throw new Error('matcher should be a GREYMatcher, but got ' + JSON.stringify(matcher));
}

return {
target: element,
method: "assertWithMatcher:error:",
args: [matcher]
};
}

/*In case of multiple matches, selects the element at the specified index. In case of the
index being over the number of matched elements, it throws an exception. Please make sure
that this is used after you've created the matcher. For example, in case three elements are
Expand All @@ -129,10 +157,7 @@ specified index in the list of matched elements.
*/static atIndex(element, index) {
if (typeof index !== "number") throw new Error("index should be a number, but got " + (index + (" (" + (typeof index + ")"))));
return {
target: {
type: "Invocation",
value: element
},
target: element,
method: "atIndex:",
args: [{
type: "NSInteger",
Expand Down
Loading