diff --git a/apps/toolbox/src/pages/a11y.xml b/apps/toolbox/src/pages/a11y.xml
index 1cf029804..d8c62cb36 100644
--- a/apps/toolbox/src/pages/a11y.xml
+++ b/apps/toolbox/src/pages/a11y.xml
@@ -7,25 +7,25 @@
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
@@ -33,7 +33,7 @@
-
+
diff --git a/packages/core/accessibility/index.android.ts b/packages/core/accessibility/index.android.ts
index d268265ff..78d531c41 100644
--- a/packages/core/accessibility/index.android.ts
+++ b/packages/core/accessibility/index.android.ts
@@ -645,6 +645,11 @@ function applyContentDescription(view: Partial, forceUpdate?: boolean) {
const contentDescription = contentDescriptionBuilder.join('. ').trim().replace(/^\.$/, '');
+ if (typeof __USE_TEST_ID__ !== 'undefined' && __USE_TEST_ID__ && view.testID) {
+ // ignore when testID is enabled
+ return;
+ }
+
if (contentDescription) {
if (Trace.isEnabled()) {
Trace.write(`${cls} - set to "${contentDescription}"`, Trace.categories.Accessibility);
diff --git a/packages/core/global-types.d.ts b/packages/core/global-types.d.ts
index 1272d24e0..e63f29601 100644
--- a/packages/core/global-types.d.ts
+++ b/packages/core/global-types.d.ts
@@ -133,6 +133,7 @@ declare const __CSS_PARSER__: string;
declare const __NS_WEBPACK__: boolean;
declare const __UI_USE_EXTERNAL_RENDERER__: boolean;
declare const __UI_USE_XML_PARSER__: boolean;
+declare const __USE_TEST_ID__: boolean | undefined;
declare const __ANDROID__: boolean;
declare const __IOS__: boolean;
diff --git a/packages/core/platforms/android/res/values/ids.xml b/packages/core/platforms/android/res/values/ids.xml
new file mode 100644
index 000000000..7a40388f1
--- /dev/null
+++ b/packages/core/platforms/android/res/values/ids.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/packages/core/ui/core/view/index.android.ts b/packages/core/ui/core/view/index.android.ts
index c7a5933e0..30ac9b62a 100644
--- a/packages/core/ui/core/view/index.android.ts
+++ b/packages/core/ui/core/view/index.android.ts
@@ -3,7 +3,7 @@ import type { Point, CustomLayoutView as CustomLayoutViewDefinition } from '.';
import type { GestureTypes, GestureEventData } from '../../gestures';
// Types.
-import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty } from './view-common';
+import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty } from './view-common';
import { paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length } from '../../styling/style-properties';
import { layout } from '../../../utils';
import { Trace } from '../../../trace';
@@ -796,6 +796,23 @@ export class View extends ViewCommon {
this.nativeViewProtected.setAlpha(float(value));
}
+ [testIDProperty.setNative](value: string) {
+ this.setTestID(this.nativeViewProtected, value);
+ }
+
+ setTestID(view, value) {
+ if (typeof __USE_TEST_ID__ !== 'undefined' && __USE_TEST_ID__) {
+ const id = Utils.ad.resources.getId(':id/nativescript_accessibility_id');
+
+ if (id) {
+ view.setTag(id, value);
+ view.setTag(value);
+ }
+
+ view.setContentDescription(value);
+ }
+ }
+
[accessibilityEnabledProperty.setNative](value: boolean): void {
this.nativeViewProtected.setFocusable(!!value);
@@ -803,11 +820,15 @@ export class View extends ViewCommon {
}
[accessibilityIdentifierProperty.setNative](value: string): void {
- const id = Utils.ad.resources.getId(':id/nativescript_accessibility_id');
+ if (typeof __USE_TEST_ID__ !== 'undefined' && __USE_TEST_ID__ && this.testID) {
+ // ignore when using testID;
+ } else {
+ const id = Utils.ad.resources.getId(':id/nativescript_accessibility_id');
- if (id) {
- this.nativeViewProtected.setTag(id, value);
- this.nativeViewProtected.setTag(value);
+ if (id) {
+ this.nativeViewProtected.setTag(id, value);
+ this.nativeViewProtected.setTag(value);
+ }
}
}
diff --git a/packages/core/ui/core/view/index.ios.ts b/packages/core/ui/core/view/index.ios.ts
index dfc05b561..b91bf1eb3 100644
--- a/packages/core/ui/core/view/index.ios.ts
+++ b/packages/core/ui/core/view/index.ios.ts
@@ -2,7 +2,7 @@
import { Point, View as ViewDefinition } from '.';
// Requires
-import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty } from './view-common';
+import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty } from './view-common';
import { ShowModalOptions, hiddenProperty } from '../view-base';
import { Trace } from '../../../trace';
import { layout, iOSNativeHelper } from '../../../utils';
@@ -572,6 +572,16 @@ export class View extends ViewCommon implements ViewDefinition {
this.updateOriginPoint(this.originX, value);
}
+ [testIDProperty.setNative](value: string) {
+ this.setTestID(this.nativeViewProtected, value);
+ }
+
+ public setTestID(view: any, value: string): void {
+ if (typeof __USE_TEST_ID__ !== 'undefined' && __USE_TEST_ID__) {
+ view.accessibilityIdentifier = value;
+ }
+ }
+
[accessibilityEnabledProperty.setNative](value: boolean): void {
this.nativeViewProtected.isAccessibilityElement = !!value;
@@ -581,8 +591,13 @@ export class View extends ViewCommon implements ViewDefinition {
[accessibilityIdentifierProperty.getDefault](): string {
return this.nativeViewProtected.accessibilityLabel;
}
+
[accessibilityIdentifierProperty.setNative](value: string): void {
- this.nativeViewProtected.accessibilityIdentifier = value;
+ if (typeof __USE_TEST_ID__ !== 'undefined' && __USE_TEST_ID__ && this.testID) {
+ // ignore when using testID
+ } else {
+ this.nativeViewProtected.accessibilityIdentifier = value;
+ }
}
[accessibilityRoleProperty.setNative](value: AccessibilityRole): void {
diff --git a/packages/core/ui/core/view/view-common.ts b/packages/core/ui/core/view/view-common.ts
index cce5a3838..298faa451 100644
--- a/packages/core/ui/core/view/view-common.ts
+++ b/packages/core/ui/core/view/view-common.ts
@@ -82,6 +82,8 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
public accessibilityValue: string;
public accessibilityHint: string;
+ public testID: string;
+
public touchAnimation: boolean | TouchAnimationOptions;
public ignoreTouchAnimation: boolean;
@@ -1119,6 +1121,10 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
public accessibilityScreenChanged(): void {
return;
}
+
+ public setTestID(view: any, value: string) {
+ return;
+ }
}
export const originXProperty = new Property({
@@ -1196,6 +1202,11 @@ const ignoreTouchAnimationProperty = new Property({
});
ignoreTouchAnimationProperty.register(ViewCommon);
+export const testIDProperty = new Property({
+ name: 'testID',
+});
+testIDProperty.register(ViewCommon);
+
accessibilityIdentifierProperty.register(ViewCommon);
accessibilityLabelProperty.register(ViewCommon);
accessibilityValueProperty.register(ViewCommon);
diff --git a/packages/core/ui/text-base/index.android.ts b/packages/core/ui/text-base/index.android.ts
index 6e6966591..32944e514 100644
--- a/packages/core/ui/text-base/index.android.ts
+++ b/packages/core/ui/text-base/index.android.ts
@@ -15,6 +15,7 @@ import { layout } from '../../utils';
import { isString, isNullOrUndefined } from '../../utils/types';
import { accessibilityIdentifierProperty } from '../../accessibility/accessibility-properties';
import * as Utils from '../../utils';
+import { testIDProperty } from '../../ui/core/view';
export * from './text-base-common';
@@ -443,13 +444,21 @@ export class TextBase extends TextBaseCommon {
org.nativescript.widgets.ViewHelper.setPaddingLeft(this.nativeTextViewProtected, Length.toDevicePixels(value, 0) + Length.toDevicePixels(this.style.borderLeftWidth, 0));
}
- [accessibilityIdentifierProperty.setNative](value: string): void {
- // we override the default setter to apply it on nativeTextViewProtected
- const id = Utils.ad.resources.getId(':id/nativescript_accessibility_id');
+ [testIDProperty.setNative](value: string): void {
+ this.setTestID(this.nativeTextViewProtected, value);
+ }
- if (id) {
- this.nativeTextViewProtected.setTag(id, value);
- this.nativeTextViewProtected.setTag(value);
+ [accessibilityIdentifierProperty.setNative](value: string): void {
+ if (typeof __USE_TEST_ID__ !== 'undefined' && __USE_TEST_ID__ && this.testID) {
+ // ignore when using testID;
+ } else {
+ // we override the default setter to apply it on nativeTextViewProtected
+ const id = Utils.ad.resources.getId(':id/nativescript_accessibility_id');
+
+ if (id) {
+ this.nativeTextViewProtected.setTag(id, value);
+ this.nativeTextViewProtected.setTag(value);
+ }
}
}
diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/angular.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/angular.spec.ts.snap
index d6ad59889..352881686 100644
--- a/packages/webpack5/__tests__/configuration/__snapshots__/angular.spec.ts.snap
+++ b/packages/webpack5/__tests__/configuration/__snapshots__/angular.spec.ts.snap
@@ -351,7 +351,8 @@ exports[`angular configuration for android 1`] = `
__IOS__: false,
'global.isAndroid': true,
'global.isIOS': false,
- process: 'global.process'
+ process: 'global.process',
+ __USE_TEST_ID__: false
}
),
/* config.plugin('CopyWebpackPlugin') */
@@ -774,7 +775,8 @@ exports[`angular configuration for ios 1`] = `
__IOS__: true,
'global.isAndroid': false,
'global.isIOS': true,
- process: 'global.process'
+ process: 'global.process',
+ __USE_TEST_ID__: false
}
),
/* config.plugin('CopyWebpackPlugin') */
diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/base.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/base.spec.ts.snap
index e6e48102c..751e44ec4 100644
--- a/packages/webpack5/__tests__/configuration/__snapshots__/base.spec.ts.snap
+++ b/packages/webpack5/__tests__/configuration/__snapshots__/base.spec.ts.snap
@@ -262,7 +262,8 @@ exports[`base configuration for android 1`] = `
__IOS__: false,
'global.isAndroid': true,
'global.isIOS': false,
- process: 'global.process'
+ process: 'global.process',
+ __USE_TEST_ID__: false
}
),
/* config.plugin('CopyWebpackPlugin') */
@@ -582,7 +583,8 @@ exports[`base configuration for ios 1`] = `
__IOS__: true,
'global.isAndroid': false,
'global.isIOS': true,
- process: 'global.process'
+ process: 'global.process',
+ __USE_TEST_ID__: false
}
),
/* config.plugin('CopyWebpackPlugin') */
diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/javascript.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/javascript.spec.ts.snap
index 098dd5a2e..a72f3b8de 100644
--- a/packages/webpack5/__tests__/configuration/__snapshots__/javascript.spec.ts.snap
+++ b/packages/webpack5/__tests__/configuration/__snapshots__/javascript.spec.ts.snap
@@ -262,7 +262,8 @@ exports[`javascript configuration for android 1`] = `
__IOS__: false,
'global.isAndroid': true,
'global.isIOS': false,
- process: 'global.process'
+ process: 'global.process',
+ __USE_TEST_ID__: false
}
),
/* config.plugin('CopyWebpackPlugin') */
@@ -591,7 +592,8 @@ exports[`javascript configuration for ios 1`] = `
__IOS__: true,
'global.isAndroid': false,
'global.isIOS': true,
- process: 'global.process'
+ process: 'global.process',
+ __USE_TEST_ID__: false
}
),
/* config.plugin('CopyWebpackPlugin') */
diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap
index 92c19dea8..f78fa4ae0 100644
--- a/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap
+++ b/packages/webpack5/__tests__/configuration/__snapshots__/react.spec.ts.snap
@@ -285,6 +285,7 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR
'global.isAndroid': true,
'global.isIOS': false,
process: 'global.process',
+ __USE_TEST_ID__: false,
__TEST__: false,
'process.env.NODE_ENV': '\\"development\\"'
}
@@ -616,6 +617,7 @@ exports[`react configuration > android > base config 1`] = `
'global.isAndroid': true,
'global.isIOS': false,
process: 'global.process',
+ __USE_TEST_ID__: false,
__TEST__: false,
'process.env.NODE_ENV': '\\"development\\"'
}
@@ -954,6 +956,7 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena
'global.isAndroid': false,
'global.isIOS': true,
process: 'global.process',
+ __USE_TEST_ID__: false,
__TEST__: false,
'process.env.NODE_ENV': '\\"development\\"'
}
@@ -1286,6 +1289,7 @@ exports[`react configuration > ios > base config 1`] = `
'global.isAndroid': false,
'global.isIOS': true,
process: 'global.process',
+ __USE_TEST_ID__: false,
__TEST__: false,
'process.env.NODE_ENV': '\\"development\\"'
}
diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/svelte.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/svelte.spec.ts.snap
index 76398655e..49d960c77 100644
--- a/packages/webpack5/__tests__/configuration/__snapshots__/svelte.spec.ts.snap
+++ b/packages/webpack5/__tests__/configuration/__snapshots__/svelte.spec.ts.snap
@@ -289,7 +289,8 @@ exports[`svelte configuration for android 1`] = `
__IOS__: false,
'global.isAndroid': true,
'global.isIOS': false,
- process: 'global.process'
+ process: 'global.process',
+ __USE_TEST_ID__: false
}
),
/* config.plugin('CopyWebpackPlugin') */
@@ -630,7 +631,8 @@ exports[`svelte configuration for ios 1`] = `
__IOS__: true,
'global.isAndroid': false,
'global.isIOS': true,
- process: 'global.process'
+ process: 'global.process',
+ __USE_TEST_ID__: false
}
),
/* config.plugin('CopyWebpackPlugin') */
diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/typescript.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/typescript.spec.ts.snap
index fd5828fc1..5831fac29 100644
--- a/packages/webpack5/__tests__/configuration/__snapshots__/typescript.spec.ts.snap
+++ b/packages/webpack5/__tests__/configuration/__snapshots__/typescript.spec.ts.snap
@@ -262,7 +262,8 @@ exports[`typescript configuration for android 1`] = `
__IOS__: false,
'global.isAndroid': true,
'global.isIOS': false,
- process: 'global.process'
+ process: 'global.process',
+ __USE_TEST_ID__: false
}
),
/* config.plugin('CopyWebpackPlugin') */
@@ -591,7 +592,8 @@ exports[`typescript configuration for ios 1`] = `
__IOS__: true,
'global.isAndroid': false,
'global.isIOS': true,
- process: 'global.process'
+ process: 'global.process',
+ __USE_TEST_ID__: false
}
),
/* config.plugin('CopyWebpackPlugin') */
diff --git a/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap b/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap
index f5c5ae2e3..9c21da6c7 100644
--- a/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap
+++ b/packages/webpack5/__tests__/configuration/__snapshots__/vue.spec.ts.snap
@@ -302,7 +302,8 @@ exports[`vue configuration for android 1`] = `
__IOS__: false,
'global.isAndroid': true,
'global.isIOS': false,
- process: 'global.process'
+ process: 'global.process',
+ __USE_TEST_ID__: false
}
),
/* config.plugin('CopyWebpackPlugin') */
@@ -656,7 +657,8 @@ exports[`vue configuration for ios 1`] = `
__IOS__: true,
'global.isAndroid': false,
'global.isIOS': true,
- process: 'global.process'
+ process: 'global.process',
+ __USE_TEST_ID__: false
}
),
/* config.plugin('CopyWebpackPlugin') */
diff --git a/packages/webpack5/src/configuration/base.ts b/packages/webpack5/src/configuration/base.ts
index d8548711f..bea1db718 100644
--- a/packages/webpack5/src/configuration/base.ts
+++ b/packages/webpack5/src/configuration/base.ts
@@ -425,6 +425,9 @@ export default function (config: Config, env: IWebpackEnv = _env): Config {
/* for compat only */ 'global.isIOS': platform === 'ios',
process: 'global.process',
+ // enable testID when using --env.e2e
+ __USE_TEST_ID__: !!env.e2e,
+
// todo: ?!?!
// profile: '() => {}',
},
diff --git a/packages/webpack5/src/index.ts b/packages/webpack5/src/index.ts
index 47ecb41ce..39c2e1c8e 100644
--- a/packages/webpack5/src/index.ts
+++ b/packages/webpack5/src/index.ts
@@ -49,6 +49,7 @@ export interface IWebpackEnv {
// misc
replace?: string[] | string;
watchNodeModules?: boolean;
+ e2e?: boolean;
}
interface IChainEntry {