diff --git a/apps/mobile/app/(tabs)/_layout.tsx b/apps/mobile/app/(tabs)/_layout.tsx
index d96fb0e88..3efcc6a51 100644
--- a/apps/mobile/app/(tabs)/_layout.tsx
+++ b/apps/mobile/app/(tabs)/_layout.tsx
@@ -27,6 +27,7 @@ export default function TabLayout() {
name="index"
options={{
title: "Home",
+ tabBarButtonTestID: "tab-home",
tabBarIcon: ({ color, size }) => (
),
@@ -36,6 +37,7 @@ export default function TabLayout() {
name="watch"
options={{
title: "Discover",
+ tabBarButtonTestID: "tab-discover",
headerShown: true,
headerTitle: "Discover",
headerStyle: { backgroundColor: BG_COLOR },
@@ -50,6 +52,7 @@ export default function TabLayout() {
name="library"
options={{
title: "Library",
+ tabBarButtonTestID: "tab-library",
tabBarIcon: ({ color, size }) => (
),
@@ -59,6 +62,7 @@ export default function TabLayout() {
name="profile"
options={{
title: "Profile",
+ tabBarButtonTestID: "tab-profile",
tabBarIcon: ({ color, size }) => (
),
diff --git a/apps/mobile/app/(tabs)/library.tsx b/apps/mobile/app/(tabs)/library.tsx
index aaed47bc6..8a8c21a36 100644
--- a/apps/mobile/app/(tabs)/library.tsx
+++ b/apps/mobile/app/(tabs)/library.tsx
@@ -96,9 +96,10 @@ export default function LibraryScreen() {
data={experiences}
keyExtractor={(item) => item.documentId}
contentContainerStyle={styles.listContent}
- renderItem={({ item }) => (
+ renderItem={({ item, index }) => (
@@ -112,6 +113,7 @@ export default function LibraryScreen() {
function ExperienceCard({
experience,
+ index,
isActive,
onSelect,
}: {
@@ -122,6 +124,7 @@ function ExperienceCard({
metaDescription: string | null
ogImage: { url: string; alternativeText: string | null } | null
}
+ index: number
isActive: boolean
onSelect: (slug: string) => void
}) {
@@ -130,6 +133,7 @@ function ExperienceCard({
return (
onSelect(experience.slug)}
style={[styles.card, isActive && styles.cardActive]}
accessibilityRole="button"
diff --git a/apps/mobile/app/(tabs)/watch.tsx b/apps/mobile/app/(tabs)/watch.tsx
index e4cde556d..f719ba228 100644
--- a/apps/mobile/app/(tabs)/watch.tsx
+++ b/apps/mobile/app/(tabs)/watch.tsx
@@ -257,6 +257,7 @@ export default function DiscoverScreen() {
(
router.back()}
accessibilityRole="button"
accessibilityLabel="Go back"
@@ -204,6 +205,7 @@ export default function RootLayout() {
headerTitleAlign: "center",
headerLeft: () => (
router.back()}
accessibilityRole="button"
accessibilityLabel="Go back"
diff --git a/apps/mobile/app/collection/[sectionKey].tsx b/apps/mobile/app/collection/[sectionKey].tsx
index e4a6229c3..d7aaf3806 100644
--- a/apps/mobile/app/collection/[sectionKey].tsx
+++ b/apps/mobile/app/collection/[sectionKey].tsx
@@ -247,6 +247,7 @@ function CollectionPlayerContent({
return (
[
styles.row,
isActive && styles.rowActive,
diff --git a/apps/mobile/app/video/[sectionKey].tsx b/apps/mobile/app/video/[sectionKey].tsx
index 54009bd99..b54189139 100644
--- a/apps/mobile/app/video/[sectionKey].tsx
+++ b/apps/mobile/app/video/[sectionKey].tsx
@@ -97,6 +97,7 @@ function VideoDetailContent({
headerTitle: title ?? "",
headerRight: () => (
{
const parts = [`Check out "${displayTitle}" on JesusFilm!`]
if (shareUrl != null) parts.push(shareUrl)
@@ -201,6 +202,7 @@ function VideoDetailContent({
/>
{!hasStarted && thumbnailUrl != null && (
onSelect(result.slug)}
accessibilityRole="button"
accessibilityLabel={`${result.title}: ${result.snippet}`}
diff --git a/apps/mobile/src/components/sections/BibleQuotesCarouselRenderer.tsx b/apps/mobile/src/components/sections/BibleQuotesCarouselRenderer.tsx
index 6c2d58158..29d343667 100644
--- a/apps/mobile/src/components/sections/BibleQuotesCarouselRenderer.tsx
+++ b/apps/mobile/src/components/sections/BibleQuotesCarouselRenderer.tsx
@@ -106,6 +106,7 @@ function QuoteCard({
return null
return (
[
styles.ctaButton,
pressed && styles.ctaButtonPressed,
@@ -231,6 +232,7 @@ export function BibleQuotesCarouselRenderer({
)}
+ return
case "text":
return
case "relatedQuestions":
diff --git a/apps/mobile/src/components/sections/CuratedHomeLayout.tsx b/apps/mobile/src/components/sections/CuratedHomeLayout.tsx
index b0782d94b..48e00f743 100644
--- a/apps/mobile/src/components/sections/CuratedHomeLayout.tsx
+++ b/apps/mobile/src/components/sections/CuratedHomeLayout.tsx
@@ -201,6 +201,7 @@ export function CuratedHomeLayout() {
>
{muteButtonRect != null && (
[
card.surface,
{ width: cardWidth },
diff --git a/apps/mobile/src/components/sections/NavigationCarouselRenderer.tsx b/apps/mobile/src/components/sections/NavigationCarouselRenderer.tsx
index 60f76a74d..7472dff78 100644
--- a/apps/mobile/src/components/sections/NavigationCarouselRenderer.tsx
+++ b/apps/mobile/src/components/sections/NavigationCarouselRenderer.tsx
@@ -62,6 +62,7 @@ export function NavigationCarouselRenderer({
return (
[
card.base,
styles.localCard,
diff --git a/apps/mobile/src/components/sections/QuizButtonRenderer.tsx b/apps/mobile/src/components/sections/QuizButtonRenderer.tsx
index ba347dcc0..a664049c8 100644
--- a/apps/mobile/src/components/sections/QuizButtonRenderer.tsx
+++ b/apps/mobile/src/components/sections/QuizButtonRenderer.tsx
@@ -68,6 +68,7 @@ function QuizModal({ url, onClose }: { url: string; onClose: () => void }) {
[styles.button, pressed && feedback.pressed]}
onPress={() => setModalVisible(true)}
accessibilityRole="button"
diff --git a/apps/mobile/src/components/sections/RelatedQuestionsRenderer.tsx b/apps/mobile/src/components/sections/RelatedQuestionsRenderer.tsx
index aa1651882..b15836fcc 100644
--- a/apps/mobile/src/components/sections/RelatedQuestionsRenderer.tsx
+++ b/apps/mobile/src/components/sections/RelatedQuestionsRenderer.tsx
@@ -30,10 +30,12 @@ export interface RelatedQuestionsRendererProps {
function QuestionRow({
item,
+ index,
isExpanded,
onToggle,
}: {
item: QuestionItem
+ index: number
isExpanded: boolean
onToggle: () => void
}) {
@@ -42,6 +44,7 @@ function QuestionRow({
return (
)}
- {questions.map((item) => (
+ {questions.map((item, index) => (
handleToggle(item.id)}
/>
diff --git a/apps/mobile/src/components/sections/VideoCardRenderer.tsx b/apps/mobile/src/components/sections/VideoCardRenderer.tsx
index 23e9d13dc..7078df941 100644
--- a/apps/mobile/src/components/sections/VideoCardRenderer.tsx
+++ b/apps/mobile/src/components/sections/VideoCardRenderer.tsx
@@ -22,11 +22,15 @@ import type { VideoRef } from "../../lib/types"
export interface VideoCardRendererProps {
section: NormalizedBlock
+ index?: number
}
// ── Component ───────────────────────────────────────────────────────────────
-export function VideoCardRenderer({ section }: VideoCardRendererProps) {
+export function VideoCardRenderer({
+ section,
+ index = 0,
+}: VideoCardRendererProps) {
const router = useRouter()
const typography = useTypography()
@@ -53,6 +57,7 @@ export function VideoCardRenderer({ section }: VideoCardRendererProps) {
return (
[
styles.container,
pressed && Platform.OS === "ios" && feedback.pressed,
diff --git a/apps/mobile/src/components/sections/VideoCarouselRenderer.tsx b/apps/mobile/src/components/sections/VideoCarouselRenderer.tsx
index 298a9e8bc..61b4f2d81 100644
--- a/apps/mobile/src/components/sections/VideoCarouselRenderer.tsx
+++ b/apps/mobile/src/components/sections/VideoCarouselRenderer.tsx
@@ -88,6 +88,7 @@ export function VideoCarouselRenderer({ section }: VideoCarouselRendererProps) {
return (
[
card.surface,
{ width: cardWidth, height: cardHeight },
diff --git a/apps/mobile/src/components/sections/VideoHeroRenderer.tsx b/apps/mobile/src/components/sections/VideoHeroRenderer.tsx
index 4bdd88a05..91414aaa1 100644
--- a/apps/mobile/src/components/sections/VideoHeroRenderer.tsx
+++ b/apps/mobile/src/components/sections/VideoHeroRenderer.tsx
@@ -276,6 +276,7 @@ export function VideoHeroRenderer({
)}
{hasCta && (
[
styles.ctaButton,
pressed && feedback.pressed,
diff --git a/apps/mobile/src/components/ui/HomeHeader.tsx b/apps/mobile/src/components/ui/HomeHeader.tsx
index a926762e6..3001f0aa0 100644
--- a/apps/mobile/src/components/ui/HomeHeader.tsx
+++ b/apps/mobile/src/components/ui/HomeHeader.tsx
@@ -31,6 +31,7 @@ export function HomeHeader({ title, titleOpacity }: HomeHeaderProps) {
pointerEvents="none"
/>
router.navigate("/(tabs)/watch")}
@@ -57,6 +58,7 @@ export function HomeHeader({ title, titleOpacity }: HomeHeaderProps) {
)}
router.navigate("/(tabs)/profile")}
diff --git a/apps/web/.gitignore b/apps/web/.gitignore
new file mode 100644
index 000000000..5c4ffa21f
--- /dev/null
+++ b/apps/web/.gitignore
@@ -0,0 +1,2 @@
+playwright-report/
+test-results/
diff --git a/apps/web/src/components/SearchOverlay.tsx b/apps/web/src/components/SearchOverlay.tsx
index bc38bf561..c243a0246 100644
--- a/apps/web/src/components/SearchOverlay.tsx
+++ b/apps/web/src/components/SearchOverlay.tsx
@@ -243,6 +243,7 @@ export function SearchOverlay({ open, onClose, closing }: SearchOverlayProps) {
onClick={onClose}
className="rounded-full p-3 text-stone-400 transition hover:text-white"
aria-label="Close search"
+ data-testid="search-close"
>