📊 Resumen de ejecución
33
Tests pasando
1
Tests fallando
3
Tests omitidos
5m 46s
Duración total (iOS+Android)
🗂 Suites de test
| Archivo | Plataforma | Tests | Estado | Duración | Retries |
|---|---|---|---|---|---|
| e2e/login.test.js | iOSAndroid | 3 × 2 = 6 | ● PASS | 42s | 0 |
| e2e/onboarding.test.js | iOSAndroid | 5 × 2 = 10 | ● PASS | 1m 08s | 0 |
| e2e/dashboard.test.js | iOSAndroid | 4 × 2 = 8 | ✖ FAIL | 1m 22s | 2 |
| e2e/addMeal.test.js | iOSAndroid | 4 × 2 = 8 | ● PASS | 1m 04s | 1 |
| e2e/shop.test.js | iOS | 3 (Android ⏭ skip) | ⏭ SKIP* | 30s | 0 |
🔍 Detalle del fallo — dashboard.test.js (Android)
❌ Test: "should pull to refresh and show updated recommendations"
● Dashboard Screen › should pull to refresh and show updated recommendations
TimeoutError: Timed out waiting for element to become visible after 5000ms
Element: by.id("refresh-indicator")
Expected: toBeNotVisible()
Actual: still visible after 5000ms
Causa probable: el endpoint /api/recommendations tarda >5s en el emulador Android
en GitHub Actions (runner Ubuntu + AVD emulado, sin aceleración GPU).
Fix recomendado: aumentar timeout a 10000ms o mockear red con detox.setNetworkMock()
at e2e/dashboard.test.js:28:5
at Object.<anonymous> (e2e/dashboard.test.js:21:3)
📄 Archivos de configuración y tests
// .detoxrc.js — NutriPaw Detox configuration
module.exports = {
testRunner: {
args: { config: 'e2e/jest.config.js' },
jest: { setupTimeout: 120000 },
},
apps: {
'ios.debug': {
type: 'ios.app',
binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/NutriPaw.app',
build: 'xcodebuild -workspace ios/NutriPaw.xcworkspace -scheme NutriPaw -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build',
},
'android.debug': {
type: 'android.apk',
binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
reversePorts: [8081],
},
},
devices: {
simulator: { type: 'ios.simulator', device: { type: 'iPhone 15' } },
emulator: { type: 'android.emulator', device: { avdName: 'Pixel_7_API_34' } },
},
configurations: {
'ios.sim.debug': { device: 'simulator', app: 'ios.debug' },
'android.emu.debug': { device: 'emulator', app: 'android.debug' },
},
};
// e2e/login.test.js — NutriPaw Login Flow
describe('Login Flow', () => {
beforeAll(async () => {
await device.launchApp({ newInstance: true });
});
beforeEach(async () => {
await device.reloadReactNative();
});
it('muestra pantalla de login al arrancar', async () => {
await expect(element(by.id('login-screen'))).toBeVisible();
await expect(element(by.id('email-input'))).toBeVisible();
await expect(element(by.id('password-input'))).toBeVisible();
});
it('login exitoso con credenciales válidas', async () => {
await element(by.id('email-input')).typeText('laura@nutripaw.app');
await element(by.id('password-input')).typeText('Test1234!');
await element(by.id('login-button')).tap();
await expect(element(by.id('home-screen'))).toBeVisible();
await expect(element(by.text('¡Hola, Laura!'))).toBeVisible();
});
it('muestra error con credenciales incorrectas', async () => {
await element(by.id('email-input')).typeText('hacker@bad.com');
await element(by.id('password-input')).typeText('wrongpass');
await element(by.id('login-button')).tap();
await expect(element(by.id('error-message'))).toBeVisible();
await expect(element(by.text('Credenciales incorrectas'))).toBeVisible();
});
});
// e2e/onboarding.test.js — NutriPaw Registro de mascota
describe('Onboarding — Registro de mascota', () => {
beforeAll(async () => {
await device.launchApp({ newInstance: true, delete: true });
});
it('muestra bienvenida en primer arranque', async () => {
await expect(element(by.id('welcome-screen'))).toBeVisible();
});
it('permite seleccionar especie (perro)', async () => {
await element(by.id('start-btn')).tap();
await element(by.id('species-dog')).tap();
await expect(element(by.id('species-dog'))).toHaveToggleValue(true);
});
it('rellena datos de la mascota', async () => {
await element(by.id('next-btn')).tap();
await element(by.id('pet-name-input')).typeText('Rocky');
await element(by.id('pet-weight-input')).typeText('12');
await element(by.id('pet-age-input')).typeText('3');
});
it('valida campos obligatorios vacíos', async () => {
await device.reloadReactNative();
await element(by.id('start-btn')).tap();
await element(by.id('next-btn')).tap();
await expect(element(by.id('species-error'))).toBeVisible();
});
it('completa onboarding y muestra dashboard', async () => {
await element(by.id('species-dog')).tap();
await element(by.id('next-btn')).tap();
await element(by.id('pet-name-input')).typeText('Rocky');
await element(by.id('pet-weight-input')).typeText('12');
await element(by.id('pet-age-input')).typeText('3');
await element(by.id('finish-onboarding-btn')).tap();
await expect(element(by.id('home-screen'))).toBeVisible();
await expect(element(by.text('Recomendaciones para Rocky'))).toBeVisible();
});
});
// e2e/dashboard.test.js — NutriPaw Dashboard + scroll + pull-to-refresh
describe('Dashboard Screen', () => {
beforeAll(async () => {
await device.launchApp();
await element(by.id('email-input')).typeText('laura@nutripaw.app');
await element(by.id('password-input')).typeText('Test1234!');
await element(by.id('login-button')).tap();
});
it('muestra lista de recomendaciones', async () => {
await waitFor(element(by.id('recs-list'))).toBeVisible().withTimeout(5000);
await expect(element(by.id('rec-item-0'))).toBeVisible();
});
it('hace scroll hasta el ítem 5', async () => {
await element(by.id('recs-list')).scroll(600, 'down');
await expect(element(by.id('rec-item-5'))).toBeVisible();
});
it('navega al detalle de una recomendación', async () => {
await element(by.id('rec-item-0')).tap();
await expect(element(by.id('rec-detail-screen'))).toBeVisible();
await element(by.id('back-btn')).tap();
});
// ❌ Este test falla en Android (timeout 5s insuficiente en emulador)
it('pull-to-refresh actualiza recomendaciones', async () => {
await element(by.id('recs-list')).scroll(200, 'up');
// Fix: aumentar timeout 5000 → 10000 para Android
await waitFor(element(by.id('refresh-indicator')))
.toBeNotVisible()
.withTimeout(5000); // <— ampliar a 10000
});
});
# .github/workflows/detox.yml — NutriPaw E2E CI
name: Detox E2E
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
detox-ios:
name: E2E iOS (iPhone 15)
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: brew tap wix/brew && brew install applesimutils
- run: cd ios && pod install --repo-update
- run: detox build --configuration ios.sim.debug
- run: detox test --configuration ios.sim.debug --retries 2
- uses: actions/upload-artifact@v4
if: failure()
with:
name: detox-artifacts-ios
path: artifacts/
detox-android:
name: E2E Android (Pixel 7 API 34)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
profile: pixel_7
script: |
npm ci
detox build --configuration android.emu.debug
detox test --configuration android.emu.debug --retries 2
🚀 Pipeline CI — Run #47 (PR #83 → main)
✓
actions/checkout@v4
Checkout del código en branch feat/shop-cart
2s
✓
npm ci
Instalación de dependencias (Node 20, cache hit)
18s
✓
brew install applesimutils + pod install
iOS toolchain preparado en macos-14
1m 4s
✓
detox build — ios.sim.debug
xcodebuild NutriPaw.xcworkspace Debug iphonesimulator
3m 21s
✓
detox test — ios.sim.debug (--retries 2)
17/17 tests OK · iPhone 15 simulator · 0 retries necesarios
2m 34s
✓
detox build — android.emu.debug
./gradlew assembleDebug assembleAndroidTest · Pixel 7 API 34
4m 08s
✖
detox test — android.emu.debug (--retries 2)
16/17 OK · 1 fallo persistente en dashboard.test.js tras 2 retries · artefactos subidos
3m 12s
Próximos pasos:
Aumentar timeout pull-to-refresh a 10 000ms en Android ·
Añadir mock de red (
detox.setNetworkMock()) para tests de Shop en Android ·
Activar --record-videos failing en CI para debugging visual ·
Integrar con Allure para reportes HTML de cobertura E2E