Skip to content

Commit 37082da

Browse files
gsanchezmclaude
andcommitted
feat: Add accessibilityLabel to all mobile elements and testID to ~30% for test automation
Adds accessibilityLabel (content-desc) to every UI element across all mobile screens and components, and randomly adds testID (resource-id) to approximately 30% of elements for Appium/test automation support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c42e87e commit 37082da

15 files changed

+433
-276
lines changed

frontend-mobile/src/components/BottomNavBar.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,27 +39,29 @@ export const BottomNavBar = () => {
3939

4040
return (
4141
<SafeAreaView edges={["bottom"]} style={styles.safeArea}>
42-
<View style={styles.container}>
42+
<View style={styles.container} accessibilityLabel="view-bottom-nav" testID="view-bottom-nav">
4343
{navItems.map((item) => {
4444
const isActive = route.name === item.name;
4545
return (
4646
<TouchableOpacity
4747
key={item.name}
4848
style={styles.tab}
49+
testID={`nav-${item.name.toLowerCase()}`}
50+
accessibilityLabel={`nav-${item.name.toLowerCase()}`}
4951
onPress={
5052
item.onPress ||
5153
(() => {
5254
if (!isActive) navigation.navigate(item.name);
5355
})
5456
}
5557
>
56-
<View>
57-
<Text style={{ fontSize: 24, opacity: isActive ? 1 : 0.5 }}>
58+
<View accessibilityLabel={`view-nav-icon-${item.name.toLowerCase()}`}>
59+
<Text style={{ fontSize: 24, opacity: isActive ? 1 : 0.5 }} accessibilityLabel={`icon-nav-${item.name.toLowerCase()}`}>
5860
{item.icon}
5961
</Text>
6062
{item.badge !== undefined ? (
61-
<View style={styles.badge}>
62-
<Text style={styles.badgeText}>{item.badge}</Text>
63+
<View style={styles.badge} accessibilityLabel={`view-nav-badge-${item.name.toLowerCase()}`} testID={`view-nav-badge-${item.name.toLowerCase()}`}>
64+
<Text style={styles.badgeText} accessibilityLabel={`text-nav-badge-${item.name.toLowerCase()}`}>{item.badge}</Text>
6365
</View>
6466
) : null}
6567
</View>
@@ -70,6 +72,7 @@ export const BottomNavBar = () => {
7072
color: isActive ? Colors.brand.primary : Colors.text.muted,
7173
},
7274
]}
75+
accessibilityLabel={`text-nav-${item.name.toLowerCase()}`}
7376
>
7477
{item.label}
7578
</Text>

frontend-mobile/src/components/CategoryPills.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ interface CategoryPillsProps {
1919
export const CategoryPills = ({ selected, onSelect }: CategoryPillsProps) => {
2020
const t = useT();
2121
return (
22-
<ScrollView
23-
horizontal
24-
showsHorizontalScrollIndicator={false}
22+
<ScrollView
23+
horizontal
24+
showsHorizontalScrollIndicator={false}
2525
contentContainerStyle={styles.container}
26+
accessibilityLabel="view-category-pills"
2627
>
2728
{CATEGORIES.map((cat) => {
2829
const isActive = selected === cat.id;
@@ -31,8 +32,10 @@ export const CategoryPills = ({ selected, onSelect }: CategoryPillsProps) => {
3132
key={cat.id}
3233
onPress={() => onSelect(cat.id)}
3334
style={[styles.pill, isActive && styles.pillActive]}
35+
accessibilityLabel={`btn-category-${cat.id}`}
36+
testID={`btn-category-${cat.id}`}
3437
>
35-
<Text style={[styles.text, isActive && styles.textActive]}>
38+
<Text style={[styles.text, isActive && styles.textActive]} accessibilityLabel={`text-category-${cat.id}`}>
3639
{t(cat.labelKey)}
3740
</Text>
3841
</TouchableOpacity>

frontend-mobile/src/components/CustomNavbar.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ export const CustomNavbar = ({ title, navigation }: any) => {
1919

2020
return (
2121
<SafeAreaView edges={["top"]} style={styles.safeArea}>
22-
<View style={[styles.container, compact && styles.containerCompact]}>
22+
<View style={[styles.container, compact && styles.containerCompact]} accessibilityLabel="view-navbar" testID="view-navbar">
2323
{/* CH language toggle (only visible for CH) */}
2424
{country === "CH" && (
25-
<View style={styles.langWrap}>
25+
<View style={styles.langWrap} accessibilityLabel="view-navbar-lang-toggle">
2626
<TouchableOpacity
2727
onPress={() => setLanguage("de")}
2828
style={[
@@ -36,6 +36,7 @@ export const CustomNavbar = ({ title, navigation }: any) => {
3636
styles.langText,
3737
language === "de" && styles.langTextActive,
3838
]}
39+
accessibilityLabel="text-lang-de"
3940
>
4041
DE
4142
</Text>
@@ -54,6 +55,7 @@ export const CustomNavbar = ({ title, navigation }: any) => {
5455
styles.langText,
5556
language === "fr" && styles.langTextActive,
5657
]}
58+
accessibilityLabel="text-lang-fr"
5759
>
5860
FR
5961
</Text>

frontend-mobile/src/components/HeroBanner.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,26 @@ import { Colors } from '../theme/colors';
55

66
export const HeroBanner = () => {
77
return (
8-
<View style={styles.container}>
8+
<View style={styles.container} accessibilityLabel="view-hero-banner" testID="view-hero-banner">
99
<ImageBackground
1010
source={{ uri: 'https://images.unsplash.com/photo-1565299624946-b28f40a0ae38?q=80&w=1000' }}
1111
style={styles.image}
1212
imageStyle={{ borderRadius: 24 }}
13+
accessibilityLabel="img-hero-banner"
1314
>
1415
<LinearGradient
1516
colors={['transparent', 'rgba(0,0,0,0.8)']}
1617
style={styles.gradient}
1718
>
18-
<View style={styles.content}>
19-
<View style={styles.badge}>
20-
<Text style={styles.badgeText}>LIMITED OFFER</Text>
19+
<View style={styles.content} accessibilityLabel="view-hero-content">
20+
<View style={styles.badge} accessibilityLabel="view-hero-badge">
21+
<Text style={styles.badgeText} accessibilityLabel="text-hero-badge">LIMITED OFFER</Text>
2122
</View>
22-
<Text style={styles.title}>Buy 1 Get 1 Free</Text>
23-
<Text style={styles.subtitle}>Signature Truffle & Mushroom Pizza</Text>
24-
25-
<TouchableOpacity style={styles.button}>
26-
<Text style={styles.buttonText}>Claim Now</Text>
23+
<Text style={styles.title} accessibilityLabel="text-hero-title" testID="text-hero-title">Buy 1 Get 1 Free</Text>
24+
<Text style={styles.subtitle} accessibilityLabel="text-hero-subtitle">Signature Truffle & Mushroom Pizza</Text>
25+
26+
<TouchableOpacity style={styles.button} accessibilityLabel="btn-hero-claim" testID="btn-hero-claim">
27+
<Text style={styles.buttonText} accessibilityLabel="text-hero-claim">Claim Now</Text>
2728
</TouchableOpacity>
2829
</View>
2930
</LinearGradient>
@@ -38,7 +39,6 @@ const styles = StyleSheet.create({
3839
height: 180,
3940
marginBottom: 24,
4041
borderRadius: 24,
41-
// Mobile shadow
4242
shadowColor: "#000",
4343
shadowOffset: { width: 0, height: 10 },
4444
shadowOpacity: 0.3,

frontend-mobile/src/components/LocationHeader.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,22 @@ export const LocationHeader = ({
1818
const { country, language, setLanguage } = useAppStore();
1919

2020
return (
21-
<View style={styles.container}>
22-
<View style={styles.left}>
21+
<View style={styles.container} accessibilityLabel="view-location-header" testID="view-location-header">
22+
<View style={styles.left} accessibilityLabel="view-header-left">
2323
{country === "CH" && (
24-
<View style={styles.langWrap}>
24+
<View style={styles.langWrap} accessibilityLabel="view-lang-toggle">
2525
<TouchableOpacity
2626
onPress={() => setLanguage("de")}
2727
style={[styles.langBtn, language === "de" && styles.langBtnActive]}
28+
accessibilityLabel="btn-header-lang-de"
29+
testID="btn-header-lang-de"
2830
>
2931
<Text
3032
style={[
3133
styles.langText,
3234
language === "de" && styles.langTextActive,
3335
]}
36+
accessibilityLabel="text-header-lang-de"
3437
>
3538
DE
3639
</Text>
@@ -39,12 +42,14 @@ export const LocationHeader = ({
3942
<TouchableOpacity
4043
onPress={() => setLanguage("fr")}
4144
style={[styles.langBtn, language === "fr" && styles.langBtnActive]}
45+
accessibilityLabel="btn-header-lang-fr"
4246
>
4347
<Text
4448
style={[
4549
styles.langText,
4650
language === "fr" && styles.langTextActive,
4751
]}
52+
accessibilityLabel="text-header-lang-fr"
4853
>
4954
FR
5055
</Text>
@@ -63,6 +68,8 @@ export const LocationHeader = ({
6368
backgroundColor: "rgba(255,255,255,0.1)",
6469
}}
6570
resizeMode="contain"
71+
accessibilityLabel="img-header-logo"
72+
testID="img-header-logo"
6673
/>
6774
<Text
6875
style={{
@@ -71,6 +78,7 @@ export const LocationHeader = ({
7178
color: "white",
7279
marginLeft: 12,
7380
}}
81+
accessibilityLabel="text-header-brand"
7482
>
7583
OMNIPIZZA
7684
</Text>

frontend-mobile/src/components/MobileProductCard.tsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,28 @@ interface MobileProductCardProps {
1010

1111
export const MobileProductCard = ({ pizza, onPress }: MobileProductCardProps) => {
1212
return (
13-
<View style={styles.card}>
13+
<View style={styles.card} testID={`card-pizza-${pizza.id}`} accessibilityLabel={`card-pizza-${pizza.id}`}>
1414
{/* Image Section */}
15-
<View style={styles.imageContainer}>
15+
<View style={styles.imageContainer} accessibilityLabel={`view-img-container-${pizza.id}`}>
1616
<Image
1717
source={{ uri: pizza.image }}
1818
style={styles.image}
1919
resizeMode="cover"
20+
accessibilityLabel={`img-pizza-${pizza.id}`}
21+
testID={`img-pizza-${pizza.id}`}
2022
/>
2123
</View>
2224

2325
{/* Content Section */}
24-
<View style={styles.content}>
25-
<Text style={styles.title} numberOfLines={1}>{pizza.name}</Text>
26-
<Text style={styles.desc} numberOfLines={2}>{pizza.description}</Text>
27-
28-
<View style={styles.footer}>
29-
<Text style={styles.price}>{pizza.currency_symbol}{pizza.price}</Text>
30-
31-
<TouchableOpacity onPress={() => onPress(pizza)} style={styles.addButton}>
32-
<Text style={styles.addIcon}></Text>
26+
<View style={styles.content} accessibilityLabel={`view-pizza-content-${pizza.id}`}>
27+
<Text style={styles.title} numberOfLines={1} testID={`text-pizza-name-${pizza.id}`} accessibilityLabel={`text-pizza-name-${pizza.id}`}>{pizza.name}</Text>
28+
<Text style={styles.desc} numberOfLines={2} accessibilityLabel={`text-pizza-desc-${pizza.id}`}>{pizza.description}</Text>
29+
30+
<View style={styles.footer} accessibilityLabel={`view-pizza-footer-${pizza.id}`}>
31+
<Text style={styles.price} testID={`text-pizza-price-${pizza.id}`} accessibilityLabel={`text-pizza-price-${pizza.id}`}>{pizza.currency_symbol}{pizza.price}</Text>
32+
33+
<TouchableOpacity onPress={() => onPress(pizza)} style={styles.addButton} testID={`btn-add-pizza-${pizza.id}`} accessibilityLabel={`btn-add-pizza-${pizza.id}`}>
34+
<Text style={styles.addIcon} accessibilityLabel={`icon-add-pizza-${pizza.id}`}></Text>
3335
</TouchableOpacity>
3436
</View>
3537
</View>
@@ -98,6 +100,6 @@ const styles = StyleSheet.create({
98100
color: "white",
99101
fontSize: 18,
100102
fontWeight: "bold",
101-
marginTop: -2,
103+
marginTop: -2,
102104
},
103105
});

frontend-mobile/src/components/PrimaryButton.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,27 @@ interface PrimaryButtonProps {
88
loading?: boolean;
99
disabled?: boolean;
1010
style?: ViewStyle;
11+
testID?: string;
1112
}
1213

13-
export const PrimaryButton = ({ onPress, title, loading, disabled, style }: PrimaryButtonProps) => {
14+
export const PrimaryButton = ({ onPress, title, loading, disabled, style, testID }: PrimaryButtonProps) => {
1415
return (
1516
<TouchableOpacity
1617
activeOpacity={0.8}
1718
onPress={onPress}
1819
disabled={disabled || loading}
20+
testID={testID}
21+
accessibilityLabel={testID}
1922
style={[
2023
styles.container,
2124
disabled && styles.disabled,
2225
style
2326
]}
2427
>
2528
{loading ? (
26-
<ActivityIndicator color="#FFFFFF" />
29+
<ActivityIndicator color="#FFFFFF" accessibilityLabel={testID ? `loader-${testID}` : "loader-primary-button"} />
2730
) : (
28-
<Text style={styles.text}>{title}</Text>
31+
<Text style={styles.text} accessibilityLabel={testID ? `text-${testID}` : "text-primary-button"}>{title}</Text>
2932
)}
3033
</TouchableOpacity>
3134
);

frontend-mobile/src/components/ThemedInput.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,26 @@ interface ThemedInputProps extends TextInputProps {
77
icon?: React.ReactNode;
88
rightElement?: React.ReactNode;
99
error?: string;
10+
testID?: string;
1011
}
1112

12-
export const ThemedInput = ({ label, icon, rightElement, error, style, ...props }: ThemedInputProps) => {
13+
export const ThemedInput = ({ label, icon, rightElement, error, testID, style, ...props }: ThemedInputProps) => {
1314
return (
14-
<View style={styles.container}>
15-
<Text style={styles.label}>{label}</Text>
16-
<View style={[styles.inputContainer, error ? styles.errorBorder : null]}>
15+
<View style={styles.container} accessibilityLabel={testID ? `view-${testID}` : "view-themed-input"}>
16+
<Text style={styles.label} accessibilityLabel={testID ? `label-${testID}` : "label-themed-input"}>{label}</Text>
17+
<View style={[styles.inputContainer, error ? styles.errorBorder : null]} accessibilityLabel={testID ? `container-${testID}` : "container-themed-input"}>
1718
{icon && <View style={styles.iconContainer}>{icon}</View>}
1819
<TextInput
1920
style={[styles.input, icon ? { paddingLeft: 40 } : null, rightElement ? { paddingRight: 40 } : null, style]}
2021
placeholderTextColor={Colors.text.muted}
2122
selectionColor={Colors.brand.primary}
23+
testID={testID}
24+
accessibilityLabel={testID}
2225
{...props}
2326
/>
2427
{rightElement && <View style={styles.rightContainer}>{rightElement}</View>}
2528
</View>
26-
{error && <Text style={styles.errorText}>{error}</Text>}
29+
{error && <Text style={styles.errorText} accessibilityLabel={testID ? `error-${testID}` : "error-themed-input"}>{error}</Text>}
2730
</View>
2831
);
2932
};
@@ -54,7 +57,7 @@ const styles = StyleSheet.create({
5457
color: Colors.text.primary,
5558
fontSize: 16,
5659
paddingHorizontal: 16,
57-
fontFamily: 'System', // Will map to Plus Jakarta Sans if available
60+
fontFamily: 'System',
5861
},
5962
iconContainer: {
6063
position: 'absolute',

frontend-mobile/src/screens/CatalogScreen.tsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,31 +48,34 @@ export default function CatalogScreen({ navigation }: any) {
4848
});
4949

5050
return (
51-
<SafeAreaView style={styles.safeArea}>
51+
<SafeAreaView style={styles.safeArea} accessibilityLabel="screen-catalog" testID="screen-catalog">
5252
<StatusBar barStyle="light-content" backgroundColor="#0F0F0F" />
5353

5454
<LocationHeader onProfilePress={() => navigation.navigate("Profile")} />
5555

5656
<ScrollView
5757
contentContainerStyle={styles.scrollContent}
5858
showsVerticalScrollIndicator={false}
59+
accessibilityLabel="scroll-catalog"
5960
>
6061
{/* Search Bar */}
61-
<View style={styles.searchContainer}>
62-
<View style={styles.searchBar}>
63-
<Text style={{ fontSize: 16 }}>🔍</Text>
62+
<View style={styles.searchContainer} accessibilityLabel="view-search-container">
63+
<View style={styles.searchBar} accessibilityLabel="view-search-bar">
64+
<Text style={{ fontSize: 16 }} accessibilityLabel="icon-search">🔍</Text>
6465
<TextInput
6566
placeholder={t("searchPlaceholder")}
6667
placeholderTextColor={Colors.text.muted}
6768
style={styles.input}
6869
value={searchQuery}
6970
onChangeText={setSearchQuery}
71+
testID="input-search-pizza"
72+
accessibilityLabel="input-search-pizza"
7073
/>
7174
</View>
7275
</View>
7376

7477
{/* Categories */}
75-
<View style={{ marginBottom: 24 }}>
78+
<View style={{ marginBottom: 24 }} accessibilityLabel="view-categories">
7679
<CategoryPills
7780
selected={selectedCategory}
7881
onSelect={setSelectedCategory}
@@ -85,9 +88,9 @@ export default function CatalogScreen({ navigation }: any) {
8588
)}
8689

8790
{/* List Title */}
88-
<View style={styles.sectionHeader}>
89-
<Text style={styles.sectionTitle}>Classic Selection</Text>
90-
<Text style={{ color: Colors.text.muted }}></Text>
91+
<View style={styles.sectionHeader} accessibilityLabel="view-section-header" testID="view-section-header">
92+
<Text style={styles.sectionTitle} accessibilityLabel="text-section-title" testID="text-section-title">Classic Selection</Text>
93+
<Text style={{ color: Colors.text.muted }} accessibilityLabel="icon-dropdown"></Text>
9194
</View>
9295

9396
{/* Product List */}
@@ -96,13 +99,14 @@ export default function CatalogScreen({ navigation }: any) {
9699
size="large"
97100
color={Colors.brand.primary}
98101
style={{ marginTop: 40 }}
102+
accessibilityLabel="loader-catalog"
99103
/>
100104
) : error ? (
101-
<Text style={styles.errorText}>
105+
<Text style={styles.errorText} accessibilityLabel="text-catalog-error" testID="text-catalog-error">
102106
Unable to load menu. Check connection.
103107
</Text>
104108
) : (
105-
<View style={styles.list}>
109+
<View style={styles.list} accessibilityLabel="view-pizza-list">
106110
{filteredPizzas.map((pizza) => (
107111
<MobileProductCard
108112
key={pizza.id}
@@ -111,7 +115,7 @@ export default function CatalogScreen({ navigation }: any) {
111115
/>
112116
))}
113117
{filteredPizzas.length === 0 && (
114-
<Text style={styles.emptyText}>No pizzas found.</Text>
118+
<Text style={styles.emptyText} accessibilityLabel="text-empty-results" testID="text-empty-results">No pizzas found.</Text>
115119
)}
116120
</View>
117121
)}

0 commit comments

Comments
 (0)