mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
feat: testID property for use with e2e testing without interfering with a11y (#9793)
* fix(android): nested frames were sometimes not recreated (#9748) Co-authored-by: Eduardo Speroni <edusperoni@gmail.com> * feat: testID property for use with e2e testing without interfering with a11y * feat: better testID support along a11y wip * fix: make sure we have a defined id * feat: --env.e2e to enable testID * chore: return if using testID * chore: cleanup Co-authored-by: Eduardo Speroni <edusperoni@gmail.com> Co-authored-by: Igor Randjelovic <rigor789@gmail.com>
This commit is contained in:
@@ -645,6 +645,11 @@ function applyContentDescription(view: Partial<View>, 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);
|
||||
|
||||
1
packages/core/global-types.d.ts
vendored
1
packages/core/global-types.d.ts
vendored
@@ -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;
|
||||
|
||||
|
||||
4
packages/core/platforms/android/res/values/ids.xml
Normal file
4
packages/core/platforms/android/res/values/ids.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item type="id" name="nativescript_accessibility_id"/>
|
||||
</resources>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<ViewCommon, number>({
|
||||
@@ -1196,6 +1202,11 @@ const ignoreTouchAnimationProperty = new Property<ViewCommon, boolean>({
|
||||
});
|
||||
ignoreTouchAnimationProperty.register(ViewCommon);
|
||||
|
||||
export const testIDProperty = new Property<ViewCommon, string>({
|
||||
name: 'testID',
|
||||
});
|
||||
testIDProperty.register(ViewCommon);
|
||||
|
||||
accessibilityIdentifierProperty.register(ViewCommon);
|
||||
accessibilityLabelProperty.register(ViewCommon);
|
||||
accessibilityValueProperty.register(ViewCommon);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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') */
|
||||
|
||||
@@ -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') */
|
||||
|
||||
@@ -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') */
|
||||
|
||||
@@ -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\\"'
|
||||
}
|
||||
|
||||
@@ -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') */
|
||||
|
||||
@@ -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') */
|
||||
|
||||
@@ -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') */
|
||||
|
||||
@@ -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: '() => {}',
|
||||
},
|
||||
|
||||
@@ -49,6 +49,7 @@ export interface IWebpackEnv {
|
||||
// misc
|
||||
replace?: string[] | string;
|
||||
watchNodeModules?: boolean;
|
||||
e2e?: boolean;
|
||||
}
|
||||
|
||||
interface IChainEntry {
|
||||
|
||||
Reference in New Issue
Block a user