diff --git a/detox/android/detox/src/full/java/com/wix/detox/espresso/action/GetAttributesAction.kt b/detox/android/detox/src/full/java/com/wix/detox/espresso/action/GetAttributesAction.kt index cb8c40cc74..de70802077 100644 --- a/detox/android/detox/src/full/java/com/wix/detox/espresso/action/GetAttributesAction.kt +++ b/detox/android/detox/src/full/java/com/wix/detox/espresso/action/GetAttributesAction.kt @@ -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 @@ -17,26 +18,25 @@ import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.notNullValue import org.json.JSONObject -class GetAttributesAction() : ViewActionWithResult { +class GetAttributesAction() : ViewActionWithResult, 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 diff --git a/detox/android/detox/src/testFull/java/com/wix/detox/espresso/action/GetAttributesActionTest.kt b/detox/android/detox/src/testFull/java/com/wix/detox/espresso/action/GetAttributesActionTest.kt index 4bf15ea536..e7159e223c 100644 --- a/detox/android/detox/src/testFull/java/com/wix/detox/espresso/action/GetAttributesActionTest.kt +++ b/detox/android/detox/src/testFull/java/com/wix/detox/espresso/action/GetAttributesActionTest.kt @@ -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 @@ -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 @@ -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 @@ -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) } diff --git a/detox/index.d.ts b/detox/index.d.ts index 90bc73a1e0..4a2c040c0c 100644 --- a/detox/index.d.ts +++ b/detox/index.d.ts @@ -1430,8 +1430,9 @@ declare global { takeScreenshot(name: string): Promise; /** - * 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 () => { @@ -1445,7 +1446,7 @@ declare global { * jestExpect(attributes.width).toHaveValue(100); * }) */ - getAttributes(): Promise; + getAttributes(): Promise; } interface WebExpect> { diff --git a/detox/src/android/AndroidExpect.test.js b/detox/src/android/AndroidExpect.test.js index e2c393f5bb..56d0cf9ed6 100644 --- a/detox/src/android/AndroidExpect.test.js +++ b/detox/src/android/AndroidExpect.test.js @@ -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); }); diff --git a/detox/src/android/core/NativeElement.js b/detox/src/android/core/NativeElement.js index df95c40d22..bcca79df83 100644 --- a/detox/src/android/core/NativeElement.js +++ b/detox/src/android/core/NativeElement.js @@ -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) { diff --git a/detox/test/e2e/33.attributes.test.js b/detox/test/e2e/33.attributes.test.js index eec8e0bde6..c1a278ee0e 100644 --- a/detox/test/e2e/33.attributes.test.js +++ b/detox/test/e2e/33.attributes.test.js @@ -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; /** @@ -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; } } @@ -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(/^$/), - }; - - 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(/^$/), + }; + + 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 }); }); }); diff --git a/detox/test/types/detox-global-tests.ts b/detox/test/types/detox-global-tests.ts index 5199b638c0..0681e85846 100644 --- a/detox/test/types/detox-global-tests.ts +++ b/detox/test/types/detox-global-tests.ts @@ -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; } });