Token导航 LogoToken导航

Agent Skill · 开发

detox-mobile-test(排毒移动测试)

用于辅助测试设计、自动化测试、用例整理和回归验证。它适合让 Agent 编写单元测试、端到端测试、测试计划或根据失败日志定位问题。使用时需要确认项目测试框架、运行命令和夹具数据,避免为了通过测试而改坏真实逻辑;涉及浏览器或外部服务时,应区分本地模拟、测试环境和生产环境。

detox-mobile-test 是开发类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证信息还不明确,第三方扫描已通过,按安装量在开发类的 21,746 个 Skill 里排第 10,665。

总安装 2,126周安装 86GitHub Stars 4下载 667核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:detox-mobile-test(排毒移动测试)
来源仓库:https://github.com/dengineproblem/agents-monorepo
仓库路径:skills/detox-mobile-test
安装命令:
npx skills add https://github.com/dengineproblem/agents-monorepo --skill detox-mobile-test
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/dengineproblem/agents-monorepo --skill detox-mobile-test

这个 Skill 是做什么的

开发类 Skill 面向写代码、改代码和排查问题,一般会读写仓库文件,部分还会执行构建或测试命令。

用于辅助测试设计、自动化测试和回归验证,适合编写单元或端到端测试。可帮助定位失败日志、整理测试用例或设计夹具数据。使用时需确认项目测试框架和运行命令,避免为通过而改坏逻辑。涉及浏览器或外部服务时应区分本地模拟与生产环境。detox-mobile-test 属于开发类 Skill,可作为该场景下的辅助能力补充。

来源仓库内路径:skills/detox-mobile-test

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/dengineproblem/agents-monorepo --skill detox-m…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

detox-mobile-test 怎么安装?

在终端执行 npx skills add https://github.com/dengineproblem/agents-monorepo --skill detox-mobile-test。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

detox-mobile-test 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

detox-mobile-test 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

detox-mobile-test 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

detox-mobile-test 用的人多吗?

公开数据是累计安装 2,126 次,近一周安装 86 次,下载 667 次,GitHub 4 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

detox-mobile-test 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理开发类任务、又不想每次都手写提示词的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

开发类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
skill-vetter

技能审查员 · OpenClaw

8,779,018986操作浏览器MIT-0通过
ontology

本体论 · OpenClaw

6,636,960576只读MIT-0通过
entra-app-registration

内部应用程序注册 · Codex、Claude

6,631,872764接触敏感数据未明确通过
entra-app-registration

内部应用程序注册 · Codex、Claude

3,757,680201接触敏感数据未明确通过
nano-banana-pro

纳米香蕉专业版 · OpenClaw

3,526,898364接触敏感数据MIT-0有提醒
detox-mobile-test

排毒移动测试 · 本页

2,1264需要联网未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Detox Mobile Testing Expert

Эксперт по E2E тестированию React Native приложений с Detox.

Core Testing Principles

Synchronization

  • Автоматическая синхронизация с React Native bridge
  • Синхронизация с анимациями и сетевыми запросами
  • waitFor() для явных ожиданий
  • toBeVisible() вместо toExist() для стабильности

Test Organization

  • AAA pattern (Arrange, Act, Assert)
  • Изоляция через beforeEach() и afterEach()
  • describe() для группировки
  • Page Object pattern для сложного UI

Configuration

.detoxrc.json

{
  "testRunner": {
    "args": {
      "$0": "jest",
      "config": "e2e/jest.config.js"
    },
    "jest": {
      "setupTimeout": 120000
    }
  },
  "apps": {
    "ios.debug": {
      "type": "ios.app",
      "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/MyApp.app",
      "build": "xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build"
    },
    "ios.release": {
      "type": "ios.app",
      "binaryPath": "ios/build/Build/Products/Release-iphonesimulator/MyApp.app",
      "build": "xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Release -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"
    },
    "android.release": {
      "type": "android.apk",
      "binaryPath": "android/app/build/outputs/apk/release/app-release.apk",
      "build": "cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release"
    }
  },
  "devices": {
    "simulator": {
      "type": "ios.simulator",
      "device": { "type": "iPhone 14" }
    },
    "emulator": {
      "type": "android.emulator",
      "device": { "avdName": "Pixel_4_API_30" }
    }
  },
  "configurations": {
    "ios.sim.debug": {
      "device": "simulator",
      "app": "ios.debug"
    },
    "ios.sim.release": {
      "device": "simulator",
      "app": "ios.release"
    },
    "android.emu.debug": {
      "device": "emulator",
      "app": "android.debug"
    },
    "android.emu.release": {
      "device": "emulator",
      "app": "android.release"
    }
  }
}

Jest Config

// e2e/jest.config.js
module.exports = {
  rootDir: '..',
  testMatch: ['<rootDir>/e2e/**/*.test.js'],
  testTimeout: 120000,
  maxWorkers: 1,
  globalSetup: 'detox/runners/jest/globalSetup',
  globalTeardown: 'detox/runners/jest/globalTeardown',
  reporters: ['detox/runners/jest/reporter'],
  testEnvironment: 'detox/runners/jest/testEnvironment',
  verbose: true
};

Basic Test Structure

describe('Login Flow', () => {
  beforeAll(async () => {
    await device.launchApp();
  });

  beforeEach(async () => {
    await device.reloadReactNative();
  });

  afterAll(async () => {
    await device.terminateApp();
  });

  it('should login with valid credentials', async () => {
    // Arrange
    const email = 'test@example.com';
    const password = 'password123';

    // Act
    await element(by.id('email-input')).typeText(email);
    await element(by.id('password-input')).typeText(password);
    await element(by.id('login-button')).tap();

    // Assert
    await expect(element(by.id('home-screen'))).toBeVisible();
  });

  it('should show error for invalid credentials', async () => {
    // Arrange
    const email = 'wrong@example.com';
    const password = 'wrongpassword';

    // Act
    await element(by.id('email-input')).typeText(email);
    await element(by.id('password-input')).typeText(password);
    await element(by.id('login-button')).tap();

    // Assert
    await expect(element(by.id('error-message'))).toBeVisible();
    await expect(element(by.text('Invalid credentials'))).toBeVisible();
  });
});

Element Matchers

// By testID
element(by.id('submit-button'))

// By text
element(by.text('Submit'))

// By label (accessibility)
element(by.label('Submit form'))

// By type
element(by.type('RCTTextInput'))

// By traits (iOS)
element(by.traits(['button']))

// Combining matchers
element(by.id('item').withAncestor(by.id('list')))
element(by.id('item').withDescendant(by.text('Title')))

// Index for multiple matches
element(by.id('list-item')).atIndex(0)

Actions

// Tap
await element(by.id('button')).tap();
await element(by.id('button')).multiTap(2);
await element(by.id('button')).longPress();
await element(by.id('button')).longPress(2000); // 2 seconds

// Text input
await element(by.id('input')).typeText('Hello');
await element(by.id('input')).replaceText('New text');
await element(by.id('input')).clearText();

// Scroll
await element(by.id('scrollView')).scroll(200, 'down');
await element(by.id('scrollView')).scroll(200, 'up');
await element(by.id('scrollView')).scrollTo('bottom');
await element(by.id('scrollView')).scrollTo('top');

// Scroll until visible
await waitFor(element(by.id('item')))
  .toBeVisible()
  .whileElement(by.id('scrollView'))
  .scroll(200, 'down');

// Swipe
await element(by.id('card')).swipe('left');
await element(by.id('card')).swipe('right', 'fast', 0.9);

// Pinch
await element(by.id('map')).pinch(1.5); // zoom in
await element(by.id('map')).pinch(0.5); // zoom out

Expectations

// Visibility
await expect(element(by.id('view'))).toBeVisible();
await expect(element(by.id('view'))).not.toBeVisible();
await expect(element(by.id('view'))).toExist();
await expect(element(by.id('view'))).not.toExist();

// Focus
await expect(element(by.id('input'))).toBeFocused();

// Text
await expect(element(by.id('label'))).toHaveText('Hello');
await expect(element(by.id('input'))).toHaveValue('input value');

// Toggle state
await expect(element(by.id('switch'))).toHaveToggleValue(true);

// Slider
await expect(element(by.id('slider'))).toHaveSliderPosition(0.5);

// ID
await expect(element(by.id('view'))).toHaveId('view');

// Label
await expect(element(by.id('button'))).toHaveLabel('Submit');

waitFor API

// Wait for element to be visible
await waitFor(element(by.id('loading')))
  .not.toBeVisible()
  .withTimeout(10000);

// Wait for element to exist
await waitFor(element(by.id('data')))
  .toExist()
  .withTimeout(5000);

// Wait while scrolling
await waitFor(element(by.id('item-50')))
  .toBeVisible()
  .whileElement(by.id('list'))
  .scroll(100, 'down');

// Custom polling
await waitFor(element(by.id('result')))
  .toHaveText('Success')
  .withTimeout(30000);

Page Object Pattern

// e2e/pages/LoginPage.js
class LoginPage {
  get emailInput() {
    return element(by.id('email-input'));
  }

  get passwordInput() {
    return element(by.id('password-input'));
  }

  get loginButton() {
    return element(by.id('login-button'));
  }

  get errorMessage() {
    return element(by.id('error-message'));
  }

  async login(email, password) {
    await this.emailInput.typeText(email);
    await this.passwordInput.typeText(password);
    await this.loginButton.tap();
  }

  async assertErrorVisible(message) {
    await expect(this.errorMessage).toBeVisible();
    if (message) {
      await expect(element(by.text(message))).toBeVisible();
    }
  }
}

module.exports = new LoginPage();

// e2e/tests/login.test.js
const LoginPage = require('../pages/LoginPage');
const HomePage = require('../pages/HomePage');

describe('Login', () => {
  it('should login successfully', async () => {
    await LoginPage.login('user@test.com', 'password123');
    await expect(HomePage.welcomeMessage).toBeVisible();
  });
});

Debugging

Verbose Logging

// In test
await device.launchApp({
  launchArgs: {
    detoxPrintBusyIdleResources: 'YES'
  }
});

Screenshots

// Take screenshot
await device.takeScreenshot('login-screen');

// On failure (in jest setup)
afterEach(async () => {
  if (jasmine.currentTest.failedExpectations.length > 0) {
    await device.takeScreenshot(`failed-${jasmine.currentTest.fullName}`);
  }
});

Element Debugging

// Get element attributes
const attributes = await element(by.id('button')).getAttributes();
console.log(attributes);
// { text: 'Submit', visible: true, enabled: true, ... }

Handling Common Issues

Disable Synchronization

// For non-React Native screens (WebViews, etc.)
await device.disableSynchronization();
await element(by.id('webview-button')).tap();
await device.enableSynchronization();

Permission Dialogs

// iOS
await device.launchApp({
  permissions: {
    notifications: 'YES',
    camera: 'YES',
    photos: 'YES',
    location: 'always'
  }
});

// Android - handle at runtime
await element(by.text('Allow')).tap();

Keyboard Issues

// Dismiss keyboard
await element(by.id('input')).typeText('text\n');
// or
await device.pressBack(); // Android

// Avoid keyboard overlap
await element(by.id('input')).tap();
await element(by.id('input')).typeText('text');
await element(by.id('submit')).tap();

CI/CD Integration

GitHub Actions

name: E2E Tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  ios-e2e:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Install pods
        run: cd ios && pod install

      - name: Build app
        run: npx detox build --configuration ios.sim.release

      - name: Run tests
        run: npx detox test --configuration ios.sim.release --cleanup

      - name: Upload artifacts
        if: failure()
        uses: actions/upload-artifact@v3
        with:
          name: detox-artifacts
          path: artifacts/

  android-e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '18'

      - name: Setup Java
        uses: actions/setup-java@v3
        with:
          distribution: 'zulu'
          java-version: '11'

      - name: Install dependencies
        run: npm ci

      - name: Build app
        run: npx detox build --configuration android.emu.release

      - name: Start emulator
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 30
          target: google_apis
          script: npx detox test --configuration android.emu.release --cleanup

Performance Tips

// Use reloadReactNative instead of launchApp
beforeEach(async () => {
  await device.reloadReactNative(); // Fast
  // await device.launchApp({ newInstance: true }); // Slow
});

// Record videos only on failure
// In detoxrc.json
{
  "artifacts": {
    "plugins": {
      "video": {
        "enabled": true,
        "keepOnlyFailedTestsArtifacts": true
      }
    }
  }
}

// Test sharding for parallel execution
// jest.config.js
module.exports = {
  maxWorkers: process.env.CI ? 2 : 1,
  // ...
};

Лучшие практики

  1. Stable selectors — используйте testID, не text
  2. Proper waits — waitFor вместо sleep
  3. Page Objects — переиспользуемые абстракции
  4. Isolated tests — каждый тест независим
  5. CI/CD first — тесты должны работать в CI
  6. Record on failure — видео/скриншоты при падении

平台分布

Codex

39.37%
按下载量换算263

Claude

27.98%
按下载量换算187

Cursor

20.43%
按下载量换算136

Gemini CLI

8.92%
按下载量换算59

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills