Skip to content

Commit

Permalink
Merge pull request #4184 from wix/feat/get-attributes-multiple-views
Browse files Browse the repository at this point in the history
feat(android): extend `getAttributes()` API to handle multiple elements.
  • Loading branch information
asafkorem authored Sep 12, 2023
2 parents d77cbe0 + ae2c5fb commit 35e5e88
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.widget.TextView
import androidx.test.espresso.UiController
import com.google.android.material.slider.Slider
import com.wix.detox.espresso.ViewActionWithResult
import com.wix.detox.espresso.MultipleViewsAction
import com.wix.detox.espresso.common.SliderHelper
import com.wix.detox.reactnative.ui.getAccessibilityLabel
import org.hamcrest.Matcher
Expand All @@ -17,26 +18,25 @@ import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.notNullValue
import org.json.JSONObject

class GetAttributesAction() : ViewActionWithResult<String?> {
class GetAttributesAction() : ViewActionWithResult<JSONObject?>, MultipleViewsAction {
private val commonAttributes = CommonAttributes()
private val textViewAttributes = TextViewAttributes()
private val checkBoxAttributes = CheckBoxAttributes()
private val progressBarAttributes = ProgressBarAttributes()
private val sliderAttributes = SliderAttributes()
private var result: String = ""
private var result: JSONObject? = null

override fun perform(uiController: UiController?, view: View?) {
view!!

val json = JSONObject()

commonAttributes.get(json, view)
textViewAttributes.get(json, view)
checkBoxAttributes.get(json, view)
progressBarAttributes.get(json, view)
sliderAttributes.get(json, view)

result = json.toString()
result = json
}

override fun getResult() = result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,10 @@ class MultipleViewsActionPerformer(
}
}

return if (results.isEmpty()) {
null
} else if (results.size == 1) {
results.first()
} else {
mapOf("elements" to results)
return when {
results.isEmpty() -> null
results.size == 1 -> results.first()
else -> mapOf("elements" to results)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class GetAttributesActionTest {

private fun perform(v: View = view): JSONObject {
uut.perform(null, v)
return JSONObject(uut.getResult())
return uut.getResult()!!
}

@Test
Expand Down Expand Up @@ -135,10 +135,10 @@ class GetAttributesActionTest {
}

val resultJson = perform()
assertThat(resultJson.opt("alpha")).isEqualTo(0.42)
assertThat(resultJson.opt("alpha")).isEqualTo(0.42f)
assertThat(resultJson.opt("width")).isEqualTo(123)
assertThat(resultJson.opt("height")).isEqualTo(456)
assertThat(resultJson.opt("elevation")).isEqualTo(0.314)
assertThat(resultJson.opt("elevation")).isEqualTo(0.314f)
}

@Test
Expand Down Expand Up @@ -208,7 +208,8 @@ class GetAttributesActionTest {
}

val resultJson = perform(slider)
assertThat(resultJson.opt("value")).isEqualTo(0.42)
android.util.Log.i("TESTS", "should return material-Slider state through value attribute: "+ resultJson)
assertThat(resultJson.opt("value")).isEqualTo(0.42f)
}

@Test
Expand All @@ -221,7 +222,7 @@ class GetAttributesActionTest {

val resultJson = perform(textView)
assertThat(resultJson.opt("text")).isEqualTo("mock-text")
assertThat(resultJson.opt("textSize")).isEqualTo(24)
assertThat(resultJson.opt("textSize")).isEqualTo(24f)
assertThat(resultJson.opt("length")).isEqualTo(111)
}

Expand Down
7 changes: 4 additions & 3 deletions detox/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1430,8 +1430,9 @@ declare global {
takeScreenshot(name: string): Promise<string>;

/**
* Gets the native (OS-dependent) attributes of the element.
* For more information, see {@link https://wix.github.io/Detox/docs/api/actions-on-element/#getattributes}
* Retrieves the OS-dependent attributes of an element.
* If there are multiple matches, it returns an array of attributes for all matched elements.
* For detailed information, refer to {@link https://wix.github.io/Detox/docs/api/actions-on-element/#getattributes}
*
* @example
* test('Get the attributes for my text element', async () => {
Expand All @@ -1445,7 +1446,7 @@ declare global {
* jestExpect(attributes.width).toHaveValue(100);
* })
*/
getAttributes(): Promise<IosElementAttributes | AndroidElementAttributes | { elements: IosElementAttributes[]; }>;
getAttributes(): Promise<IosElementAttributes | AndroidElementAttributes | { elements: IosElementAttributes[] } | { elements: AndroidElementAttributes[] } >;
}

interface WebExpect<R = Promise<void>> {
Expand Down
2 changes: 1 addition & 1 deletion detox/src/android/AndroidExpect.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ describe('AndroidExpect', () => {
text: 'hello',
value: 1,
};
mockExecutor.executeResult = Promise.resolve(JSON.stringify(execResult));
mockExecutor.executeResult = Promise.resolve(execResult);
const result = await e.element(e.by.id('UniqueId005')).getAttributes();
expect(result).toEqual(execResult);
});
Expand Down
3 changes: 1 addition & 2 deletions detox/src/android/core/NativeElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,7 @@ class NativeElement {
async getAttributes() {
const action = new actions.GetAttributes();
const traceDescription = actionDescription.getAttributes();
const result = await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
return JSON.parse(result);
return await new ActionInteraction(this._invocationManager, this._matcher, action, traceDescription).execute();
}

async adjustSliderToPosition(newPosition) {
Expand Down
100 changes: 64 additions & 36 deletions detox/test/e2e/33.attributes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ const expect = require('expect').default;
describe('Attributes', () => {
/** @type {Detox.IndexableNativeElement} */
let currentElement;
/** @type {Detox.ElementAttributes} */
/** @type {Detox.IosElementAttributes | Detox.AndroidElementAttributes} */
let attributes;
/** @type {Detox.ElementAttributes[]} */
/** @type {Detox.IosElementAttributes[] | Detox.AndroidElementAttributes[]} */
let attributesArray;

/**
Expand All @@ -15,10 +15,11 @@ describe('Attributes', () => {
async function useMatcher(matcher) {
currentElement = element(matcher);
const result = await currentElement.getAttributes();

if ('elements' in result) {
attributesArray = result.elements;
} else {
attributes = await result;
attributes = result;
}
}

Expand Down Expand Up @@ -202,41 +203,68 @@ describe('Attributes', () => {
});

describe('of multiple views', () => {
describe(':ios:', () => {
beforeAll(() => useMatcher(by.type('RCTView').withAncestor(by.id('attrScrollView'))));

it('should return an object with .elements array', async () => {
const viewShape = {
identifier: expect.any(String),
enabled: true,
visible: true,
activationPoint: shapes.Point2D(),
normalizedActivationPoint: shapes.Point2D(),
hittable: true,
frame: shapes.IosElementAttributeFrame(),
elementFrame: shapes.IosElementAttributeFrame(),
elementBounds: shapes.IosElementAttributeFrame(),
safeAreaInsets: shapes.IosElementAttributesInsets(),
elementSafeBounds: shapes.IosElementAttributeFrame(),
layer: expect.stringMatching(/^<CALayer: 0x[\da-f]+>$/),
};

const innerViews = attributesArray.filter(a => a.identifier);
expect(innerViews.length).toBe(2);
expect(innerViews[0]).toMatchObject({ ...viewShape });
expect(innerViews[1]).toMatchObject({ ...viewShape });
});
it(':ios: should return an object with .elements array', async () => {
await useMatcher(by.type('RCTView').withAncestor(by.id('attrScrollView')));

const viewShape = {
identifier: expect.any(String),
enabled: true,
visible: true,
activationPoint: shapes.Point2D(),
normalizedActivationPoint: shapes.Point2D(),
hittable: true,
frame: shapes.IosElementAttributeFrame(),
elementFrame: shapes.IosElementAttributeFrame(),
elementBounds: shapes.IosElementAttributeFrame(),
safeAreaInsets: shapes.IosElementAttributesInsets(),
elementSafeBounds: shapes.IosElementAttributeFrame(),
layer: expect.stringMatching(/^<CALayer: 0x[\da-f]+>$/),
};

const innerViews = attributesArray.filter(a => a.identifier);
expect(innerViews.length).toBe(2);
expect(innerViews[0]).toMatchObject({ ...viewShape });
expect(innerViews[1]).toMatchObject({ ...viewShape });
});

describe(':android:', () => {
// TODO (@jonathanmos) : Can we decide something about it officially?
it('should throw an error (because it is not implemented)', async () => {
await expect(
element(
by.type('com.facebook.react.views.view.ReactViewGroup')
.withAncestor(by.id('attrScrollView'))
).getAttributes()
).rejects.toThrowError(/Problem views are marked with '.{4}MATCHES.{4}' below/m);
it(':android: should return an object with .elements array', async () => {
await useMatcher(by.type('com.facebook.react.views.view.ReactViewGroup').withAncestor(by.id('attrScrollView')));

expect(attributesArray.length).toBe(3);

const baseAttributes = {
visibility: 'visible',
visible: true,
alpha: 1,
elevation: 0,
focused: false,
enabled: true,
};

expect(attributesArray[0]).toMatchObject({
...{
height: 394,
width: 1074,
},
...baseAttributes
});

expect(attributesArray[1]).toMatchObject({
...{
height: 197,
width: 262,
identifier: 'innerView1'
},
...baseAttributes
});

expect(attributesArray[2]).toMatchObject({
...{
height: 197,
width: 262,
identifier: 'innerView2'
},
...baseAttributes
});
});
});
Expand Down
11 changes: 8 additions & 3 deletions detox/test/types/detox-global-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,17 @@ describe("Test", () => {

beforeEach(async () => {
const attributes = await element(by.id("element")).getAttributes();

if ('elements' in attributes) {
commonAttributes = iosAttributes = attributes.elements[0];
if ('activationPoint' in attributes.elements[0]) {
commonAttributes = iosAttributes = attributes.elements[0] as Detox.IosElementAttributes;
} else {
commonAttributes = androidAttributes = attributes.elements[0] as Detox.AndroidElementAttributes;
}
} else if ('activationPoint' in attributes) {
commonAttributes = iosAttributes = attributes;
commonAttributes = iosAttributes = attributes as Detox.IosElementAttributes;
} else {
commonAttributes = androidAttributes = attributes;
commonAttributes = androidAttributes = attributes as Detox.AndroidElementAttributes;
}
});

Expand Down

0 comments on commit 35e5e88

Please sign in to comment.