TỔNG QUAN Trong một monorepo React phục vụ cả Next.js (web) và Expo/React Native (mobile), việc duy trì bộ kiểm thử end‑to‑end (E2E) riêng biệt thường dẫn đến trùng lặp logic, sai lệch hành vi và chi phí bảo trì cao. Bài viết này chia sẻ cách xây dựng một lớp kiểm thử chung, tái sử dụng dữ liệu test, helper và cấu hình CI để cả hai nền tảng chạy trên cùng một pipeline. CẤU TRÚC THƯ MỤC GỢI Ý - apps/web-e2e // Playwright tests cho Next.js - apps/mobile-e2e // Detox tests cho React Native - packages/e2e-shared // Utilities, fixtures, custom commands dùng chung - turbo.json / nx.json // Cấu hình task pipeline BƯỚC 1: TẠO PACKAGE CHUNG (e2e-shared) - Xuất các hàm helper như loginUser, seedDatabase, waitForApi. - Định nghĩa type TestContext chứa baseUrl (web) và deviceId (mobile) để test viết một lần, chạy hai nơi. - Cung cấp fixture dữ liệu mẫu (users, products) dưới dạng JSON, nạp bằng fs.readFileSync trong Node. BƯỚC 2: CẤU HÌNH PLAYWRIGHT CHO WEB - Trong apps/web-e2e/playwright.config.ts import shared helpers. - Sử dụng projects để chạy trên Chromium, Firefox, WebKit. - Thêm globalSetup/globalTeardown để seed DB một lần cho toàn bộ suite. BƯỚC 3: CẤU HÌNH DETOX CHO MOBILE - Trong apps/mobile-e2e/.detoxrc.json định nghĩa configurations cho iOS simulator và Android emulator. - Viết custom Detox matcher (expect(element).toHaveTextFromShared()) gọi helper từ e2e-shared. - Sử dụng detox test --configuration ios.debug để chạy cục bộ. BƯỚC 4: VIẾT TEST CASE CHUNG ```typescript // packages/e2e-shared/tests/login.test.ts export const loginFlow = async (ctx: TestContext) => { await ctx.loginUser('test@example.com', 'password'); await ctx.expectDashboardVisible(); }; ``` - Web test: import { loginFlow } from 'e2e-shared/tests/login.test'; await loginFlow(webCtx); - Mobile test: import { loginFlow } from 'e2e-shared/tests/login.test'; await loginFlow(mobileCtx); BƯỚC 5: TÍCH HỢP VÀO CI (GITHUB ACTIONS + TURBOREPO) - Job "e2e-web" chạy `turbo run test:e2e --filter=web-e2e`. - Job "e2e-mobile" chạy `turbo run test:e2e --filter=mobile-e2e` trên macOS runner (iOS) và ubuntu (Android). - Sử dụng artifact upload để lưu screenshot/video khi fail, giúp debug nhanh. LỜI KHUYÊN THỰC TẾ 1. Giữ selector ổn định: dùng data-testid trên web và testID trên React Native, map qua shared constant. 2. Tách environment config (baseUrl, device) vào file .env.test, nạp bằng dotenv-cli. 3. Chạy song song: Turborepo tự động parallelize các task không phụ thuộc, giảm thời gian pipeline. 4. Theo dõi flakiness: bật retry 2 lần trong Playwright và Detox, ghi log vào dashboard để phát hiện test không ổn định. 5. Cập nhật shared helpers khi API thay đổi, tránh sửa từng repo con. KẾT LUẬN Việc tập trung logic E2E vào package shared giúp đội ngũ duy nhất một nguồn sự thật cho hành vi người dùng, giảm thiểu bug chỉ xuất hiện trên một nền tảng và tăng tốc độ phát hành. Hãy bắt đầu từ một flow đăng nhập đơn giản, mở rộng dần đến các luồng nghiệp vụ phức tạp như thanh toán, đồng bộ offline, hay feature flag.